Skip to content

Commit 7ac23ee

Browse files
committed
refactor: drop legacy implicit startup sidecar fallback
1 parent 5e3265b commit 7ac23ee

16 files changed

Lines changed: 60 additions & 261 deletions

docs/plugins/architecture-internals.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ to narrow plugin loading before broader registry materialization:
6161
- explicit provider setup/runtime resolution narrows to plugins that own the
6262
requested provider id
6363
- Gateway startup planning uses `activation.onStartup` for explicit startup
64-
imports and startup opt-outs; every plugin should declare it as OpenClaw
65-
moves away from implicit startup imports, while plugins without static
66-
capability metadata and without `activation.onStartup` still use the
67-
deprecated implicit startup sidecar fallback for compatibility
64+
imports and startup opt-outs; plugins without startup metadata load only
65+
through narrower activation triggers
6866

6967
The activation planner exposes both an ids-only API for existing callers and a
7068
plan API for new diagnostics. Plan entries report why a plugin was selected,

docs/plugins/compatibility.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,6 @@ Current compatibility records include:
135135
- legacy channel route key and comparable-target helper aliases while plugins
136136
move to `openclaw/plugin-sdk/channel-route`
137137
- activation hints that are being replaced by manifest contribution ownership
138-
- deprecated implicit startup sidecar loading for plugins that have not declared
139-
`activation.onStartup`; maintainers can test the future stricter behavior with
140-
`OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS=1`
141138
- `setup-api` runtime fallback while setup descriptors move to cold
142139
`setup.requiresRuntime: false` metadata
143140
- provider `discovery` hooks while provider catalog hooks move to

docs/plugins/manifest.md

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -255,24 +255,15 @@ embedded agent harness ids that do not already have an ownership field.
255255
This block is metadata only. It does not register runtime behavior, and it does
256256
not replace `register(...)`, `setupEntry`, or other runtime/plugin entrypoints.
257257
Current consumers use it as a narrowing hint before broader plugin loading, so
258-
missing activation metadata usually only costs performance; it should not
259-
change correctness while legacy manifest ownership fallbacks still exist.
260-
261-
Every plugin should set `activation.onStartup` intentionally as OpenClaw moves
262-
away from implicit startup imports. Set it to `true` only when the plugin must
263-
run during Gateway startup. Set it to `false` when the plugin is inert at
264-
startup and should load only from narrower triggers. Omitting `onStartup` keeps
265-
the deprecated legacy implicit startup sidecar fallback for plugins with no
266-
static capability metadata; future versions may stop startup-loading those
267-
plugins unless they declare `activation.onStartup: true`. Plugin status and
268-
compatibility reports warn with `legacy-implicit-startup-sidecar` when a plugin
269-
still relies on that fallback.
270-
271-
For migration testing, set
272-
`OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS=1` to disable only that
273-
deprecated fallback. This opt-in mode does not block explicit
274-
`activation.onStartup: true` plugins or plugins loaded by channel, config,
275-
agent-harness, memory, or other narrower activation triggers.
258+
missing non-startup activation metadata usually only costs performance; it
259+
should not change correctness while manifest ownership fallbacks still exist.
260+
261+
Every plugin should set `activation.onStartup` intentionally. Set it to `true`
262+
only when the plugin must run during Gateway startup. Set it to `false` when
263+
the plugin is inert at startup and should load only from narrower triggers.
264+
Omitting `onStartup` no longer startup-loads the plugin implicitly; use explicit
265+
activation metadata for startup, channel, config, agent-harness, memory, or
266+
other narrower activation triggers.
276267

277268
```json
278269
{
@@ -288,21 +279,21 @@ agent-harness, memory, or other narrower activation triggers.
288279
}
289280
```
290281

291-
| Field | Required | Type | What it means |
292-
| ------------------ | -------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
293-
| `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. |
294-
| `onProviders` | No | `string[]` | Provider ids that should include this plugin in activation/load plans. |
295-
| `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. |
296-
| `onCommands` | No | `string[]` | Command ids that should include this plugin in activation/load plans. |
297-
| `onChannels` | No | `string[]` | Channel ids that should include this plugin in activation/load plans. |
298-
| `onRoutes` | No | `string[]` | Route kinds that should include this plugin in activation/load plans. |
299-
| `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. |
300-
| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. Prefer narrower fields when possible. |
282+
| Field | Required | Type | What it means |
283+
| ------------------ | -------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
284+
| `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. |
285+
| `onProviders` | No | `string[]` | Provider ids that should include this plugin in activation/load plans. |
286+
| `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. |
287+
| `onCommands` | No | `string[]` | Command ids that should include this plugin in activation/load plans. |
288+
| `onChannels` | No | `string[]` | Channel ids that should include this plugin in activation/load plans. |
289+
| `onRoutes` | No | `string[]` | Route kinds that should include this plugin in activation/load plans. |
290+
| `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. |
291+
| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. Prefer narrower fields when possible. |
301292

302293
Current live consumers:
303294

304295
- Gateway startup planning uses `activation.onStartup` for explicit startup
305-
import and opt-out of deprecated implicit sidecar startup fallback
296+
import
306297
- command-triggered CLI planning falls back to legacy
307298
`commandAliases[].cliCommand` or `commandAliases[].name`
308299
- agent-runtime startup planning uses `activation.onAgentHarnesses` for

scripts/bench-gateway-startup.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,6 @@ const GATEWAY_CASES: readonly GatewayBenchCase[] = [
137137
pluginCount: 50,
138138
config: BASE_CONFIG,
139139
},
140-
{
141-
id: "fiftyPluginsFutureStrict",
142-
name: "gateway, 50 manifest plugins with legacy startup fallback disabled",
143-
env: {
144-
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
145-
OPENCLAW_SKIP_CHANNELS: "1",
146-
},
147-
pluginCount: 50,
148-
config: BASE_CONFIG,
149-
},
150140
{
151141
id: "fiftyStartupLazyPlugins",
152142
name: "gateway, 50 startup-lazy manifest plugins",

src/gateway/gateway.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ async function writeWorkspacePlugin(params: {
5252
workspaceDir: string;
5353
id: string;
5454
body: string;
55+
activation?: { onStartup?: boolean };
5556
}): Promise<void> {
5657
const pluginDir = path.join(params.workspaceDir, ".openclaw", "extensions", params.id);
5758
await fs.mkdir(pluginDir, { recursive: true });
@@ -60,6 +61,7 @@ async function writeWorkspacePlugin(params: {
6061
`${JSON.stringify(
6162
{
6263
id: params.id,
64+
...(params.activation ? { activation: params.activation } : {}),
6365
configSchema: { type: "object", additionalProperties: false, properties: {} },
6466
},
6567
null,
@@ -233,6 +235,7 @@ describe("gateway e2e", () => {
233235
await writeWorkspacePlugin({
234236
workspaceDir,
235237
id: "http-probe",
238+
activation: { onStartup: true },
236239
body: `
237240
const fs = require("node:fs");
238241
const counterPath = ${JSON.stringify(registerCountPath)};

src/plugins/channel-plugin-ids.test.ts

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
104104
id: "browser",
105105
channels: [],
106106
activation: {
107+
onStartup: true,
107108
onConfigPaths: ["browser"],
108109
},
109110
origin: "bundled",
@@ -212,6 +213,9 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
212213
{
213214
id: "voice-call",
214215
channels: [],
216+
activation: {
217+
onStartup: true,
218+
},
215219
origin: "bundled",
216220
enabledByDefault: undefined,
217221
providers: [],
@@ -238,6 +242,9 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
238242
{
239243
id: "demo-global-sidecar",
240244
channels: [],
245+
activation: {
246+
onStartup: true,
247+
},
241248
origin: "global",
242249
enabledByDefault: undefined,
243250
providers: [],
@@ -295,10 +302,6 @@ function normalizeStartupAgentHarnesses(record: PluginManifestRecord): readonly
295302
].toSorted((left, right) => left.localeCompare(right));
296303
}
297304

298-
function hasRuntimeContractSurface(record: PluginManifestRecord): boolean {
299-
return record.providers.length > 0 || record.cliBackends.length > 0;
300-
}
301-
302305
function hasPluginKind(record: PluginManifestRecord, kind: string): boolean {
303306
return Array.isArray(record.kind) ? record.kind.includes(kind as never) : record.kind === kind;
304307
}
@@ -317,12 +320,7 @@ function createInstalledPluginRecordFixture(
317320
enabled: true,
318321
...(record.enabledByDefault === true ? { enabledByDefault: true } : {}),
319322
startup: {
320-
sidecar:
321-
record.activation?.onStartup === true ||
322-
(record.activation?.onStartup === undefined &&
323-
record.channels.length === 0 &&
324-
!hasRuntimeContractSurface(record) &&
325-
!memory),
323+
sidecar: record.activation?.onStartup === true,
326324
memory,
327325
deferConfiguredChannelFullLoadUntilAfterListen:
328326
record.startupDeferConfiguredChannelFullLoadUntilAfterListen === true,
@@ -654,35 +652,7 @@ describe("resolveGatewayStartupPluginIds", () => {
654652
});
655653
});
656654

657-
it("keeps deprecated implicit startup sidecar fallback for legacy plugins", () => {
658-
expectStartupPluginIdsCase({
659-
config: createStartupConfig({
660-
enabledPluginIds: ["demo-global-sidecar"],
661-
allowPluginIds: ["demo-global-sidecar"],
662-
noConfiguredChannels: true,
663-
memorySlot: "none",
664-
}),
665-
expected: ["demo-global-sidecar"],
666-
});
667-
});
668-
669-
it("can disable deprecated implicit startup sidecar fallback for future-mode testing", () => {
670-
expectStartupPluginIdsCase({
671-
config: createStartupConfig({
672-
enabledPluginIds: ["demo-global-sidecar"],
673-
allowPluginIds: ["demo-global-sidecar"],
674-
noConfiguredChannels: true,
675-
memorySlot: "none",
676-
}),
677-
env: {
678-
...process.env,
679-
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
680-
},
681-
expected: [],
682-
});
683-
});
684-
685-
it("skips deprecated implicit startup sidecar fallback when activation.onStartup is false", () => {
655+
it("skips startup when activation.onStartup is false", () => {
686656
expectStartupPluginIdsCase({
687657
config: createStartupConfig({
688658
enabledPluginIds: ["demo-global-startup-opt-out"],
@@ -702,10 +672,6 @@ describe("resolveGatewayStartupPluginIds", () => {
702672
noConfiguredChannels: true,
703673
memorySlot: "none",
704674
}),
705-
env: {
706-
...process.env,
707-
OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS: "1",
708-
},
709675
expected: ["demo-global-explicit-startup"],
710676
});
711677
});

src/plugins/compat/registry.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,21 +277,6 @@ export const PLUGIN_COMPAT_RECORDS = [
277277
diagnostics: ["activation plan compat reason"],
278278
tests: ["src/plugins/activation-planner.test.ts"],
279279
},
280-
{
281-
code: "legacy-implicit-startup-sidecar",
282-
status: "deprecated",
283-
owner: "plugin-execution",
284-
introduced: "2026-04-28",
285-
deprecated: "2026-04-28",
286-
warningStarts: "2026-04-28",
287-
removeAfter: "2026-07-28",
288-
replacement:
289-
"`activation.onStartup: true` for startup work or `activation.onStartup: false` for inert plugins",
290-
docsPath: "/plugins/manifest",
291-
surfaces: ["Gateway startup plugin planning", "openclaw.plugin.json activation"],
292-
diagnostics: ["plugin compatibility notice"],
293-
tests: ["src/plugins/channel-plugin-ids.test.ts", "src/plugins/installed-plugin-index.test.ts"],
294-
},
295280
{
296281
code: "activation-provider-hint",
297282
status: "active",

src/plugins/gateway-startup-plugin-ids.ts

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,6 @@ import {
2323
} from "./plugin-registry-contributions.js";
2424
import { loadPluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
2525

26-
const DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS_ENV =
27-
"OPENCLAW_DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS";
28-
29-
function isTruthyEnvValue(value: string | undefined): boolean {
30-
const normalized = value?.trim().toLowerCase();
31-
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
32-
}
33-
34-
function shouldDisableLegacyImplicitStartupSidecars(env: NodeJS.ProcessEnv): boolean {
35-
return isTruthyEnvValue(env[DISABLE_LEGACY_IMPLICIT_STARTUP_SIDECARS_ENV]);
36-
}
37-
3826
function isRecord(value: unknown): value is Record<string, unknown> {
3927
return Boolean(value && typeof value === "object" && !Array.isArray(value));
4028
}
@@ -60,18 +48,6 @@ function isGatewayStartupMemoryPlugin(plugin: InstalledPluginIndexRecord): boole
6048
return plugin.startup.memory;
6149
}
6250

63-
/**
64-
* @deprecated Compatibility fallback for plugins that do not declare
65-
* `activation.onStartup`. Keep this path visible so we can remove it after
66-
* plugin manifests migrate to explicit startup activation.
67-
*/
68-
function isDeprecatedLegacyImplicitStartupSidecar(params: {
69-
plugin: InstalledPluginIndexRecord;
70-
manifest: PluginManifestRecord | undefined;
71-
}): boolean {
72-
return params.plugin.startup.sidecar && params.manifest?.activation?.onStartup === undefined;
73-
}
74-
7551
function resolveGatewayStartupDreamingPluginIds(config: OpenClawConfig): Set<string> {
7652
const dreamingConfig = resolveMemoryDreamingConfig({
7753
pluginConfig: resolveMemoryDreamingPluginConfig(config),
@@ -112,29 +88,12 @@ function resolveMemorySlotStartupPluginId(params: {
11288
function shouldConsiderForGatewayStartup(params: {
11389
plugin: InstalledPluginIndexRecord;
11490
manifest: PluginManifestRecord | undefined;
115-
disableLegacyImplicitStartupSidecars: boolean;
11691
startupDreamingPluginIds: ReadonlySet<string>;
11792
memorySlotStartupPluginId?: string;
11893
}): boolean {
11994
if (params.manifest?.activation?.onStartup === true) {
12095
return true;
12196
}
122-
if (params.plugin.startup.sidecar) {
123-
if (params.manifest?.activation?.onStartup === false) {
124-
return false;
125-
}
126-
if (params.disableLegacyImplicitStartupSidecars) {
127-
return false;
128-
}
129-
// Deprecated compatibility fallback: plugins without explicit startup
130-
// activation metadata may still need startup import to register hooks or
131-
// services. All plugins should declare activation.onStartup explicitly as
132-
// we migrate away from implicit startup sidecar loading.
133-
return isDeprecatedLegacyImplicitStartupSidecar({
134-
plugin: params.plugin,
135-
manifest: params.manifest,
136-
});
137-
}
13897
if (!isGatewayStartupMemoryPlugin(params.plugin)) {
13998
return false;
14099
}
@@ -391,9 +350,6 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
391350
collectConfiguredAgentHarnessRuntimes(activationSourceConfig, params.env),
392351
);
393352
const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config);
394-
const disableLegacyImplicitStartupSidecars = shouldDisableLegacyImplicitStartupSidecars(
395-
params.env,
396-
);
397353
const manifestLookup = createManifestRegistryLookup(params.manifestRegistry);
398354
const memorySlotStartupPluginId = resolveMemorySlotStartupPluginId({
399355
activationSourceConfig,
@@ -448,7 +404,6 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
448404
!shouldConsiderForGatewayStartup({
449405
plugin,
450406
manifest,
451-
disableLegacyImplicitStartupSidecars,
452407
startupDreamingPluginIds,
453408
memorySlotStartupPluginId,
454409
})

src/plugins/install.path.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ describe("installPluginFromPath", () => {
338338
expect(fs.existsSync(path.join(result.targetDir, ".claude-plugin", "plugin.json"))).toBe(true);
339339
});
340340

341-
it("prefers native package metadata for dual-format archives", async () => {
341+
it("prefers native package metadata without installing dependencies for dual-format archives", async () => {
342342
const { pluginDir, extensionsDir } = setupDualFormatInstallFixture({
343343
bundleFormat: "claude",
344344
});
@@ -371,5 +371,6 @@ describe("installPluginFromPath", () => {
371371
expect(result.pluginId).toBe("native-dual");
372372
expect(result.targetDir).toBe(path.join(extensionsDir, "native-dual"));
373373
expect(run).not.toHaveBeenCalled();
374+
expect(fs.existsSync(path.join(result.targetDir, "node_modules"))).toBe(false);
374375
});
375376
});

0 commit comments

Comments
 (0)