refactor: drop legacy implicit startup sidecar fallback

This commit is contained in:
Peter Steinberger
2026-05-01 22:57:10 +01:00
parent 5e3265b09b
commit 7ac23eeeb5
16 changed files with 59 additions and 260 deletions

View File

@@ -61,10 +61,8 @@ to narrow plugin loading before broader registry materialization:
- explicit provider setup/runtime resolution narrows to plugins that own the
requested provider id
- Gateway startup planning uses `activation.onStartup` for explicit startup
imports and startup opt-outs; every plugin should declare it as OpenClaw
moves away from implicit startup imports, while plugins without static
capability metadata and without `activation.onStartup` still use the
deprecated implicit startup sidecar fallback for compatibility
imports and startup opt-outs; plugins without startup metadata load only
through narrower activation triggers
The activation planner exposes both an ids-only API for existing callers and a
plan API for new diagnostics. Plan entries report why a plugin was selected,

View File

@@ -135,9 +135,6 @@ Current compatibility records include:
- legacy channel route key and comparable-target helper aliases while plugins
move to `openclaw/plugin-sdk/channel-route`
- activation hints that are being replaced by manifest contribution ownership
- deprecated implicit startup sidecar loading for plugins that have not declared
`activation.onStartup`; maintainers can test the future stricter behavior with
`OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS=1`
- `setup-api` runtime fallback while setup descriptors move to cold
`setup.requiresRuntime: false` metadata
- provider `discovery` hooks while provider catalog hooks move to

View File

@@ -255,24 +255,15 @@ embedded agent harness ids that do not already have an ownership field.
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.
missing non-startup activation metadata usually only costs performance; it
should not change correctness while manifest ownership fallbacks still exist.
Every plugin should set `activation.onStartup` intentionally as OpenClaw moves
away from implicit startup imports. Set it to `true` only when the plugin must
run during Gateway startup. Set it to `false` when the plugin is inert at
startup and should load only from narrower triggers. Omitting `onStartup` keeps
the deprecated legacy implicit startup sidecar fallback for plugins with no
static capability metadata; future versions may stop startup-loading those
plugins unless they declare `activation.onStartup: true`. Plugin status and
compatibility reports warn with `legacy-implicit-startup-sidecar` when a plugin
still relies on that fallback.
For migration testing, set
`OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS=1` to disable only that
deprecated fallback. This opt-in mode does not block explicit
`activation.onStartup: true` plugins or plugins loaded by channel, config,
agent-harness, memory, or other narrower activation triggers.
Every plugin should set `activation.onStartup` intentionally. Set it to `true`
only when the plugin must run during Gateway startup. Set it to `false` when
the plugin is inert at startup and should load only from narrower triggers.
Omitting `onStartup` no longer startup-loads the plugin implicitly; use explicit
activation metadata for startup, channel, config, agent-harness, memory, or
other narrower activation triggers.
```json
{
@@ -288,21 +279,21 @@ agent-harness, memory, or other narrower activation triggers.
}
```
| Field | Required | Type | What it means |
| ------------------ | -------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onStartup` | No | `boolean` | Explicit Gateway startup activation. Every plugin should set this. `true` imports the plugin during startup; `false` opts out of the deprecated implicit sidecar startup fallback unless another matched trigger requires loading. |
| `onProviders` | No | `string[]` | Provider ids that should include this plugin in activation/load plans. |
| `onAgentHarnesses` | No | `string[]` | Embedded agent harness runtime ids that should include this plugin in activation/load plans. Use top-level `cliBackends` for CLI backend aliases. |
| `onCommands` | No | `string[]` | Command ids that should include this plugin in activation/load plans. |
| `onChannels` | No | `string[]` | Channel ids that should include this plugin in activation/load plans. |
| `onRoutes` | No | `string[]` | Route kinds that should include this plugin in activation/load plans. |
| `onConfigPaths` | No | `string[]` | Root-relative config paths that should include this plugin in startup/load plans when the path is present and not explicitly disabled. |
| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. Prefer narrower fields when possible. |
| Field | Required | Type | What it means |
| ------------------ | -------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onStartup` | No | `boolean` | Explicit Gateway startup activation. Every plugin should set this. `true` imports the plugin during startup; `false` keeps it startup-lazy unless another matched trigger requires loading. |
| `onProviders` | No | `string[]` | Provider ids that should include this plugin in activation/load plans. |
| `onAgentHarnesses` | No | `string[]` | Embedded agent harness runtime ids that should include this plugin in activation/load plans. Use top-level `cliBackends` for CLI backend aliases. |
| `onCommands` | No | `string[]` | Command ids that should include this plugin in activation/load plans. |
| `onChannels` | No | `string[]` | Channel ids that should include this plugin in activation/load plans. |
| `onRoutes` | No | `string[]` | Route kinds that should include this plugin in activation/load plans. |
| `onConfigPaths` | No | `string[]` | Root-relative config paths that should include this plugin in startup/load plans when the path is present and not explicitly disabled. |
| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. Prefer narrower fields when possible. |
Current live consumers:
- Gateway startup planning uses `activation.onStartup` for explicit startup
import and opt-out of deprecated implicit sidecar startup fallback
import
- command-triggered CLI planning falls back to legacy
`commandAliases[].cliCommand` or `commandAliases[].name`
- agent-runtime startup planning uses `activation.onAgentHarnesses` for

View File

@@ -137,16 +137,6 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [
pluginCount: 50,
config: BASE_CONFIG,
},
{
id: "fiftyPluginsFutureStrict",
name: "gateway, 50 manifest plugins with legacy startup fallback disabled",
env: {
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
OPENCLAW_SKIP_CHANNELS: "1",
},
pluginCount: 50,
config: BASE_CONFIG,
},
{
id: "fiftyStartupLazyPlugins",
name: "gateway, 50 startup-lazy manifest plugins",

View File

@@ -52,6 +52,7 @@ async function writeWorkspacePlugin(params: {
workspaceDir: string;
id: string;
body: string;
activation?: { onStartup?: boolean };
}): Promise<void> {
const pluginDir = path.join(params.workspaceDir, ".openclaw", "extensions", params.id);
await fs.mkdir(pluginDir, { recursive: true });
@@ -60,6 +61,7 @@ async function writeWorkspacePlugin(params: {
`${JSON.stringify(
{
id: params.id,
...(params.activation ? { activation: params.activation } : {}),
configSchema: { type: "object", additionalProperties: false, properties: {} },
},
null,
@@ -233,6 +235,7 @@ describe("gateway e2e", () => {
await writeWorkspacePlugin({
workspaceDir,
id: "http-probe",
activation: { onStartup: true },
body: `
const fs = require("node:fs");
const counterPath = ${JSON.stringify(registerCountPath)};

View File

@@ -104,6 +104,7 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
id: "browser",
channels: [],
activation: {
onStartup: true,
onConfigPaths: ["browser"],
},
origin: "bundled",
@@ -212,6 +213,9 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
{
id: "voice-call",
channels: [],
activation: {
onStartup: true,
},
origin: "bundled",
enabledByDefault: undefined,
providers: [],
@@ -238,6 +242,9 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
{
id: "demo-global-sidecar",
channels: [],
activation: {
onStartup: true,
},
origin: "global",
enabledByDefault: undefined,
providers: [],
@@ -295,10 +302,6 @@ function normalizeStartupAgentHarnesses(record: PluginManifestRecord): readonly
].toSorted((left, right) => left.localeCompare(right));
}
function hasRuntimeContractSurface(record: PluginManifestRecord): boolean {
return record.providers.length > 0 || record.cliBackends.length > 0;
}
function hasPluginKind(record: PluginManifestRecord, kind: string): boolean {
return Array.isArray(record.kind) ? record.kind.includes(kind as never) : record.kind === kind;
}
@@ -317,12 +320,7 @@ function createInstalledPluginRecordFixture(
enabled: true,
...(record.enabledByDefault === true ? { enabledByDefault: true } : {}),
startup: {
sidecar:
record.activation?.onStartup === true ||
(record.activation?.onStartup === undefined &&
record.channels.length === 0 &&
!hasRuntimeContractSurface(record) &&
!memory),
sidecar: record.activation?.onStartup === true,
memory,
deferConfiguredChannelFullLoadUntilAfterListen:
record.startupDeferConfiguredChannelFullLoadUntilAfterListen === true,
@@ -654,35 +652,7 @@ describe("resolveGatewayStartupPluginIds", () => {
});
});
it("keeps deprecated implicit startup sidecar fallback for legacy plugins", () => {
expectStartupPluginIdsCase({
config: createStartupConfig({
enabledPluginIds: ["demo-global-sidecar"],
allowPluginIds: ["demo-global-sidecar"],
noConfiguredChannels: true,
memorySlot: "none",
}),
expected: ["demo-global-sidecar"],
});
});
it("can disable deprecated implicit startup sidecar fallback for future-mode testing", () => {
expectStartupPluginIdsCase({
config: createStartupConfig({
enabledPluginIds: ["demo-global-sidecar"],
allowPluginIds: ["demo-global-sidecar"],
noConfiguredChannels: true,
memorySlot: "none",
}),
env: {
...process.env,
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
},
expected: [],
});
});
it("skips deprecated implicit startup sidecar fallback when activation.onStartup is false", () => {
it("skips startup when activation.onStartup is false", () => {
expectStartupPluginIdsCase({
config: createStartupConfig({
enabledPluginIds: ["demo-global-startup-opt-out"],
@@ -702,10 +672,6 @@ describe("resolveGatewayStartupPluginIds", () => {
noConfiguredChannels: true,
memorySlot: "none",
}),
env: {
...process.env,
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
},
expected: ["demo-global-explicit-startup"],
});
});

View File

@@ -277,21 +277,6 @@ export const PLUGIN_COMPAT_RECORDS = [
diagnostics: ["activation plan compat reason"],
tests: ["src/plugins/activation-planner.test.ts"],
},
{
code: "legacy-implicit-startup-sidecar",
status: "deprecated",
owner: "plugin-execution",
introduced: "2026-04-28",
deprecated: "2026-04-28",
warningStarts: "2026-04-28",
removeAfter: "2026-07-28",
replacement:
"`activation.onStartup: true` for startup work or `activation.onStartup: false` for inert plugins",
docsPath: "/plugins/manifest",
surfaces: ["Gateway startup plugin planning", "openclaw.plugin.json activation"],
diagnostics: ["plugin compatibility notice"],
tests: ["src/plugins/channel-plugin-ids.test.ts", "src/plugins/installed-plugin-index.test.ts"],
},
{
code: "activation-provider-hint",
status: "active",

View File

@@ -23,18 +23,6 @@ import {
} from "./plugin-registry-contributions.js";
import { loadPluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
const DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS_ENV =
"OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS";
function isTruthyEnvValue(value: string | undefined): boolean {
const normalized = value?.trim().toLowerCase();
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
}
function shouldDisableLegacyImplicitStartupSidecars(env: NodeJS.ProcessEnv): boolean {
return isTruthyEnvValue(env[DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS_ENV]);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
@@ -60,18 +48,6 @@ function isGatewayStartupMemoryPlugin(plugin: InstalledPluginIndexRecord): boole
return plugin.startup.memory;
}
/**
* @deprecated Compatibility fallback for plugins that do not declare
* `activation.onStartup`. Keep this path visible so we can remove it after
* plugin manifests migrate to explicit startup activation.
*/
function isDeprecatedLegacyImplicitStartupSidecar(params: {
plugin: InstalledPluginIndexRecord;
manifest: PluginManifestRecord | undefined;
}): boolean {
return params.plugin.startup.sidecar && params.manifest?.activation?.onStartup === undefined;
}
function resolveGatewayStartupDreamingPluginIds(config: OpenClawConfig): Set<string> {
const dreamingConfig = resolveMemoryDreamingConfig({
pluginConfig: resolveMemoryDreamingPluginConfig(config),
@@ -112,29 +88,12 @@ function resolveMemorySlotStartupPluginId(params: {
function shouldConsiderForGatewayStartup(params: {
plugin: InstalledPluginIndexRecord;
manifest: PluginManifestRecord | undefined;
disableLegacyImplicitStartupSidecars: boolean;
startupDreamingPluginIds: ReadonlySet<string>;
memorySlotStartupPluginId?: string;
}): boolean {
if (params.manifest?.activation?.onStartup === true) {
return true;
}
if (params.plugin.startup.sidecar) {
if (params.manifest?.activation?.onStartup === false) {
return false;
}
if (params.disableLegacyImplicitStartupSidecars) {
return false;
}
// Deprecated compatibility fallback: plugins without explicit startup
// activation metadata may still need startup import to register hooks or
// services. All plugins should declare activation.onStartup explicitly as
// we migrate away from implicit startup sidecar loading.
return isDeprecatedLegacyImplicitStartupSidecar({
plugin: params.plugin,
manifest: params.manifest,
});
}
if (!isGatewayStartupMemoryPlugin(params.plugin)) {
return false;
}
@@ -391,9 +350,6 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
collectConfiguredAgentHarnessRuntimes(activationSourceConfig, params.env),
);
const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config);
const disableLegacyImplicitStartupSidecars = shouldDisableLegacyImplicitStartupSidecars(
params.env,
);
const manifestLookup = createManifestRegistryLookup(params.manifestRegistry);
const memorySlotStartupPluginId = resolveMemorySlotStartupPluginId({
activationSourceConfig,
@@ -448,7 +404,6 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
!shouldConsiderForGatewayStartup({
plugin,
manifest,
disableLegacyImplicitStartupSidecars,
startupDreamingPluginIds,
memorySlotStartupPluginId,
})

View File

@@ -338,7 +338,7 @@ describe("installPluginFromPath", () => {
expect(fs.existsSync(path.join(result.targetDir, ".claude-plugin", "plugin.json"))).toBe(true);
});
it("prefers native package metadata for dual-format archives", async () => {
it("prefers native package metadata without installing dependencies for dual-format archives", async () => {
const { pluginDir, extensionsDir } = setupDualFormatInstallFixture({
bundleFormat: "claude",
});
@@ -371,5 +371,6 @@ describe("installPluginFromPath", () => {
expect(result.pluginId).toBe("native-dual");
expect(result.targetDir).toBe(path.join(extensionsDir, "native-dual"));
expect(run).not.toHaveBeenCalled();
expect(fs.existsSync(path.join(result.targetDir, "node_modules"))).toBe(false);
});
});

View File

@@ -29,44 +29,9 @@ function sortUnique(values: readonly string[] | undefined): readonly string[] {
);
}
function hasRuntimeContractSurface(record: PluginManifestRecord): boolean {
const providers = record.providers ?? [];
const cliBackends = record.cliBackends ?? [];
return Boolean(
providers.length > 0 ||
cliBackends.length > 0 ||
record.contracts?.speechProviders?.length ||
record.contracts?.mediaUnderstandingProviders?.length ||
record.contracts?.documentExtractors?.length ||
record.contracts?.imageGenerationProviders?.length ||
record.contracts?.videoGenerationProviders?.length ||
record.contracts?.musicGenerationProviders?.length ||
record.contracts?.webContentExtractors?.length ||
record.contracts?.webFetchProviders?.length ||
record.contracts?.webSearchProviders?.length ||
record.contracts?.migrationProviders?.length ||
record.contracts?.memoryEmbeddingProviders?.length ||
hasKind(record.kind, "memory"),
);
}
/**
* @deprecated Compatibility classification for plugins that predate explicit
* `activation.onStartup`. Every plugin manifest should move to an explicit
* startup decision so Gateway boot can avoid importing inert plugins.
*/
function isLegacyImplicitStartupSidecar(record: PluginManifestRecord): boolean {
const channels = Array.isArray(record.channels) ? record.channels : [];
return (
channels.length === 0 &&
!hasRuntimeContractSurface(record) &&
record.activation?.onStartup === undefined
);
}
function buildStartupInfo(record: PluginManifestRecord): InstalledPluginStartupInfo {
return {
sidecar: record.activation?.onStartup === true || isLegacyImplicitStartupSidecar(record),
sidecar: record.activation?.onStartup === true,
memory: hasKind(record.kind, "memory"),
deferConfiguredChannelFullLoadUntilAfterListen:
record.startupDeferConfiguredChannelFullLoadUntilAfterListen === true,
@@ -81,9 +46,6 @@ export function collectPluginManifestCompatCodes(
record: PluginManifestRecord,
): readonly PluginCompatCode[] {
const codes: PluginCompatCode[] = [];
if (isLegacyImplicitStartupSidecar(record)) {
codes.push("legacy-implicit-startup-sidecar");
}
if (record.providerAuthEnvVars && Object.keys(record.providerAuthEnvVars).length > 0) {
codes.push("provider-auth-env-vars");
}

View File

@@ -270,7 +270,7 @@ describe("installed plugin index", () => {
});
});
it("tags deprecated implicit startup sidecars for legacy plugins", () => {
it("does not classify legacy plugins as startup sidecars", () => {
const rootDir = makeTempDir();
writeRuntimeEntry(rootDir);
writePluginManifest(rootDir, {
@@ -290,9 +290,9 @@ describe("installed plugin index", () => {
expect(index.plugins[0]).toMatchObject({
pluginId: "legacy-sidecar",
startup: {
sidecar: true,
sidecar: false,
},
compat: ["legacy-implicit-startup-sidecar"],
compat: [],
});
});
@@ -326,9 +326,9 @@ describe("installed plugin index", () => {
expect(records[0]).toMatchObject({
pluginId: "stale-record",
startup: {
sidecar: true,
sidecar: false,
},
compat: ["legacy-implicit-startup-sidecar"],
compat: [],
});
});
@@ -394,7 +394,7 @@ describe("installed plugin index", () => {
expect(second.plugins[0]?.manifestHash).not.toBe(first.plugins[0]?.manifestHash);
});
it("does not classify or tag explicit startup opt-outs as deprecated implicit sidecars", () => {
it("keeps explicit startup opt-outs out of startup sidecars", () => {
const rootDir = makeTempDir();
writeRuntimeEntry(rootDir);
writePluginManifest(rootDir, {

View File

@@ -155,11 +155,9 @@ export type PluginManifestActivationCapability = "provider" | "channel" | "tool"
export type PluginManifestActivation = {
/**
* Explicit Gateway startup activation. Every plugin should set this as
* OpenClaw moves away from implicit startup sidecar loading. Set true when
* the plugin must be imported during Gateway startup; set false to opt out
* of the deprecated implicit startup sidecar fallback when no other
* activation trigger matches.
* Explicit Gateway startup activation. Set true when the plugin must be
* imported during Gateway startup; set false when narrower activation
* triggers should load it on demand.
*/
onStartup?: boolean;
/**

View File

@@ -47,18 +47,16 @@ describe("plugin compatibility snapshot notices", () => {
cleanupPluginLoaderFixturesForTest();
});
it("reports implicit startup sidecar compatibility from a real legacy manifest", () => {
it("does not report startup compatibility warnings for legacy manifests", () => {
const plugin = writePlugin({
id: "legacy-sidecar",
body: `module.exports = { id: "legacy-sidecar", register() {} };\n`,
});
expect(buildSnapshotCompatibilityNoticeCodes(plugin)).toEqual([
"legacy-implicit-startup-sidecar",
]);
expect(buildSnapshotCompatibilityNoticeCodes(plugin)).toEqual([]);
});
it("does not report implicit startup compatibility for explicit startup-lazy manifests", () => {
it("does not report startup compatibility warnings for explicit startup-lazy manifests", () => {
const plugin = writePlugin({
id: "modern-startup-lazy",
body: `module.exports = { id: "modern-startup-lazy", register() {} };\n`,

View File

@@ -5,8 +5,6 @@ import type { PluginHookName } from "./types.js";
export const LEGACY_BEFORE_AGENT_START_MESSAGE =
"still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.";
export const LEGACY_IMPLICIT_STARTUP_SIDECAR_MESSAGE =
"relies on deprecated implicit startup loading; add activation.onStartup: true for startup work or activation.onStartup: false for startup-lazy plugins.";
export const HOOK_ONLY_MESSAGE =
"is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.";
@@ -22,14 +20,6 @@ export function createCompatibilityNotice(
severity: "warn",
message: LEGACY_BEFORE_AGENT_START_MESSAGE,
};
case "legacy-implicit-startup-sidecar":
return {
pluginId: params.pluginId,
code: params.code,
compatCode: "legacy-implicit-startup-sidecar",
severity: "warn",
message: LEGACY_IMPLICIT_STARTUP_SIDECAR_MESSAGE,
};
case "hook-only":
return {
pluginId: params.pluginId,

View File

@@ -7,7 +7,6 @@ import {
createTypedHook,
HOOK_ONLY_MESSAGE,
LEGACY_BEFORE_AGENT_START_MESSAGE,
LEGACY_IMPLICIT_STARTUP_SIDECAR_MESSAGE,
} from "./status.test-helpers.js";
const loadConfigMock = vi.fn();
@@ -450,34 +449,34 @@ describe("plugin status reports", () => {
loadPluginRegistrySnapshotWithMetadataMock.mockReturnValue({
snapshot: createInstalledPluginIndexSnapshot([
{
pluginId: "legacy-sidecar",
manifestPath: "/tmp/legacy-sidecar/openclaw.plugin.json",
pluginId: "provider-env-plugin",
manifestPath: "/tmp/provider-env-plugin/openclaw.plugin.json",
manifestHash: "manifest-hash",
rootDir: "/tmp/legacy-sidecar",
rootDir: "/tmp/provider-env-plugin",
origin: "workspace",
enabled: true,
startup: {
sidecar: true,
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: ["legacy-implicit-startup-sidecar"],
compat: ["provider-auth-env-vars"],
},
]),
source: "derived",
diagnostics: [],
});
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue({
plugins: [{ id: "legacy-sidecar", name: "Legacy Sidecar" }],
plugins: [{ id: "provider-env-plugin", name: "Provider Env Plugin" }],
diagnostics: [],
});
const report = buildPluginRegistrySnapshotReport({ config: {} });
expect(report.plugins[0]).toMatchObject({
id: "legacy-sidecar",
compat: ["legacy-implicit-startup-sidecar"],
id: "provider-env-plugin",
compat: ["provider-auth-env-vars"],
});
});
@@ -843,27 +842,7 @@ describe("plugin status reports", () => {
});
});
it("builds compatibility warnings for deprecated implicit startup sidecar metadata", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "legacy-sidecar",
name: "Legacy Sidecar",
compat: ["legacy-implicit-startup-sidecar"],
}),
);
expectCompatibilityOutput({
notices: [
createCompatibilityNotice({
pluginId: "legacy-sidecar",
code: "legacy-implicit-startup-sidecar",
}),
],
warnings: [`legacy-sidecar ${LEGACY_IMPLICIT_STARTUP_SIDECAR_MESSAGE}`],
});
});
it("does not warn when explicit startup-lazy metadata avoids legacy startup compatibility", () => {
it("does not warn for explicit startup-lazy metadata", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "modern-startup-lazy",
@@ -939,14 +918,10 @@ describe("plugin status reports", () => {
expect(
summarizePluginCompatibility([
notice,
createCompatibilityNotice({
pluginId: "legacy-plugin",
code: "legacy-implicit-startup-sidecar",
}),
createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }),
]),
).toEqual({
noticeCount: 3,
noticeCount: 2,
pluginCount: 1,
});
});

View File

@@ -53,7 +53,7 @@ export type { PluginCapabilityKind, PluginInspectShape } from "./inspect-shape.j
export type PluginCompatibilityNotice = {
pluginId: string;
code: "legacy-before-agent-start" | "legacy-implicit-startup-sidecar" | "hook-only";
code: "legacy-before-agent-start" | "hook-only";
compatCode: PluginCompatCode;
severity: "warn" | "info";
message: string;
@@ -124,16 +124,6 @@ function buildCompatibilityNoticesForInspect(
"still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.",
});
}
if (inspect.plugin.compat?.includes("legacy-implicit-startup-sidecar")) {
warnings.push({
pluginId: inspect.plugin.id,
code: "legacy-implicit-startup-sidecar",
compatCode: "legacy-implicit-startup-sidecar",
severity: "warn",
message:
"relies on deprecated implicit startup loading; add activation.onStartup: true for startup work or activation.onStartup: false for startup-lazy plugins.",
});
}
if (inspect.shape === "hook-only") {
warnings.push({
pluginId: inspect.plugin.id,