From 5d52233c25e888cbb656c77c6e7081c571704c30 Mon Sep 17 00:00:00 2001 From: Shakker Date: Tue, 28 Apr 2026 02:55:45 +0100 Subject: [PATCH] refactor: mark implicit startup sidecars deprecated --- src/plugins/compat/registry.ts | 15 ++++ .../installed-plugin-index-record-builder.ts | 19 ++++- src/plugins/installed-plugin-index.test.ts | 84 +++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/plugins/compat/registry.ts b/src/plugins/compat/registry.ts index 85899f34e9f..62ab50f8777 100644 --- a/src/plugins/compat/registry.ts +++ b/src/plugins/compat/registry.ts @@ -206,6 +206,21 @@ 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", diff --git a/src/plugins/installed-plugin-index-record-builder.ts b/src/plugins/installed-plugin-index-record-builder.ts index f33cd03ad9c..3f0d1434be5 100644 --- a/src/plugins/installed-plugin-index-record-builder.ts +++ b/src/plugins/installed-plugin-index-record-builder.ts @@ -49,10 +49,22 @@ function hasRuntimeContractSurface(record: PluginManifestRecord): boolean { ); } +/** + * @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 { + return ( + record.channels.length === 0 && + !hasRuntimeContractSurface(record) && + record.activation?.onStartup === undefined + ); +} + function buildStartupInfo(record: PluginManifestRecord): InstalledPluginStartupInfo { - const channels = record.channels ?? []; return { - sidecar: channels.length === 0 && !hasRuntimeContractSurface(record), + sidecar: record.activation?.onStartup === true || isLegacyImplicitStartupSidecar(record), memory: hasKind(record.kind, "memory"), deferConfiguredChannelFullLoadUntilAfterListen: record.startupDeferConfiguredChannelFullLoadUntilAfterListen === true, @@ -65,6 +77,9 @@ function buildStartupInfo(record: PluginManifestRecord): InstalledPluginStartupI function collectCompatCodes(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"); } diff --git a/src/plugins/installed-plugin-index.test.ts b/src/plugins/installed-plugin-index.test.ts index decff32735a..83dbfc52af6 100644 --- a/src/plugins/installed-plugin-index.test.ts +++ b/src/plugins/installed-plugin-index.test.ts @@ -263,6 +263,90 @@ describe("installed plugin index", () => { }); }); + it("tags deprecated implicit startup sidecars for legacy plugins", () => { + const rootDir = makeTempDir(); + writeRuntimeEntry(rootDir); + writePluginManifest(rootDir, { + id: "legacy-sidecar", + configSchema: { type: "object" }, + }); + + const index = loadInstalledPluginIndex({ + candidates: [ + createPluginCandidate({ + rootDir, + }), + ], + env: hermeticEnv(), + }); + + expect(index.plugins[0]).toMatchObject({ + pluginId: "legacy-sidecar", + startup: { + sidecar: true, + }, + compat: ["legacy-implicit-startup-sidecar"], + }); + }); + + it("does not classify or tag explicit startup opt-outs as deprecated implicit sidecars", () => { + const rootDir = makeTempDir(); + writeRuntimeEntry(rootDir); + writePluginManifest(rootDir, { + id: "modern-inert", + activation: { + onStartup: false, + }, + configSchema: { type: "object" }, + }); + + const index = loadInstalledPluginIndex({ + candidates: [ + createPluginCandidate({ + rootDir, + }), + ], + env: hermeticEnv(), + }); + + expect(index.plugins[0]).toMatchObject({ + pluginId: "modern-inert", + startup: { + sidecar: false, + }, + compat: [], + }); + }); + + it("classifies explicit startup activation as a gateway startup sidecar", () => { + const rootDir = makeTempDir(); + writeRuntimeEntry(rootDir); + writePluginManifest(rootDir, { + id: "explicit-startup-provider", + providers: ["demo"], + activation: { + onStartup: true, + }, + configSchema: { type: "object" }, + }); + + const index = loadInstalledPluginIndex({ + candidates: [ + createPluginCandidate({ + rootDir, + }), + ], + env: hermeticEnv(), + }); + + expect(index.plugins[0]).toMatchObject({ + pluginId: "explicit-startup-provider", + startup: { + sidecar: true, + }, + }); + }); + it("keeps bundle format metadata needed for manifest reconstruction", () => { const rootDir = makeTempDir(); fs.mkdirSync(path.join(rootDir, ".claude-plugin"), { recursive: true });