mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(plugins): isolate definition metadata failures
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user