import fs from "node:fs"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { describeRootFileOpenFailure, openRootFileSync } from "../infra/boundary-file-read.js"; import type { NormalizedPluginsConfig } from "./config-state.js"; import { channelPluginIdBelongsToManifest, loadBundledRuntimeChannelPlugin, mergeSetupRuntimeChannelPlugin, resolveBundledRuntimeChannelRegistration, resolveSetupChannelRegistration, } from "./loader-channel-setup.js"; import type { PluginModuleLoader } from "./loader-module-runtime.js"; import { runPluginRegisterSyncInRegistry } from "./loader-module-runtime.js"; import { recordPluginError } from "./loader-records.js"; import type { PluginRegistrationPlan } from "./loader-registration-plan.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; import { withProfile } from "./plugin-load-profile.js"; import { resolveCanonicalDistRuntimeSource } from "./plugin-runtime-artifact-resolution.js"; import type { createPluginRegistry, PluginRecord } from "./registry.js"; import type { OpenClawPluginModule, PluginLogger } from "./types.js"; type PluginRegistryBuilder = ReturnType; /** * Handles the setup-entry channel path. * Returns true when the candidate is complete (loaded, disabled, or failed). */ export function loadSetupRuntimeChannelCandidate(params: { mod: OpenClawPluginModule | null; manifestRecord: PluginManifestRecord; record: PluginRecord; registrationPlan: PluginRegistrationPlan; runtimeCandidateEntry: { source: string; rootDir: string }; safeSource: string; rejectHardlinks: boolean; loadPluginModule: PluginModuleLoader; registryBuilder: PluginRegistryBuilder; cfg: OpenClawConfig; entry: NormalizedPluginsConfig["entries"][string] | undefined; seenIds: Map; candidateOrigin: PluginRecord["origin"]; logger: PluginLogger; pushPluginLoadError: (message: string) => void; }): boolean { const { manifestRecord, record, registrationPlan, runtimeCandidateEntry, registryBuilder } = params; if (!registrationPlan.loadSetupEntry || !manifestRecord.setupSource) { return false; } const setupRegistration = resolveSetupChannelRegistration(params.mod); if (setupRegistration.loadError) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error: setupRegistration.loadError, logPrefix: `[plugins] ${record.id} failed to load setup entry from ${record.source}: `, diagnosticMessagePrefix: "failed to load setup entry: ", diagnosticCode: "channel-setup-failure", }); return true; } if (!setupRegistration.plugin) { return false; } if ( !channelPluginIdBelongsToManifest({ channelId: setupRegistration.plugin.id, pluginId: record.id, manifestChannels: manifestRecord.channels, }) ) { params.pushPluginLoadError( `plugin id mismatch (config uses "${record.id}", setup export uses "${setupRegistration.plugin.id}")`, ); return true; } const api = registryBuilder.createApi(record, { config: params.cfg, pluginConfig: {}, hookPolicy: params.entry?.hooks, registrationMode: registrationPlan.mode, }); let mergedSetupRegistration = setupRegistration; let runtimeSetterApplied = false; if ( registrationPlan.loadSetupRuntimeEntry && setupRegistration.usesBundledSetupContract && resolveCanonicalDistRuntimeSource(runtimeCandidateEntry.source) !== params.safeSource ) { const runtimeModuleSource = resolveCanonicalDistRuntimeSource(runtimeCandidateEntry.source); const runtimeModuleRoot = resolveCanonicalDistRuntimeSource(runtimeCandidateEntry.rootDir); const runtimeOpened = openRootFileSync({ absolutePath: runtimeModuleSource, rootPath: runtimeModuleRoot, boundaryLabel: "plugin root", rejectHardlinks: params.rejectHardlinks, skipLexicalRootCheck: true, }); if (!runtimeOpened.ok) { params.pushPluginLoadError( describeRootFileOpenFailure({ failure: runtimeOpened, subject: "plugin entry path", boundaryLabel: "plugin root", filePath: runtimeModuleSource, }), ); return true; } const safeRuntimeSource = runtimeOpened.path; fs.closeSync(runtimeOpened.fd); let runtimeMod: OpenClawPluginModule | null; try { runtimeMod = withProfile( { pluginId: record.id, source: safeRuntimeSource }, "load-setup-runtime-entry", () => params.loadPluginModule(safeRuntimeSource) as OpenClawPluginModule, ); } catch (error) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error, logPrefix: `[plugins] ${record.id} failed to load setup-runtime entry from ${record.source}: `, diagnosticMessagePrefix: "failed to load setup-runtime entry: ", diagnosticCode: "channel-setup-failure", }); return true; } const runtimeRegistration = resolveBundledRuntimeChannelRegistration(runtimeMod); if (runtimeRegistration.id && runtimeRegistration.id !== record.id) { params.pushPluginLoadError( `plugin id mismatch (config uses "${record.id}", runtime entry uses "${runtimeRegistration.id}")`, ); return true; } if (runtimeRegistration.setChannelRuntime) { try { runtimeRegistration.setChannelRuntime(api.runtime); runtimeSetterApplied = true; } catch (error) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error, logPrefix: `[plugins] ${record.id} failed to apply setup-runtime channel runtime from ${record.source}: `, diagnosticMessagePrefix: "failed to apply setup-runtime channel runtime: ", diagnosticCode: "channel-setup-failure", }); return true; } } const runtimePluginRegistration = loadBundledRuntimeChannelPlugin({ registration: runtimeRegistration, }); if (runtimePluginRegistration.loadError) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error: runtimePluginRegistration.loadError, logPrefix: `[plugins] ${record.id} failed to load setup-runtime channel entry from ${record.source}: `, diagnosticMessagePrefix: "failed to load setup-runtime channel entry: ", diagnosticCode: "channel-setup-failure", }); return true; } if (runtimePluginRegistration.plugin) { if ( runtimePluginRegistration.plugin.id && runtimePluginRegistration.plugin.id !== record.id ) { params.pushPluginLoadError( `plugin id mismatch (config uses "${record.id}", runtime export uses "${runtimePluginRegistration.plugin.id}")`, ); return true; } mergedSetupRegistration = { ...setupRegistration, plugin: mergeSetupRuntimeChannelPlugin( runtimePluginRegistration.plugin, setupRegistration.plugin, ), setChannelRuntime: runtimeRegistration.setChannelRuntime ?? setupRegistration.setChannelRuntime, }; } } const mergedSetupPlugin = mergedSetupRegistration.plugin; if (!mergedSetupPlugin) { return true; } if ( !channelPluginIdBelongsToManifest({ channelId: mergedSetupPlugin.id, pluginId: record.id, manifestChannels: manifestRecord.channels, }) ) { params.pushPluginLoadError( `plugin id mismatch (config uses "${record.id}", setup export uses "${mergedSetupPlugin.id}")`, ); return true; } if (!runtimeSetterApplied) { try { mergedSetupRegistration.setChannelRuntime?.(api.runtime); } catch (error) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error, logPrefix: `[plugins] ${record.id} failed to apply setup channel runtime from ${record.source}: `, diagnosticMessagePrefix: "failed to apply setup channel runtime: ", diagnosticCode: "channel-setup-failure", }); return true; } } if (registrationPlan.mode === "setup-runtime" && mergedSetupRegistration.registerSetupRuntime) { try { runPluginRegisterSyncInRegistry( (registrationApi) => mergedSetupRegistration.registerSetupRuntime?.(registrationApi), api, registryBuilder.registry, record.id, ); } catch (error) { registryBuilder.rollbackPluginGlobalSideEffects(record.id, record); recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "register", error, logPrefix: `[plugins] ${record.id} failed to register setup-runtime channel side effects from ${record.source}: `, diagnosticMessagePrefix: "failed to register setup-runtime channel side effects: ", diagnosticCode: "channel-setup-failure", }); return true; } } try { api.registerChannel(mergedSetupPlugin); } catch (error) { recordPluginError({ logger: params.logger, registry: registryBuilder.registry, record, seenIds: params.seenIds, pluginId: record.id, origin: params.candidateOrigin, phase: "load", error, logPrefix: `[plugins] ${record.id} failed to register setup channel from ${record.source}: `, diagnosticMessagePrefix: "failed to register setup channel: ", diagnosticCode: "channel-setup-failure", }); return true; } registryBuilder.registry.plugins.push(record); params.seenIds.set(record.id, params.candidateOrigin); return true; }