diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index c2178e54523..0cfd4c438b1 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -3590,7 +3590,8 @@ module.exports = { ); }); - it("rejects mismatched bundled runtime plugin ids during setup-runtime merge", () => { + it("rejects mismatched bundled runtime entry ids before applying setup-runtime setters", () => { + const runtimeMarker = path.join(makeTempDir(), "setup-runtime-mismatch.txt"); const built = createSetupEntryChannelPluginFixture({ id: "setup-runtime-mismatch-test", bundledFullEntryId: "wrong-runtime-id", @@ -3601,7 +3602,7 @@ module.exports = { configured: false, useBundledFullEntryContract: true, useBundledSetupEntryContract: true, - bundledFullRuntimeMarker: path.join(makeTempDir(), "setup-runtime-mismatch.txt"), + bundledFullRuntimeMarker: runtimeMarker, }); const registry = loadOpenClawPlugins({ @@ -3619,8 +3620,9 @@ module.exports = { ).toBe("error"); expect( registry.plugins.find((entry) => entry.id === "setup-runtime-mismatch-test")?.error, - ).toContain('runtime export uses "wrong-runtime-id"'); + ).toContain('runtime entry uses "wrong-runtime-id"'); expect(registry.channels).toHaveLength(0); + expect(fs.existsSync(runtimeMarker)).toBe(false); }); it("isolates loadSetupPlugin errors as per-plugin diagnostics instead of crashing registry load", () => { diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 70a990c8635..a0ad93e5bc9 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -684,6 +684,7 @@ function mergeSetupRuntimeChannelPlugin( } function resolveBundledRuntimeChannelRegistration(moduleExport: unknown): { + id?: string; loadChannelPlugin?: () => ChannelPlugin; loadChannelSecrets?: () => ChannelPlugin["secrets"] | undefined; setChannelRuntime?: (runtime: PluginRuntime) => void; @@ -694,17 +695,20 @@ function resolveBundledRuntimeChannelRegistration(moduleExport: unknown): { } const entryRecord = resolved as { kind?: unknown; + id?: unknown; loadChannelPlugin?: unknown; loadChannelSecrets?: unknown; setChannelRuntime?: unknown; }; if ( entryRecord.kind !== "bundled-channel-entry" || + typeof entryRecord.id !== "string" || typeof entryRecord.loadChannelPlugin !== "function" ) { return {}; } return { + id: entryRecord.id, loadChannelPlugin: entryRecord.loadChannelPlugin as () => ChannelPlugin, ...(typeof entryRecord.loadChannelSecrets === "function" ? { @@ -1850,6 +1854,12 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi continue; } const runtimeRegistration = resolveBundledRuntimeChannelRegistration(runtimeMod); + if (runtimeRegistration.id && runtimeRegistration.id !== record.id) { + pushPluginLoadError( + `plugin id mismatch (config uses "${record.id}", runtime entry uses "${runtimeRegistration.id}")`, + ); + continue; + } if (runtimeRegistration.setChannelRuntime) { try { runtimeRegistration.setChannelRuntime(api.runtime);