fix(plugins): isolate definition metadata failures

This commit is contained in:
Vincent Koc
2026-06-04 07:33:44 +02:00
parent 5b98f03c64
commit 88c568462e
2 changed files with 81 additions and 8 deletions

View File

@@ -2789,6 +2789,61 @@ module.exports = { id: "throws-after-import", register() {} };`,
clearInternalHooks();
});
it("isolates unreadable definition reload metadata without dropping healthy plugins", () => {
useNoBundledPlugins();
const badPlugin = writePlugin({
id: "bad-definition-reload",
filename: "bad-definition-reload.cjs",
body: `module.exports = {
id: "bad-definition-reload",
reload: Object.defineProperty({}, "restartPrefixes", {
get() {
throw new Error("definition reload restart prefixes exploded");
},
}),
register() {
globalThis.badDefinitionReloadRegistered = true;
},
};`,
});
const healthyPlugin = writePlugin({
id: "healthy-after-bad-reload",
filename: "healthy-after-bad-reload.cjs",
body: `module.exports = {
id: "healthy-after-bad-reload",
register() {
globalThis.healthyAfterBadReloadRegistered = true;
},
};`,
});
const globals = globalThis as Record<string, unknown>;
delete globals.badDefinitionReloadRegistered;
delete globals.healthyAfterBadReloadRegistered;
const registry = loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [badPlugin.file, healthyPlugin.file] },
allow: [badPlugin.id, healthyPlugin.id],
},
},
onlyPluginIds: [badPlugin.id, healthyPlugin.id],
});
expect(registry.plugins.find((entry) => entry.id === badPlugin.id)?.status).toBe("error");
expect(registry.plugins.find((entry) => entry.id === healthyPlugin.id)?.status).toBe("loaded");
expect(globals.badDefinitionReloadRegistered).toBeUndefined();
expect(globals.healthyAfterBadReloadRegistered).toBe(true);
expect(registry.reloads).toStrictEqual([]);
expectDiagnosticContaining({
registry,
level: "error",
pluginId: badPlugin.id,
message: "definition reload restart prefixes exploded",
});
});
it("rolls back global side effects when registration fails", async () => {
useNoBundledPlugins();
const plugin = writePlugin({

View File

@@ -2642,14 +2642,32 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
}
if (registrationPlan.runFullActivationOnlyRegistrations) {
if (definition?.reload) {
registerReload(record, definition.reload);
}
for (const nodeHostCommand of definition?.nodeHostCommands ?? []) {
registerNodeHostCommand(record, nodeHostCommand);
}
for (const collector of definition?.securityAuditCollectors ?? []) {
registerSecurityAuditCollector(record, collector);
const registrySnapshot = snapshotPluginRegistry(registry);
try {
if (definition?.reload) {
registerReload(record, definition.reload);
}
for (const nodeHostCommand of definition?.nodeHostCommands ?? []) {
registerNodeHostCommand(record, nodeHostCommand);
}
for (const collector of definition?.securityAuditCollectors ?? []) {
registerSecurityAuditCollector(record, collector);
}
} catch (err) {
restorePluginRegistry(registry, registrySnapshot);
recordPluginError({
logger,
registry,
record,
seenIds,
pluginId,
origin: candidate.origin,
phase: "load",
error: err,
logPrefix: `[plugins] ${record.id} failed to register plugin definition metadata from ${record.source}: `,
diagnosticMessagePrefix: "failed to register plugin definition metadata: ",
});
continue;
}
}