UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

569 lines (568 loc) 26.7 kB
import { t as sanitizeForLog } from "./ansi-BI9w76Cm.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { _ as uniqueStrings, g as sortUniqueStrings } from "./string-normalization-WNUDCpXX.js"; import { t as isBlockedObjectKey } from "./prototype-keys-D2nJOZIy.js"; import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js"; import "./agent-scope-MrLta7Pq.js"; import { n as normalizeAccountId, t as DEFAULT_ACCOUNT_ID } from "./account-id-Df9e41E6.js"; import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js"; import { n as registerPluginMetadataProcessMemoLifecycleClear } from "./plugin-metadata-lifecycle-C3dWg4tn.js"; import { a as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-Ctk9Oarq.js"; import { n as getCachedPluginModuleLoader } from "./plugin-module-loader-cache-lz0qhu-X.js"; import { a as getBundledChannelSetupPlugin } from "./bundled-DcYUmgzC.js"; import { E as hasExplicitChannelConfig, M as resolveDiscoverableScopedChannelPluginIds, O as listConfiguredChannelIdsForReadOnlyScope } from "./gateway-startup-plugin-ids-3X_G47Qt.js"; import { g as resolveRuntimeConfigCacheKey } from "./runtime-snapshot-D93_HOsR.js"; import { a as resolveSetupChannelRegistration, t as channelPluginIdBelongsToManifest } from "./loader-channel-setup-FLQKABwG.js"; import { n as getActivePluginChannelRegistryVersion } from "./runtime-B2ROiq5l.js"; import { i as listChannelPlugins } from "./registry-MkxNn1Ue.js"; import "./channel-plugin-ids-yxLDmEfb.js"; import { n as normalizeChannelCommandDefaults, r as readOwnRecordValue, t as isSafeManifestChannelId } from "./read-only-command-defaults-3UCyWRWA.js"; import { fileURLToPath, pathToFileURL } from "node:url"; import path from "node:path"; import { createHash } from "node:crypto"; //#region src/channels/plugins/read-only.ts /** * Read-only channel plugin discovery. * * Builds lightweight channel plugin views from config, manifests, and setup metadata. */ const SOURCE_PLUGIN_LOADER_MODULE_CANDIDATES = ["../../plugins/loader.js", "../../plugins/loader.ts"]; const BUILT_PLUGIN_LOADER_MODULE_CANDIDATES = ["plugins/loader.js", "plugins/build-smoke-entry.js"]; const moduleLoaders = /* @__PURE__ */ new Map(); const log = createSubsystemLogger("channels"); let pluginLoaderModule; function listBuiltPluginLoaderModuleCandidateUrls(importerUrl) { let importerPath; try { importerPath = fileURLToPath(importerUrl); } catch { return []; } const distMarker = `${path.sep}dist${path.sep}`; const distMarkerIndex = importerPath.lastIndexOf(distMarker); if (distMarkerIndex < 0) return []; const distRoot = importerPath.slice(0, distMarkerIndex + distMarker.length - 1); return BUILT_PLUGIN_LOADER_MODULE_CANDIDATES.map((candidate) => pathToFileURL(path.join(distRoot, candidate))); } function listPluginLoaderModuleCandidateUrls(importerUrl = import.meta.url) { const builtCandidates = listBuiltPluginLoaderModuleCandidateUrls(importerUrl); if (builtCandidates.length > 0) return builtCandidates; return SOURCE_PLUGIN_LOADER_MODULE_CANDIDATES.map((candidate) => new URL(candidate, importerUrl)); } function loadPluginLoaderModule() { if (pluginLoaderModule) return pluginLoaderModule; for (const candidate of listPluginLoaderModuleCandidateUrls()) { const modulePath = fileURLToPath(candidate); try { pluginLoaderModule = getCachedPluginModuleLoader({ cache: moduleLoaders, modulePath, importerUrl: import.meta.url, preferBuiltDist: true, loaderFilename: import.meta.url, tryNative: true })(modulePath); return pluginLoaderModule; } catch {} } throw new Error("Could not load plugin runtime loader for channel setup fallback."); } const readOnlyChannelPluginResolutionCache = /* @__PURE__ */ new Map(); const MAX_READ_ONLY_CHANNEL_PLUGIN_RESOLUTION_CACHE_SIZE = 8; const readOnlyChannelPluginObjectIds = /* @__PURE__ */ new WeakMap(); let nextReadOnlyChannelPluginObjectId = 1; registerPluginMetadataProcessMemoLifecycleClear(() => { readOnlyChannelPluginResolutionCache.clear(); }); function cloneReadOnlyChannelPluginResolution(resolution) { return { plugins: [...resolution.plugins], configuredChannelIds: [...resolution.configuredChannelIds], missingConfiguredChannelIds: [...resolution.missingConfiguredChannelIds], loadFailures: resolution.loadFailures.map((failure) => ({ ...failure })) }; } function rememberReadOnlyChannelPluginResolution(key, resolution) { if (readOnlyChannelPluginResolutionCache.has(key)) readOnlyChannelPluginResolutionCache.delete(key); readOnlyChannelPluginResolutionCache.set(key, cloneReadOnlyChannelPluginResolution(resolution)); while (readOnlyChannelPluginResolutionCache.size > MAX_READ_ONLY_CHANNEL_PLUGIN_RESOLUTION_CACHE_SIZE) { const oldestKey = readOnlyChannelPluginResolutionCache.keys().next().value; if (!oldestKey) break; readOnlyChannelPluginResolutionCache.delete(oldestKey); } } function resolveReadOnlyChannelPluginResolutionCacheKey(params) { if (params.env !== process.env) return null; if (params.options.includePersistedAuthState !== false) return null; const activationSourceConfig = params.options.activationSourceConfig ?? params.cfg; return [ resolveRuntimeConfigCacheKey(params.cfg), activationSourceConfig === params.cfg ? "activation:same" : resolveRuntimeConfigCacheKey(activationSourceConfig), `channel-registry:${getActivePluginChannelRegistryVersion()}`, `loaded-channels:${fingerprintLoadedChannelPlugins(params.loadedChannelPlugins)}`, `env:${hashEnvironment(params.env)}`, `cwd:${process.cwd()}`, `state:${params.options.stateDir ?? ""}`, `workspace:${params.workspaceDir}`, `setup:${params.options.includeSetupFallbackPlugins === true}` ].join("\0"); } function resolveReadOnlyChannelPluginObjectId(plugin) { const existing = readOnlyChannelPluginObjectIds.get(plugin); if (existing !== void 0) return existing; const next = nextReadOnlyChannelPluginObjectId; nextReadOnlyChannelPluginObjectId += 1; readOnlyChannelPluginObjectIds.set(plugin, next); return next; } function fingerprintLoadedChannelPlugins(plugins) { return plugins.map((plugin) => `${plugin.id}:${resolveReadOnlyChannelPluginObjectId(plugin)}`).join(","); } function hashEnvironment(env) { const hash = createHash("sha256"); for (const key of Object.keys(env).toSorted((left, right) => left.localeCompare(right))) { hash.update(key); hash.update("\0"); hash.update(env[key] ?? ""); hash.update("\0"); } return hash.digest("base64url"); } function addChannelPlugins(byId, plugins, options) { for (const plugin of plugins) { if (!plugin) continue; if (options?.onlyIds && !options.onlyIds.has(plugin.id)) continue; if (options?.allowOverwrite === false && byId.has(plugin.id)) continue; byId.set(plugin.id, plugin); } } function rebindChannelScopedString(value, sourceChannelId, targetChannelId) { const sourcePrefix = `channels.${sourceChannelId}`; if (value === sourcePrefix) return `channels.${targetChannelId}`; if (value.startsWith(`${sourcePrefix}.`)) return `channels.${targetChannelId}${value.slice(sourcePrefix.length)}`; return value; } function normalizeManifestText(value, fallback) { return sanitizeForLog(value?.trim() || fallback).trim(); } function rebindChannelConfig(cfg, sourceChannelId, targetChannelId) { if (sourceChannelId === targetChannelId || !cfg.channels) return cfg; return { ...cfg, channels: { ...cfg.channels, [sourceChannelId]: cfg.channels[targetChannelId] } }; } function restoreReboundChannelConfig(params) { if (params.sourceChannelId === params.targetChannelId || !params.updated.channels) return params.updated; const nextChannels = { ...params.updated.channels }; if (Object.hasOwn(nextChannels, params.sourceChannelId)) nextChannels[params.targetChannelId] = nextChannels[params.sourceChannelId]; else delete nextChannels[params.targetChannelId]; if (params.original.channels && Object.hasOwn(params.original.channels, params.sourceChannelId)) nextChannels[params.sourceChannelId] = params.original.channels[params.sourceChannelId]; else delete nextChannels[params.sourceChannelId]; return { ...params.updated, channels: nextChannels }; } function getChannelConfigRecord(cfg, channelId) { if (!isSafeManifestChannelId(channelId)) return {}; const channels = cfg.channels; if (!channels || typeof channels !== "object" || Array.isArray(channels)) return {}; const entry = readOwnRecordValue(channels, channelId); return entry && typeof entry === "object" && !Array.isArray(entry) ? entry : {}; } function listManifestChannelAccountIds(cfg, channelId) { const accounts = getChannelConfigRecord(cfg, channelId).accounts; if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) return sortUniqueStrings(Object.keys(accounts).filter((accountId) => !isBlockedObjectKey(accountId)).map((accountId) => normalizeAccountId(accountId)).filter((accountId) => !isBlockedObjectKey(accountId))); return hasExplicitChannelConfig({ config: cfg, channelId }) ? [DEFAULT_ACCOUNT_ID] : []; } function resolveManifestChannelAccountConfig(params) { const channelConfig = getChannelConfigRecord(params.cfg, params.channelId); const resolvedAccountId = normalizeAccountId(params.accountId); const accounts = channelConfig.accounts; if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) { const accountConfig = readOwnRecordValue(accounts, resolvedAccountId); if (accountConfig && typeof accountConfig === "object" && !Array.isArray(accountConfig)) return accountConfig; } return channelConfig; } function buildManifestChannelPlugin(params) { if (!isSafeManifestChannelId(params.channelId)) return; const catalogMeta = params.record.channelCatalogMeta?.id === params.channelId ? params.record.channelCatalogMeta : void 0; const channelConfigValue = params.record.channelConfigs ? readOwnRecordValue(params.record.channelConfigs, params.channelId) : void 0; if (!catalogMeta && (!channelConfigValue || typeof channelConfigValue !== "object" || Array.isArray(channelConfigValue)) && !params.record.channels.includes(params.channelId)) return; const channelConfig = channelConfigValue && typeof channelConfigValue === "object" && !Array.isArray(channelConfigValue) ? channelConfigValue : void 0; const label = normalizeManifestText(channelConfig?.label ?? catalogMeta?.label, params.record.name || params.channelId) || params.channelId; const blurb = normalizeManifestText(channelConfig?.description ?? catalogMeta?.blurb, params.record.description || ""); const commands = normalizeChannelCommandDefaults(channelConfig?.commands ?? catalogMeta?.commands); return { id: params.channelId, meta: { id: params.channelId, label, selectionLabel: label, docsPath: `/channels/${encodeURIComponent(params.channelId)}`, blurb, ...channelConfig?.preferOver?.length ? { preferOver: channelConfig.preferOver } : catalogMeta?.preferOver?.length ? { preferOver: catalogMeta.preferOver } : {} }, capabilities: { chatTypes: ["direct"] }, ...commands ? { commands } : {}, ...channelConfig ? { configSchema: { schema: channelConfig.schema, ...channelConfig.uiHints ? { uiHints: channelConfig.uiHints } : {}, ...channelConfig.runtime ? { runtime: channelConfig.runtime } : {} } } : {}, config: { listAccountIds: (cfg) => listManifestChannelAccountIds(cfg, params.channelId), defaultAccountId: () => DEFAULT_ACCOUNT_ID, resolveAccount: (cfg, accountId) => ({ accountId: normalizeAccountId(accountId), config: resolveManifestChannelAccountConfig({ cfg, channelId: params.channelId, accountId }) }), isEnabled: (_account, cfg) => getChannelConfigRecord(cfg, params.channelId).enabled !== false, isConfigured: (_account, cfg) => hasExplicitChannelConfig({ config: cfg, channelId: params.channelId }), hasConfiguredState: ({ cfg }) => hasExplicitChannelConfig({ config: cfg, channelId: params.channelId }) } }; } function canUseManifestChannelPlugin(record, channelId) { if (Boolean(record.channelConfigs && Object.hasOwn(record.channelConfigs, channelId))) return record.setup?.requiresRuntime === false || !record.setupSource; return record.channelCatalogMeta?.id === channelId || !record.setupSource; } function loadSetupChannelPluginFromManifestRecord(params) { if (!params.record.setupSource || !params.record.channels.includes(params.channelId)) return {}; try { const registration = resolveSetupChannelRegistration(getCachedPluginModuleLoader({ cache: moduleLoaders, modulePath: params.record.setupSource, importerUrl: import.meta.url, preferBuiltDist: true, loaderFilename: import.meta.url, tryNative: true, cacheScopeKey: "read-only-setup-entry" })(params.record.setupSource)); if (registration.loadError) return { failure: { channelId: params.channelId, pluginId: params.record.id, source: params.record.setupSource, message: `failed to load setup entry: ${formatErrorMessage(registration.loadError)}` } }; if (!registration.plugin) return {}; if (!channelPluginIdBelongsToManifest({ channelId: registration.plugin.id, pluginId: params.record.id, manifestChannels: params.record.channels })) return {}; return { plugin: cloneChannelPluginForChannelId(registration.plugin, params.channelId) }; } catch (error) { const detail = formatErrorMessage(error); log.warn(`[channels] failed to load channel setup ${params.record.id}: ${detail}`); return { failure: { channelId: params.channelId, pluginId: params.record.id, source: params.record.setupSource, message: `failed to load setup entry: ${detail}` } }; } } function collectChannelPluginLoadFailuresFromDiagnostics(params) { if (!params.diagnostics?.length || params.channelIds.length === 0) return []; const configuredChannelIds = new Set(params.channelIds); const recordsByPluginId = new Map(params.records.map((record) => [record.id, record])); const failures = []; for (const diagnostic of params.diagnostics) { if (diagnostic.level !== "error" || !diagnostic.pluginId) continue; const record = recordsByPluginId.get(diagnostic.pluginId); if (!record) continue; for (const channelId of record.channels) { if (!configuredChannelIds.has(channelId)) continue; failures.push({ channelId, pluginId: record.id, source: diagnostic.source, message: diagnostic.message }); } } return failures; } function rebindChannelPluginConfig(config, sourceChannelId, targetChannelId) { const rebind = (cfg) => rebindChannelConfig(cfg, sourceChannelId, targetChannelId); return { ...config, listAccountIds: (cfg) => config.listAccountIds(rebind(cfg)), resolveAccount: (cfg, accountId) => config.resolveAccount(rebind(cfg), accountId), inspectAccount: config.inspectAccount ? (cfg, accountId) => config.inspectAccount?.(rebind(cfg), accountId) : void 0, defaultAccountId: config.defaultAccountId ? (cfg) => config.defaultAccountId?.(rebind(cfg)) ?? "" : void 0, setAccountEnabled: config.setAccountEnabled ? (params) => restoreReboundChannelConfig({ original: params.cfg, updated: config.setAccountEnabled?.({ ...params, cfg: rebind(params.cfg) }) ?? params.cfg, sourceChannelId, targetChannelId }) : void 0, deleteAccount: config.deleteAccount ? (params) => restoreReboundChannelConfig({ original: params.cfg, updated: config.deleteAccount?.({ ...params, cfg: rebind(params.cfg) }) ?? params.cfg, sourceChannelId, targetChannelId }) : void 0, isEnabled: config.isEnabled ? (account, cfg) => config.isEnabled?.(account, rebind(cfg)) ?? false : void 0, disabledReason: config.disabledReason ? (account, cfg) => config.disabledReason?.(account, rebind(cfg)) ?? "" : void 0, isConfigured: config.isConfigured ? (account, cfg) => config.isConfigured?.(account, rebind(cfg)) ?? false : void 0, unconfiguredReason: config.unconfiguredReason ? (account, cfg) => config.unconfiguredReason?.(account, rebind(cfg)) ?? "" : void 0, describeAccount: config.describeAccount ? (account, cfg) => config.describeAccount(account, rebind(cfg)) : void 0, resolveAllowFrom: config.resolveAllowFrom ? (params) => config.resolveAllowFrom?.({ ...params, cfg: rebind(params.cfg) }) : void 0, formatAllowFrom: config.formatAllowFrom ? (params) => config.formatAllowFrom?.({ ...params, cfg: rebind(params.cfg) }) ?? [] : void 0, hasConfiguredState: config.hasConfiguredState ? (params) => config.hasConfiguredState?.({ ...params, cfg: rebind(params.cfg) }) ?? false : void 0, hasPersistedAuthState: config.hasPersistedAuthState ? (params) => config.hasPersistedAuthState?.({ ...params, cfg: rebind(params.cfg) }) ?? false : void 0, resolveDefaultTo: config.resolveDefaultTo ? (params) => config.resolveDefaultTo?.({ ...params, cfg: rebind(params.cfg) }) : void 0 }; } function rebindChannelPluginSecrets(secrets, sourceChannelId, targetChannelId) { if (!secrets) return; return { ...secrets, secretTargetRegistryEntries: secrets.secretTargetRegistryEntries?.map((entry) => ({ ...entry, id: rebindChannelScopedString(entry.id, sourceChannelId, targetChannelId), pathPattern: rebindChannelScopedString(entry.pathPattern, sourceChannelId, targetChannelId), ...entry.refPathPattern ? { refPathPattern: rebindChannelScopedString(entry.refPathPattern, sourceChannelId, targetChannelId) } : {} })), unsupportedSecretRefSurfacePatterns: secrets.unsupportedSecretRefSurfacePatterns?.map((pattern) => rebindChannelScopedString(pattern, sourceChannelId, targetChannelId)), collectRuntimeConfigAssignments: secrets.collectRuntimeConfigAssignments ? (params) => secrets.collectRuntimeConfigAssignments?.({ ...params, config: rebindChannelConfig(params.config, sourceChannelId, targetChannelId) }) : void 0 }; } function cloneChannelPluginForChannelId(plugin, channelId) { if (plugin.id === channelId && plugin.meta.id === channelId) return plugin; const sourceChannelId = plugin.id; return { ...plugin, id: channelId, meta: { ...plugin.meta, id: channelId }, config: rebindChannelPluginConfig(plugin.config, sourceChannelId, channelId), secrets: rebindChannelPluginSecrets(plugin.secrets, sourceChannelId, channelId) }; } function addSetupChannelPlugins(byId, setups, options) { for (const setup of setups) { const ownedMissingChannelIds = options.ownedMissingChannelIdsByPluginId.get(setup.pluginId)?.filter(isSafeManifestChannelId); if (!ownedMissingChannelIds || ownedMissingChannelIds.length === 0) continue; if (ownedMissingChannelIds.includes(setup.plugin.id)) { addChannelPlugins(byId, [setup.plugin], { onlyIds: new Set(ownedMissingChannelIds), allowOverwrite: false }); addChannelPlugins(byId, ownedMissingChannelIds.filter((channelId) => channelId !== setup.plugin.id).map((channelId) => cloneChannelPluginForChannelId(setup.plugin, channelId)), { onlyIds: new Set(ownedMissingChannelIds), allowOverwrite: false }); continue; } const ownedChannelIds = (options.ownedChannelIdsByPluginId.get(setup.pluginId) ?? []).filter(isSafeManifestChannelId); if (setup.plugin.id !== setup.pluginId && !ownedChannelIds.includes(setup.plugin.id)) continue; addChannelPlugins(byId, ownedMissingChannelIds.map((channelId) => cloneChannelPluginForChannelId(setup.plugin, channelId)), { onlyIds: new Set(ownedMissingChannelIds), allowOverwrite: false }); } } function addManifestChannelPlugins(byId, records, options) { const channelIds = new Set(options.channelIds); for (const record of records) { if (!options.pluginIds.has(record.id)) continue; for (const channelId of record.channels) { if (!isSafeManifestChannelId(channelId)) continue; if (!channelIds.has(channelId)) continue; if (!canUseManifestChannelPlugin(record, channelId)) continue; addChannelPlugins(byId, [buildManifestChannelPlugin({ record, channelId })], { onlyIds: channelIds, allowOverwrite: false }); } } } function resolveReadOnlyWorkspaceDir(cfg, options) { return options.workspaceDir ?? resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)); } function listExternalChannelManifestRecords(records) { return records.filter((plugin) => plugin.origin !== "bundled" && plugin.channels.length > 0); } function listBundledChannelManifestRecords(records) { return records.filter((plugin) => plugin.origin === "bundled" && plugin.channels.length > 0); } function listPluginIdsForChannels(records, channelIds) { const requestedChannelIds = new Set(channelIds); return records.filter((plugin) => plugin.channels.some((channelId) => requestedChannelIds.has(channelId))).map((plugin) => plugin.id).toSorted((left, right) => left.localeCompare(right)); } function resolveExternalReadOnlyChannelPluginIds(params) { if (params.channelIds.length === 0) return []; const candidatePluginIds = resolveDiscoverableScopedChannelPluginIds({ config: params.cfg, activationSourceConfig: params.activationSourceConfig, channelIds: params.channelIds, workspaceDir: params.workspaceDir, env: params.env, manifestRecords: params.records }); if (candidatePluginIds.length === 0) return []; const requestedChannelIds = new Set(params.channelIds); const candidatePluginIdSet = new Set(candidatePluginIds); return params.records.filter((plugin) => candidatePluginIdSet.has(plugin.id) && plugin.channels.some((channelId) => requestedChannelIds.has(channelId))).map((plugin) => plugin.id).toSorted((left, right) => left.localeCompare(right)); } function listReadOnlyChannelPluginsForConfig(cfg, options) { return resolveReadOnlyChannelPluginsForConfig(cfg, options).plugins; } function resolveReadOnlyChannelPluginsForConfig(cfg, options = {}) { const env = options.env ?? process.env; const workspaceDir = resolveReadOnlyWorkspaceDir(cfg, options); const loadedChannelPlugins = listChannelPlugins(); const cacheKey = resolveReadOnlyChannelPluginResolutionCacheKey({ cfg, options, env, loadedChannelPlugins, workspaceDir }); const cached = cacheKey ? readOnlyChannelPluginResolutionCache.get(cacheKey) : void 0; if (cached) return cloneReadOnlyChannelPluginResolution(cached); const manifestRecords = resolvePluginMetadataSnapshot({ config: cfg, stateDir: options.stateDir, workspaceDir, env, allowWorkspaceScopedCurrent: true }).plugins; const bundledManifestRecords = listBundledChannelManifestRecords(manifestRecords); const externalManifestRecords = listExternalChannelManifestRecords(manifestRecords); const activationSourceConfig = options.activationSourceConfig ?? cfg; const configuredChannelIds = uniqueStrings([...listConfiguredChannelIdsForReadOnlyScope({ config: cfg, activationSourceConfig, workspaceDir, env, includePersistedAuthState: options.includePersistedAuthState, manifestRecords }), ...activationSourceConfig === cfg ? [] : listConfiguredChannelIdsForReadOnlyScope({ config: activationSourceConfig, activationSourceConfig, workspaceDir, env, includePersistedAuthState: options.includePersistedAuthState, manifestRecords })]).filter(isSafeManifestChannelId); const byId = /* @__PURE__ */ new Map(); const loadFailures = []; addChannelPlugins(byId, loadedChannelPlugins); if (options.includeSetupFallbackPlugins === true) for (const channelId of configuredChannelIds) { if (byId.has(channelId)) continue; const setupResults = bundledManifestRecords.filter((record) => record.channels.includes(channelId)).map((record) => loadSetupChannelPluginFromManifestRecord({ record, channelId })); loadFailures.push(...setupResults.map((result) => result.failure).filter((failure) => Boolean(failure))); addChannelPlugins(byId, [setupResults.map((result) => result.plugin).find((plugin) => plugin) ?? getBundledChannelSetupPlugin(channelId, env)]); } const bundledManifestMissingChannelIds = configuredChannelIds.filter((channelId) => !byId.has(channelId)); addManifestChannelPlugins(byId, bundledManifestRecords, { pluginIds: new Set(listPluginIdsForChannels(bundledManifestRecords, bundledManifestMissingChannelIds)), channelIds: bundledManifestMissingChannelIds }); const missingConfiguredChannelIds = configuredChannelIds.filter((channelId) => !byId.has(channelId)); const externalPluginIds = resolveExternalReadOnlyChannelPluginIds({ cfg, activationSourceConfig: options.activationSourceConfig ?? cfg, channelIds: missingConfiguredChannelIds, records: externalManifestRecords, workspaceDir, env }); if (externalPluginIds.length > 0) { const externalPluginIdSet = new Set(externalPluginIds); const ownedChannelIdsByPluginId = new Map(externalManifestRecords.filter((record) => externalPluginIdSet.has(record.id)).map((record) => [record.id, record.channels])); if (missingConfiguredChannelIds.length > 0 && options.includeSetupFallbackPlugins === true) { const missingChannelIdSet = new Set(missingConfiguredChannelIds); const ownedMissingChannelIdsByPluginId = new Map([...ownedChannelIdsByPluginId].map(([pluginId, channelIds]) => [pluginId, channelIds.filter((channelId) => missingChannelIdSet.has(channelId))])); const registry = loadPluginLoaderModule().loadOpenClawPlugins({ config: cfg, activationSourceConfig: options.activationSourceConfig ?? cfg, env, workspaceDir, cache: false, activate: false, includeSetupOnlyChannelPlugins: true, forceSetupOnlyChannelPlugins: true, requireSetupEntryForSetupOnlyChannelPlugins: true, onlyPluginIds: externalPluginIds }); loadFailures.push(...collectChannelPluginLoadFailuresFromDiagnostics({ diagnostics: registry.diagnostics, records: externalManifestRecords, channelIds: missingConfiguredChannelIds })); addSetupChannelPlugins(byId, registry.channelSetups, { ownedChannelIdsByPluginId, ownedMissingChannelIdsByPluginId }); } addManifestChannelPlugins(byId, externalManifestRecords, { pluginIds: externalPluginIdSet, channelIds: missingConfiguredChannelIds.filter((channelId) => !byId.has(channelId)) }); } const resolution = { plugins: [...byId.values()], configuredChannelIds, missingConfiguredChannelIds: configuredChannelIds.filter((channelId) => !byId.has(channelId)), loadFailures }; if (cacheKey) rememberReadOnlyChannelPluginResolution(cacheKey, resolution); return cloneReadOnlyChannelPluginResolution(resolution); } //#endregion export { listReadOnlyChannelPluginsForConfig as n, resolveReadOnlyChannelPluginsForConfig as r, listPluginLoaderModuleCandidateUrls as t };