fix(plugins): support bundled setup-entry contract in loader (#66261)

Merged via squash.

Prepared head SHA: 0a4201115c
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
This commit is contained in:
Mason Huang
2026-04-14 22:51:22 +08:00
committed by GitHub
parent 8f0628d43b
commit 1558a352f8
3 changed files with 258 additions and 8 deletions

View File

@@ -644,13 +644,65 @@ function resolvePluginModuleExport(moduleExport: unknown): {
return {};
}
function mergeSetupPluginSection<T>(
baseValue: T | undefined,
setupValue: T | undefined,
): T | undefined {
if (baseValue && setupValue && typeof baseValue === "object" && typeof setupValue === "object") {
const merged = {
...(baseValue as Record<string, unknown>),
};
for (const [key, value] of Object.entries(setupValue as Record<string, unknown>)) {
if (value !== undefined) {
merged[key] = value;
}
}
return {
...merged,
} as T;
}
return setupValue ?? baseValue;
}
function resolveSetupChannelRegistration(moduleExport: unknown): {
plugin?: ChannelPlugin;
loadError?: unknown;
} {
const resolved = unwrapDefaultModuleExport(moduleExport);
if (!resolved || typeof resolved !== "object") {
return {};
}
const setupEntryRecord = resolved as {
kind?: unknown;
loadSetupPlugin?: unknown;
loadSetupSecrets?: unknown;
};
if (
setupEntryRecord.kind === "bundled-channel-setup-entry" &&
typeof setupEntryRecord.loadSetupPlugin === "function"
) {
try {
const loadedPlugin = setupEntryRecord.loadSetupPlugin();
const loadedSecrets =
typeof setupEntryRecord.loadSetupSecrets === "function"
? (setupEntryRecord.loadSetupSecrets() as ChannelPlugin["secrets"] | undefined)
: undefined;
if (loadedPlugin && typeof loadedPlugin === "object") {
const mergedSecrets = mergeSetupPluginSection(
(loadedPlugin as ChannelPlugin).secrets,
loadedSecrets,
);
return {
plugin: {
...(loadedPlugin as ChannelPlugin),
...(mergedSecrets !== undefined ? { secrets: mergedSecrets } : {}),
},
};
}
} catch (err) {
return { loadError: err };
}
}
const setup = resolved as {
plugin?: unknown;
};
@@ -1635,6 +1687,21 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
manifestRecord.setupSource
) {
const setupRegistration = resolveSetupChannelRegistration(mod);
if (setupRegistration.loadError) {
recordPluginError({
logger,
registry,
record,
seenIds,
pluginId,
origin: candidate.origin,
phase: "load",
error: setupRegistration.loadError,
logPrefix: `[plugins] ${record.id} failed to load setup entry from ${record.source}: `,
diagnosticMessagePrefix: "failed to load setup entry: ",
});
continue;
}
if (setupRegistration.plugin) {
if (setupRegistration.plugin.id && setupRegistration.plugin.id !== record.id) {
pushPluginLoadError(