mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:30:42 +00:00
refactor: drop legacy implicit startup sidecar fallback
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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"],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user