refactor: mark implicit startup sidecars deprecated

This commit is contained in:
Shakker
2026-04-28 02:55:45 +01:00
parent b16fe2b229
commit 5d52233c25
3 changed files with 116 additions and 2 deletions

View File

@@ -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",

View File

@@ -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");
}

View File

@@ -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 });