UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

704 lines (703 loc) 31.9 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { t as sanitizeForLog } from "./ansi-BI9w76Cm.js"; import { _ as uniqueStrings, a as normalizeOptionalTrimmedStringList } from "./string-normalization-WNUDCpXX.js"; import { f as safeRealpathSync, i as isPathInside, p as safeStatSync } from "./path-BlG8lhgR.js"; import { s as shouldRejectHardlinkedPluginFiles, t as discoverOpenClawPlugins } from "./discovery-BDSfxwT6.js"; import { p as resolveUserPath } from "./utils-CCC-BEJH.js"; import { o as resolveCompatibilityHostVersion } from "./version-Crcn9X9T.js"; import { t as isBlockedObjectKey } from "./prototype-keys-D2nJOZIy.js"; import { x as satisfiesPluginApiRange } from "./clawhub-CzBrlwb2.js"; import { i as loadPluginManifest } from "./manifest-BcY4wyAU.js"; import { a as loadBundleManifest } from "./bundle-manifest-DN_uOlFE.js"; import { c as resolvePluginActivationDecisionShared, i as normalizePluginsConfigWithResolver$1, l as toPluginActivationState, n as identityNormalizePluginId, r as isBundledChannelEnabledByChannelConfig$1, s as resolveMemorySlotDecisionShared, t as hasExplicitPluginConfig$1 } from "./config-normalization-shared-DReIawrL.js"; import "./path-safety-4zNHq1Ot.js"; import { t as isBundledPluginInsideDevSourceRoot } from "./dev-source-root-BS5CseCe.js"; import { i as resolvePackagePluginApiRange } from "./package-entry-resolution-XklWAQ1C.js"; import { n as loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader-CPHayOZI.js"; import { t as checkMinHostVersion } from "./min-host-version-jLSVTA8X.js"; import { c as resolveOfficialExternalPluginInstall, n as getOfficialExternalPluginCatalogEntryForPackage, r as getOfficialExternalPluginCatalogManifest, s as resolveOfficialExternalPluginId } from "./official-external-plugin-catalog-h7dZZiYs.js"; import fs from "node:fs"; import path from "node:path"; //#region src/plugins/config-policy.ts function normalizePluginsConfigWithResolver(config, normalizePluginId = identityNormalizePluginId) { return normalizePluginsConfigWithResolver$1(config, normalizePluginId); } function resolvePluginActivationState(params) { return toPluginActivationState(resolvePluginActivationDecisionShared({ ...params, activationSource: { plugins: params.sourceConfig ?? params.config, rootConfig: params.sourceRootConfig ?? params.rootConfig }, isBundledChannelEnabledByChannelConfig })); } const hasExplicitPluginConfig = hasExplicitPluginConfig$1; const isBundledChannelEnabledByChannelConfig = isBundledChannelEnabledByChannelConfig$1; function resolveEffectivePluginActivationState(params) { return resolvePluginActivationState(params); } function resolveMemorySlotDecision(params) { return resolveMemorySlotDecisionShared(params); } //#endregion //#region src/plugins/manifest-registry.ts function resolvePluginSourcePath(sourcePath) { if (fs.existsSync(sourcePath)) return sourcePath; if (sourcePath.endsWith(".ts")) { const jsPath = sourcePath.slice(0, -3) + ".js"; if (fs.existsSync(jsPath)) return jsPath; } return sourcePath; } function isPluginRootPath(params) { const resolvedTargetPath = path.resolve(params.targetPath); if (!isPathInside(path.resolve(params.rootPath), resolvedTargetPath)) return false; const targetRealPath = safeRealpathSync(resolvedTargetPath); if (!targetRealPath) return params.targetMustExist !== true; if (!isPathInside(params.rootRealPath, targetRealPath)) return false; if (params.rejectHardlinks === true) { const targetStat = safeStatSync(resolvedTargetPath); if (!targetStat || targetStat.nlink > 1) return false; } return true; } function resolveManifestPluginSourcePath(params) { const pushDiagnostic = () => { params.diagnostics.push({ level: "warn", pluginId: sanitizeForLog(params.pluginId), source: sanitizeForLog(params.manifestPath), message: `plugin manifest ${params.entryName} must resolve inside the plugin root; ignoring entry` }); }; if (path.isAbsolute(params.entry)) { pushDiagnostic(); return; } const rootPath = path.resolve(params.rootDir); const rootRealPath = safeRealpathSync(rootPath) ?? rootPath; const sourcePath = path.resolve(rootPath, params.entry); if (!isPluginRootPath({ rootPath, targetPath: sourcePath, rootRealPath, rejectHardlinks: params.rejectHardlinks, targetMustExist: fs.existsSync(sourcePath) })) { pushDiagnostic(); return; } const resolvedSourcePath = resolvePluginSourcePath(sourcePath); if (!isPluginRootPath({ rootPath, targetPath: resolvedSourcePath, rootRealPath, rejectHardlinks: params.rejectHardlinks, targetMustExist: fs.existsSync(resolvedSourcePath) })) { pushDiagnostic(); return; } return resolvedSourcePath; } const PLUGIN_ORIGIN_RANK = { config: 0, workspace: 1, global: 2, bundled: 3 }; function safeStatMtimeMs(filePath) { try { return fs.statSync(filePath).mtimeMs; } catch { return null; } } function normalizePreferredPluginIds(raw) { return normalizeOptionalTrimmedStringList(raw); } function normalizePackageChannelCommands(commands) { if (!commands || typeof commands !== "object" || Array.isArray(commands)) return; const record = commands; const nativeCommandsAutoEnabled = typeof record.nativeCommandsAutoEnabled === "boolean" ? record.nativeCommandsAutoEnabled : void 0; const nativeSkillsAutoEnabled = typeof record.nativeSkillsAutoEnabled === "boolean" ? record.nativeSkillsAutoEnabled : void 0; return nativeCommandsAutoEnabled !== void 0 || nativeSkillsAutoEnabled !== void 0 ? { ...nativeCommandsAutoEnabled !== void 0 ? { nativeCommandsAutoEnabled } : {}, ...nativeSkillsAutoEnabled !== void 0 ? { nativeSkillsAutoEnabled } : {} } : void 0; } function mergePackageChannelMetaIntoChannelConfigs(params) { const channelId = params.packageChannel?.id?.trim(); if (!channelId || isBlockedObjectKey(channelId) || !params.channelConfigs || !Object.hasOwn(params.channelConfigs, channelId)) return params.channelConfigs; const existing = params.channelConfigs[channelId]; if (!existing) return params.channelConfigs; const label = existing.label ?? normalizeOptionalString(params.packageChannel?.label) ?? ""; const description = existing.description ?? normalizeOptionalString(params.packageChannel?.blurb) ?? ""; const preferOver = existing.preferOver ?? normalizePreferredPluginIds(params.packageChannel?.preferOver); const commands = existing.commands ?? normalizePackageChannelCommands(params.packageChannel?.commands); const merged = Object.create(null); for (const [key, value] of Object.entries(params.channelConfigs)) if (!isBlockedObjectKey(key)) merged[key] = value; merged[channelId] = { ...existing, ...label ? { label } : {}, ...description ? { description } : {}, ...preferOver?.length ? { preferOver } : {}, ...commands ? { commands } : {} }; return merged; } function mergeContractLists(left, right) { const merged = uniqueStrings([...left ?? [], ...right ?? []].map((value) => value.trim()).filter((value) => value.length > 0)); return merged.length > 0 ? merged : void 0; } function mergeManifestContracts(manifestContracts, catalogContracts) { if (!catalogContracts) return manifestContracts; const contracts = {}; for (const key of [ "embeddedExtensionFactories", "agentToolResultMiddleware", "trustedToolPolicies", "externalAuthProviders", "embeddingProviders", "memoryEmbeddingProviders", "speechProviders", "realtimeTranscriptionProviders", "realtimeVoiceProviders", "mediaUnderstandingProviders", "transcriptSourceProviders", "documentExtractors", "imageGenerationProviders", "videoGenerationProviders", "musicGenerationProviders", "webContentExtractors", "webFetchProviders", "webSearchProviders", "migrationProviders", "gatewayMethodDispatch", "tools" ]) { const merged = mergeContractLists(manifestContracts?.[key], catalogContracts[key]); if (merged) contracts[key] = merged; } return Object.keys(contracts).length > 0 ? contracts : void 0; } function mergeCatalogChannelConfigs(params) { if (!params.catalogChannelConfigs) return params.manifestChannelConfigs; const merged = Object.create(null); for (const [key, value] of Object.entries(params.catalogChannelConfigs)) if (!isBlockedObjectKey(key)) merged[key] = value; for (const [key, value] of Object.entries(params.manifestChannelConfigs ?? {})) if (!isBlockedObjectKey(key)) { const catalogValue = merged[key]; merged[key] = catalogValue ? { ...catalogValue, ...value, schema: value.schema ?? catalogValue.schema, ...catalogValue.uiHints || value.uiHints ? { uiHints: { ...catalogValue.uiHints, ...value.uiHints } } : {}, ...value.runtime ?? catalogValue.runtime ? { runtime: value.runtime ?? catalogValue.runtime } : {}, ...value.label ?? catalogValue.label ? { label: value.label ?? catalogValue.label } : {}, ...value.description ?? catalogValue.description ? { description: value.description ?? catalogValue.description } : {}, ...value.preferOver ?? catalogValue.preferOver ? { preferOver: value.preferOver ?? catalogValue.preferOver } : {}, ...value.commands ?? catalogValue.commands ? { commands: value.commands ?? catalogValue.commands } : {} } : value; } return Object.keys(merged).length > 0 ? merged : void 0; } function buildRecord(params) { const providerSourceEntry = params.manifest.providerCatalogEntry !== void 0 ? { entryName: "providerCatalogEntry", entry: params.manifest.providerCatalogEntry } : void 0; const manifestChannelConfigs = params.candidate.origin === "bundled" && params.bundledChannelConfigCollector ? params.bundledChannelConfigCollector({ pluginDir: params.candidate.packageDir ?? params.candidate.rootDir, manifest: params.manifest, packageManifest: params.candidate.packageManifest }) : params.manifest.channelConfigs; const officialCatalogManifest = params.candidate.origin !== "bundled" ? getOfficialExternalPluginCatalogManifest(getOfficialExternalPluginCatalogEntryForPackage(params.candidate.packageName) ?? {}) : void 0; const channelConfigs = mergePackageChannelMetaIntoChannelConfigs({ channelConfigs: mergeCatalogChannelConfigs({ manifestChannelConfigs, catalogChannelConfigs: officialCatalogManifest?.channelConfigs }), packageChannel: params.candidate.packageManifest?.channel }); const packageChannelCommands = normalizePackageChannelCommands(params.candidate.packageManifest?.channel?.commands); return { id: params.manifest.id, name: normalizeOptionalString(params.manifest.name) ?? params.candidate.packageName, description: normalizeOptionalString(params.manifest.description) ?? params.candidate.packageDescription, version: normalizeOptionalString(params.manifest.version) ?? params.candidate.packageVersion, packageName: params.candidate.packageName, packageVersion: params.candidate.packageVersion, packageDescription: params.candidate.packageDescription, enabledByDefault: params.manifest.enabledByDefault === true ? true : void 0, enabledByDefaultOnPlatforms: params.manifest.enabledByDefaultOnPlatforms, autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders, legacyPluginIds: params.manifest.legacyPluginIds, format: params.candidate.format ?? "openclaw", bundleFormat: params.candidate.bundleFormat, kind: params.manifest.kind, channels: params.manifest.channels ?? [], providers: params.manifest.providers ?? [], providerDiscoverySource: providerSourceEntry ? resolveManifestPluginSourcePath({ rootDir: params.candidate.rootDir, manifestPath: params.manifestPath, pluginId: params.manifest.id, entryName: providerSourceEntry.entryName, entry: providerSourceEntry.entry, rejectHardlinks: params.rejectHardlinks, diagnostics: params.diagnostics }) : void 0, modelSupport: params.manifest.modelSupport, modelCatalog: params.manifest.modelCatalog, modelPricing: params.manifest.modelPricing, modelIdNormalization: params.manifest.modelIdNormalization, providerEndpoints: params.manifest.providerEndpoints, providerRequest: params.manifest.providerRequest, secretProviderIntegrations: params.manifest.secretProviderIntegrations, cliBackends: params.manifest.cliBackends ?? [], syntheticAuthRefs: params.manifest.syntheticAuthRefs ?? [], nonSecretAuthMarkers: params.manifest.nonSecretAuthMarkers ?? [], commandAliases: params.manifest.commandAliases, providerAuthEnvVars: params.manifest.providerAuthEnvVars, providerAuthAliases: params.manifest.providerAuthAliases, channelEnvVars: params.manifest.channelEnvVars, providerAuthChoices: params.manifest.providerAuthChoices, activation: params.manifest.activation, setup: params.manifest.setup, packageManifest: params.candidate.packageManifest, packageDependencies: params.candidate.packageDependencies, packageOptionalDependencies: params.candidate.packageOptionalDependencies, packageChannel: params.candidate.packageManifest?.channel, packageInstall: params.candidate.packageManifest?.install, trustedOfficialInstall: params.trustedOfficialInstall === true ? true : void 0, qaRunners: params.manifest.qaRunners, skills: params.manifest.skills ?? [], settingsFiles: [], hooks: [], origin: params.candidate.origin, workspaceDir: params.candidate.workspaceDir, rootDir: params.candidate.rootDir, source: params.candidate.source, setupSource: params.candidate.setupSource, startupDeferConfiguredChannelFullLoadUntilAfterListen: params.candidate.packageManifest?.startup?.deferConfiguredChannelFullLoadUntilAfterListen === true, manifestPath: params.manifestPath, schemaCacheKey: params.schemaCacheKey, configSchema: params.configSchema, configUiHints: params.manifest.uiHints, contracts: mergeManifestContracts(params.manifest.contracts, officialCatalogManifest?.contracts), mediaUnderstandingProviderMetadata: params.manifest.mediaUnderstandingProviderMetadata, imageGenerationProviderMetadata: params.manifest.imageGenerationProviderMetadata, videoGenerationProviderMetadata: params.manifest.videoGenerationProviderMetadata, musicGenerationProviderMetadata: params.manifest.musicGenerationProviderMetadata, toolMetadata: params.manifest.toolMetadata, configContracts: params.manifest.configContracts, channelConfigs, ...params.candidate.packageManifest?.channel?.id ? { channelCatalogMeta: { id: params.candidate.packageManifest.channel.id, ...typeof params.candidate.packageManifest.channel.label === "string" ? { label: params.candidate.packageManifest.channel.label } : {}, ...typeof params.candidate.packageManifest.channel.blurb === "string" ? { blurb: params.candidate.packageManifest.channel.blurb } : {}, ...params.candidate.packageManifest.channel.preferOver ? { preferOver: params.candidate.packageManifest.channel.preferOver } : {}, ...packageChannelCommands ? { commands: packageChannelCommands } : {} } } : {} }; } function buildBundleRecord(params) { return { id: params.manifest.id, name: normalizeOptionalString(params.manifest.name) ?? params.candidate.idHint, description: normalizeOptionalString(params.manifest.description), version: normalizeOptionalString(params.manifest.version), packageName: params.candidate.packageName, packageVersion: params.candidate.packageVersion, packageDescription: params.candidate.packageDescription, packageManifest: params.candidate.packageManifest, packageDependencies: params.candidate.packageDependencies, packageOptionalDependencies: params.candidate.packageOptionalDependencies, packageChannel: params.candidate.packageManifest?.channel, packageInstall: params.candidate.packageManifest?.install, format: "bundle", bundleFormat: params.candidate.bundleFormat, bundleCapabilities: params.manifest.capabilities, activation: params.manifest.activation, channels: [], providers: [], cliBackends: [], syntheticAuthRefs: [], nonSecretAuthMarkers: [], skills: params.manifest.skills ?? [], settingsFiles: params.manifest.settingsFiles ?? [], hooks: params.manifest.hooks ?? [], origin: params.candidate.origin, workspaceDir: params.candidate.workspaceDir, rootDir: params.candidate.rootDir, source: params.candidate.source, manifestPath: params.manifestPath, schemaCacheKey: void 0, configSchema: void 0, configUiHints: void 0, configContracts: void 0, channelConfigs: void 0 }; } function pushProviderAuthEnvVarsCompatDiagnostic(params) { if (params.record.origin === "bundled" || !params.record.providerAuthEnvVars) return; const setupProviderEnvVars = new Map((params.record.setup?.providers ?? []).map((provider) => [provider.id, new Set(provider.envVars ?? [])])); const providerIds = Object.entries(params.record.providerAuthEnvVars).filter(([providerId, envVars]) => { if (!providerId.trim() || envVars.length === 0) return false; const mirroredEnvVars = setupProviderEnvVars.get(providerId); return !mirroredEnvVars || envVars.some((envVar) => !mirroredEnvVars.has(envVar)); }).map(([providerId]) => providerId).toSorted((left, right) => left.localeCompare(right)); if (providerIds.length === 0) return; params.diagnostics.push({ level: "warn", pluginId: sanitizeForLog(params.record.id), source: sanitizeForLog(params.record.manifestPath), message: `providerAuthEnvVars is deprecated compatibility metadata for provider env-var lookup; mirror ${providerIds.map(sanitizeForLog).join(", ")} env vars to setup.providers[].envVars before the deprecation window closes` }); } function pushNonBundledChannelConfigDescriptorDiagnostic(params) { if (params.record.origin === "bundled" || params.record.format === "bundle") return; const configuredEntry = params.normalized?.entries[params.record.id]; if (params.normalized?.enabled === false || configuredEntry?.enabled === false || params.normalized?.deny.includes(params.record.id) || params.normalized?.allow.length && !params.normalized.allow.includes(params.record.id)) return; const declaredChannels = params.record.channels.map((channelId) => channelId.trim()).filter((channelId) => channelId.length > 0); if (declaredChannels.length === 0) return; const channelConfigs = params.record.channelConfigs ?? {}; const missingChannels = declaredChannels.filter((channelId) => !Object.hasOwn(channelConfigs, channelId)); if (missingChannels.length === 0) return; const safeMissingChannels = missingChannels.map(sanitizeForLog); params.diagnostics.push({ level: "warn", pluginId: sanitizeForLog(params.record.id), source: sanitizeForLog(params.record.manifestPath), message: `channel plugin manifest declares ${safeMissingChannels.join(", ")} without channelConfigs metadata; add openclaw.plugin.json#channelConfigs so config schema and setup surfaces work before runtime loads. Channels without channelConfigs still appear in channel listings, but setup UI may be limited.` }); } function pushManifestCompatibilityDiagnostics(params) { pushProviderAuthEnvVarsCompatDiagnostic(params); pushNonBundledChannelConfigDescriptorDiagnostic(params); } function dedupePluginDiagnostics(diagnostics) { const seen = /* @__PURE__ */ new Set(); const deduped = []; for (const diagnostic of diagnostics) { const key = JSON.stringify([ diagnostic.level, diagnostic.pluginId ?? "", diagnostic.message ]); if (seen.has(key)) continue; seen.add(key); deduped.push(diagnostic); } return deduped; } function matchesInstalledPluginRecord(params) { if (params.candidate.origin !== "global" && params.candidate.origin !== "config") return false; const record = params.installRecords[params.pluginId]; if (!record) return false; const candidatePaths = [ params.candidate.rootDir, params.candidate.packageDir, params.candidate.source, params.candidate.setupSource ].filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => { const resolved = resolveUserPath(entry, params.env); return safeRealpathSync(resolved) ?? resolved; }); const trackedPaths = [record.installPath, record.sourcePath].filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => { const resolved = resolveUserPath(entry, params.env); return safeRealpathSync(resolved) ?? resolved; }); if (candidatePaths.length === 0 || trackedPaths.length === 0) return false; return trackedPaths.some((trackedPath) => candidatePaths.some((candidatePath) => candidatePath === trackedPath || isPathInside(trackedPath, candidatePath) || isPathInside(candidatePath, trackedPath))); } function npmSpecMatchesPackage(value, packageName) { const normalized = value?.trim(); if (!normalized) return false; if (normalized === packageName) return true; return normalized.startsWith(`${packageName}@`); } function isTrustedOfficialPluginInstall(params) { if (params.candidate.origin !== "global" && params.candidate.origin !== "config" || !matchesInstalledPluginRecord({ pluginId: params.pluginId, candidate: params.candidate, env: params.env, installRecords: params.installRecords })) return false; const packageName = params.candidate.packageName?.trim(); if (!packageName) return false; const catalogEntry = getOfficialExternalPluginCatalogEntryForPackage(packageName); if (!catalogEntry || resolveOfficialExternalPluginId(catalogEntry) !== params.pluginId) return false; const officialInstall = resolveOfficialExternalPluginInstall(catalogEntry); const installRecord = params.installRecords[params.pluginId]; if (!installRecord) return false; if (installRecord.source === "npm" && officialInstall?.npmSpec === packageName && [ installRecord.resolvedName, installRecord.spec, installRecord.resolvedSpec, params.candidate.packageName ].some((value) => npmSpecMatchesPackage(value, packageName))) return true; if (installRecord.source === "clawhub" && officialInstall?.clawhubSpec && installRecord.clawhubChannel === "official" && (installRecord.clawhubPackage === packageName || installRecord.spec === officialInstall.clawhubSpec || installRecord.resolvedSpec === officialInstall.clawhubSpec)) return true; return false; } function resolveDuplicatePrecedenceRank(params) { if (params.candidate.origin === "config") return 0; if (params.candidate.origin === "bundled" && isBundledPluginInsideDevSourceRoot({ rootDir: params.candidate.rootDir, env: params.env })) return 1; if (params.candidate.origin === "global" && matchesInstalledPluginRecord({ pluginId: params.pluginId, candidate: params.candidate, config: params.config, env: params.env, installRecords: params.installRecords })) return 2; if (params.candidate.origin === "bundled") return 3; if (params.candidate.origin === "workspace") return 4; return 5; } function isIntentionalInstalledBundledDuplicate(params) { const leftIsInstalled = matchesInstalledPluginRecord({ pluginId: params.pluginId, candidate: params.left, config: params.config, env: params.env, installRecords: params.installRecords }); const rightIsInstalled = matchesInstalledPluginRecord({ pluginId: params.pluginId, candidate: params.right, config: params.config, env: params.env, installRecords: params.installRecords }); return leftIsInstalled && params.right.origin === "bundled" && !isBundledPluginInsideDevSourceRoot({ rootDir: params.right.rootDir, env: params.env }) || rightIsInstalled && params.left.origin === "bundled" && !isBundledPluginInsideDevSourceRoot({ rootDir: params.left.rootDir, env: params.env }); } function isSameGlobalPackageDuplicate(left, right) { if (left.origin !== "global" || right.origin !== "global") return false; const leftPackageName = normalizeOptionalString(left.packageName); const rightPackageName = normalizeOptionalString(right.packageName); if (!leftPackageName || leftPackageName !== rightPackageName) return false; const leftPackageVersion = normalizeOptionalString(left.packageVersion); const rightPackageVersion = normalizeOptionalString(right.packageVersion); return Boolean(leftPackageVersion && rightPackageVersion && leftPackageVersion === rightPackageVersion); } function loadPluginManifestRegistry(params = {}) { const config = params.config ?? {}; const normalized = normalizePluginsConfigWithResolver(config.plugins); const env = params.env ?? process.env; let installRecords = params.installRecords; let installRecordsLoaded = Boolean(params.installRecords); const getInstallRecords = () => { if (!installRecordsLoaded) { installRecords = loadInstalledPluginIndexInstallRecordsSync({ env }); installRecordsLoaded = true; } return installRecords ?? {}; }; const discovery = params.candidates ? { candidates: params.candidates, diagnostics: params.diagnostics ?? [] } : params.discovery ?? discoverOpenClawPlugins({ workspaceDir: params.workspaceDir, extraPaths: normalized.loadPaths, env, installRecords: getInstallRecords() }); const diagnostics = [...discovery.diagnostics]; const candidates = discovery.candidates; const records = []; const seenIds = /* @__PURE__ */ new Map(); const realpathCache = /* @__PURE__ */ new Map(); const currentHostVersion = resolveCompatibilityHostVersion(env); for (const candidate of candidates) { const rejectHardlinks = shouldRejectHardlinkedPluginFiles({ origin: candidate.origin, rootDir: candidate.rootDir, env, realpathCache }); const isBundleRecord = (candidate.format ?? "openclaw") === "bundle"; const manifestRes = candidate.origin === "bundled" && candidate.bundledManifest && candidate.bundledManifestPath ? { ok: true, manifest: candidate.bundledManifest, manifestPath: candidate.bundledManifestPath } : isBundleRecord && candidate.bundleFormat ? loadBundleManifest({ rootDir: candidate.rootDir, bundleFormat: candidate.bundleFormat, rejectHardlinks }) : loadPluginManifest(candidate.rootDir, rejectHardlinks); if (!manifestRes.ok) { diagnostics.push({ level: "error", message: manifestRes.error, source: manifestRes.manifestPath }); continue; } const manifest = manifestRes.manifest; if (candidate.origin !== "bundled") { const packageManifestSource = path.join(candidate.packageDir ?? candidate.rootDir, "package.json"); const allowLegacyBareMinHostVersion = candidate.origin === "global" && matchesInstalledPluginRecord({ pluginId: manifest.id, candidate, config, env, installRecords: getInstallRecords() }); const minHostVersionCheck = checkMinHostVersion({ currentVersion: currentHostVersion, minHostVersion: candidate.packageManifest?.install?.minHostVersion, allowLegacyBareSemver: allowLegacyBareMinHostVersion }); if (!minHostVersionCheck.ok) { diagnostics.push({ level: minHostVersionCheck.kind === "invalid" ? "error" : "warn", pluginId: manifest.id, source: packageManifestSource, message: minHostVersionCheck.kind === "invalid" ? `plugin manifest invalid | ${minHostVersionCheck.error}` : minHostVersionCheck.kind === "unknown_host_version" ? `plugin requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host version could not be determined; skipping load` : `plugin requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host is ${minHostVersionCheck.currentVersion}; skipping load` }); continue; } const packagePluginApiRangeCheck = resolvePackagePluginApiRange(candidate.packageManifest); if (!packagePluginApiRangeCheck.ok) { diagnostics.push({ level: "error", pluginId: manifest.id, source: packageManifestSource, message: `plugin manifest invalid | ${packagePluginApiRangeCheck.error}` }); continue; } const packagePluginApiRange = packagePluginApiRangeCheck.range; if (packagePluginApiRange && !satisfiesPluginApiRange(currentHostVersion, packagePluginApiRange)) { diagnostics.push({ level: "warn", pluginId: manifest.id, source: packageManifestSource, message: `plugin requires plugin API ${packagePluginApiRange}, but this host is ${currentHostVersion}; skipping load` }); continue; } } const configSchema = "configSchema" in manifest ? manifest.configSchema : void 0; const schemaCacheKey = (() => { if (!configSchema) return; const manifestMtime = safeStatMtimeMs(manifestRes.manifestPath); return manifestMtime ? `${manifestRes.manifestPath}:${manifestMtime}` : manifestRes.manifestPath; })(); const record = isBundleRecord ? buildBundleRecord({ manifest, candidate, manifestPath: manifestRes.manifestPath }) : buildRecord({ manifest, candidate, manifestPath: manifestRes.manifestPath, diagnostics, rejectHardlinks, schemaCacheKey, configSchema, trustedOfficialInstall: isTrustedOfficialPluginInstall({ pluginId: manifest.id, candidate, env, installRecords: getInstallRecords() }), ...params.bundledChannelConfigCollector ? { bundledChannelConfigCollector: params.bundledChannelConfigCollector } : {} }); const existing = seenIds.get(manifest.id); if (existing) { const samePath = existing.candidate.rootDir === candidate.rootDir; if ((() => { if (samePath) return true; const existingReal = safeRealpathSync(existing.candidate.rootDir, realpathCache); const candidateReal = safeRealpathSync(candidate.rootDir, realpathCache); return Boolean(existingReal && candidateReal && existingReal === candidateReal); })()) { if (PLUGIN_ORIGIN_RANK[candidate.origin] < PLUGIN_ORIGIN_RANK[existing.candidate.origin]) { records[existing.recordIndex] = record; seenIds.set(manifest.id, { candidate, recordIndex: existing.recordIndex }); pushManifestCompatibilityDiagnostics({ record, diagnostics, normalized }); } continue; } const candidateWins = resolveDuplicatePrecedenceRank({ pluginId: manifest.id, candidate, config, env, installRecords: getInstallRecords() }) < resolveDuplicatePrecedenceRank({ pluginId: manifest.id, candidate: existing.candidate, config, env, installRecords: getInstallRecords() }); const winnerCandidate = candidateWins ? candidate : existing.candidate; const overriddenCandidate = candidateWins ? existing.candidate : candidate; if (candidateWins) { records[existing.recordIndex] = record; seenIds.set(manifest.id, { candidate, recordIndex: existing.recordIndex }); pushManifestCompatibilityDiagnostics({ record, diagnostics, normalized }); } if (isIntentionalInstalledBundledDuplicate({ pluginId: manifest.id, left: candidate, right: existing.candidate, config, env, installRecords: getInstallRecords() })) continue; if (isSameGlobalPackageDuplicate(candidate, existing.candidate)) continue; diagnostics.push({ level: "warn", pluginId: manifest.id, source: overriddenCandidate.source, message: winnerCandidate.origin === "config" ? `duplicate plugin id resolved by explicit config-selected plugin; ${overriddenCandidate.origin} plugin will be overridden by config plugin (${winnerCandidate.source})` : `duplicate plugin id detected; ${overriddenCandidate.origin} plugin will be overridden by ${winnerCandidate.origin} plugin (${winnerCandidate.source})` }); continue; } seenIds.set(manifest.id, { candidate, recordIndex: records.length }); records.push(record); pushManifestCompatibilityDiagnostics({ record, diagnostics, normalized }); } return { plugins: records, diagnostics: dedupePluginDiagnostics(diagnostics) }; } //#endregion export { resolveMemorySlotDecision as a, resolveEffectivePluginActivationState as i, hasExplicitPluginConfig as n, normalizePluginsConfigWithResolver as r, loadPluginManifestRegistry as t };