UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

276 lines (275 loc) 14.2 kB
import { t as sanitizeForLog } from "./ansi-BI9w76Cm.js"; import { t as CHANNEL_IDS } from "./ids-BVNPk-iM.js"; import { n as defaultSlotIdForKey } from "./slots-kpL659LX.js"; import { n as loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader-CPHayOZI.js"; import { o as normalizePluginId } from "./config-state-CikNNz7T.js"; import "./agent-scope-MrLta7Pq.js"; import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js"; import "./installed-plugin-index-records-DXbAzXDd.js"; import { t as shouldSuppressMissingCodexPluginDiagnostics } from "./codex-plugin-diagnostics-BPUTSpy2.js"; import { s as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-DEeoLRlM.js"; import { t as asObjectRecord } from "./object-BsiS9JXh.js"; //#region src/commands/doctor/shared/stale-plugin-config.ts const CHANNEL_CONFIG_META_KEYS = new Set(["defaults", "modelByChannel"]); function collectPluginRegistryState(cfg, env) { const registry = loadManifestMetadataSnapshot({ config: cfg, workspaceDir: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)) ?? void 0, env: env ?? process.env }).manifestRegistry; const knownIds = new Set(registry.plugins.map((plugin) => plugin.id)); const installedIds = /* @__PURE__ */ new Set(); for (const pluginId of Object.keys(cfg.plugins?.installs ?? {})) { const normalized = normalizePluginId(pluginId); if (normalized) installedIds.add(normalized); } try { for (const pluginId of Object.keys(loadInstalledPluginIndexInstallRecordsSync({ env }))) { const normalized = normalizePluginId(pluginId); if (normalized) installedIds.add(normalized); } } catch {} const knownChannelIds = new Set(CHANNEL_IDS.map((channelId) => normalizePluginId(channelId))); for (const plugin of registry.plugins) for (const channelId of plugin.channels) { const normalized = normalizePluginId(channelId); if (normalized) knownChannelIds.add(normalized); } return { knownIds, knownChannelIds, missingInstalledIds: new Set([...installedIds].filter((pluginId) => !knownIds.has(pluginId))), hasDiscoveryErrors: registry.diagnostics.some((diag) => diag.level === "error") }; } /** Return true when plugin discovery errors should pause stale-plugin auto-removal. */ function isStalePluginAutoRepairBlocked(cfg, env) { if (cfg.plugins?.enabled === false) return false; return collectPluginRegistryState(cfg, env).hasDiscoveryErrors; } /** Scan plugin/channel config surfaces for ids no longer present in manifests or installs. */ function scanStalePluginConfig(cfg, env) { if (cfg.plugins?.enabled === false) return []; return scanStalePluginConfigWithState(cfg, collectPluginRegistryState(cfg, env)); } function scanStalePluginConfigWithState(cfg, registryState) { const plugins = asObjectRecord(cfg.plugins); const { knownIds } = registryState; const hits = []; const staleEvidenceIds = new Set(registryState.missingInstalledIds); const allow = Array.isArray(plugins?.allow) ? plugins.allow : []; for (const rawPluginId of allow) { if (typeof rawPluginId !== "string") continue; const pluginId = normalizePluginId(rawPluginId); if (!pluginId || knownIds.has(pluginId) || registryState.knownChannelIds.has(pluginId)) continue; hits.push({ pluginId: rawPluginId, pathLabel: "plugins.allow", surface: "allow" }); staleEvidenceIds.add(pluginId); } const deny = Array.isArray(plugins?.deny) ? plugins.deny : []; for (const rawPluginId of deny) { if (typeof rawPluginId !== "string") continue; const pluginId = normalizePluginId(rawPluginId); if (!pluginId || knownIds.has(pluginId) || registryState.knownChannelIds.has(pluginId)) continue; hits.push({ pluginId: rawPluginId, pathLabel: "plugins.deny", surface: "deny" }); staleEvidenceIds.add(pluginId); } const entries = asObjectRecord(plugins?.entries); if (entries) for (const rawPluginId of Object.keys(entries)) { const pluginId = normalizePluginId(rawPluginId); if (!pluginId || knownIds.has(pluginId) || registryState.knownChannelIds.has(pluginId)) continue; if (pluginId === "codex" && shouldSuppressMissingCodexPluginDiagnostics(cfg)) continue; hits.push({ pluginId: rawPluginId, pathLabel: `plugins.entries.${rawPluginId}`, surface: "entries" }); staleEvidenceIds.add(pluginId); } const slots = asObjectRecord(plugins?.slots); if (slots) for (const slotKey of ["memory", "contextEngine"]) { const rawPluginId = slots[slotKey]; if (typeof rawPluginId !== "string") continue; const pluginId = normalizePluginId(rawPluginId); const defaultSlotId = defaultSlotIdForKey(slotKey); if (!pluginId || rawPluginId.trim().toLowerCase() === "none" || pluginId === normalizePluginId(defaultSlotId) || knownIds.has(pluginId)) continue; hits.push({ pluginId: rawPluginId, pathLabel: `plugins.slots.${slotKey}`, surface: "slot", slotKey }); } const staleChannelIds = collectDanglingChannelIds({ cfg, registryState, staleEvidenceIds }); for (const channelId of staleChannelIds) hits.push({ pluginId: channelId, pathLabel: `channels.${channelId}`, surface: "channel" }); for (const hit of collectDependentChannelConfigHits(cfg, staleChannelIds)) hits.push(hit); return hits; } function collectDanglingChannelIds(params) { const channels = asObjectRecord(params.cfg.channels); if (!channels) return []; const ids = []; const seen = /* @__PURE__ */ new Set(); for (const channelId of Object.keys(channels)) { if (CHANNEL_CONFIG_META_KEYS.has(channelId)) continue; const normalized = normalizePluginId(channelId); if (!normalized || params.registryState.knownChannelIds.has(normalized) || !params.staleEvidenceIds.has(normalized) || seen.has(normalized)) continue; seen.add(normalized); ids.push(channelId); } return ids; } function collectDependentChannelConfigHits(cfg, channelIds) { if (channelIds.length === 0) return []; const staleChannelIds = new Set(channelIds.map((channelId) => normalizePluginId(channelId))); const hits = []; const defaultTarget = cfg.agents?.defaults?.heartbeat?.target; if (typeof defaultTarget === "string" && staleChannelIds.has(normalizePluginId(defaultTarget))) hits.push({ pluginId: defaultTarget, pathLabel: "agents.defaults.heartbeat.target", surface: "heartbeat" }); const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : []; for (const [index, agent] of agents.entries()) { const target = agent?.heartbeat?.target; if (typeof target !== "string" || !staleChannelIds.has(normalizePluginId(target))) continue; hits.push({ pluginId: target, pathLabel: `agents.list.${index}.heartbeat.target`, surface: "heartbeat" }); } const modelByChannel = asObjectRecord(cfg.channels?.modelByChannel); if (modelByChannel) for (const [providerId, channelMap] of Object.entries(modelByChannel)) { const channels = asObjectRecord(channelMap); if (!channels) continue; for (const channelId of Object.keys(channels)) { if (!staleChannelIds.has(normalizePluginId(channelId))) continue; hits.push({ pluginId: channelId, pathLabel: `channels.modelByChannel.${providerId}.${channelId}`, surface: "modelByChannel" }); } } return hits; } function formatStalePluginHitWarning(hit) { if (hit.surface === "allow" || hit.surface === "deny" || hit.surface === "entries") return `- ${hit.pathLabel}: stale plugin reference "${hit.pluginId}" was found.`; if (hit.surface === "slot") return `- ${hit.pathLabel}: slot references missing plugin "${hit.pluginId}".`; if (hit.surface === "channel") return `- ${hit.pathLabel}: dangling channel config for missing plugin "${hit.pluginId}" was found.`; if (hit.surface === "heartbeat") return `- ${hit.pathLabel}: heartbeat target references missing channel plugin "${hit.pluginId}".`; return `- ${hit.pathLabel}: model override references missing channel plugin "${hit.pluginId}".`; } /** Format warnings for stale plugin config hits. */ function collectStalePluginConfigWarnings(params) { if (params.hits.length === 0) return []; const lines = params.hits.map((hit) => formatStalePluginHitWarning(hit)); if (params.autoRepairBlocked) lines.push(`- Auto-removal is paused because plugin discovery currently has errors. Fix plugin discovery first, then rerun "${params.doctorFixCommand}".`); else lines.push(`- Run "${params.doctorFixCommand}" to remove stale plugin ids and dangling channel references.`); return lines.map((line) => sanitizeForLog(line)); } /** Remove stale plugin ids and dangling channel references when discovery is healthy. */ function maybeRepairStalePluginConfig(cfg, env, params) { if (cfg.plugins?.enabled === false) return { config: cfg, changes: [] }; const registryState = collectPluginRegistryState(cfg, env); if (registryState.hasDiscoveryErrors) return { config: cfg, changes: [] }; const preservePluginIds = new Set([...params?.preservePluginIds ?? []].map((pluginId) => normalizePluginId(pluginId)).filter((pluginId) => Boolean(pluginId))); const hits = scanStalePluginConfigWithState(cfg, registryState).filter((hit) => !preservePluginIds.has(normalizePluginId(hit.pluginId))); if (hits.length === 0) return { config: cfg, changes: [] }; const next = structuredClone(cfg); const nextPlugins = asObjectRecord(next.plugins); const allowIds = hits.filter((hit) => hit.surface === "allow").map((hit) => hit.pluginId); if (allowIds.length > 0 && Array.isArray(nextPlugins?.allow)) { const staleAllowIds = new Set(allowIds.map((pluginId) => normalizePluginId(pluginId))); nextPlugins.allow = nextPlugins.allow.filter((pluginId) => typeof pluginId !== "string" || !staleAllowIds.has(normalizePluginId(pluginId))); } const denyIds = hits.filter((hit) => hit.surface === "deny").map((hit) => hit.pluginId); if (denyIds.length > 0 && Array.isArray(nextPlugins?.deny)) { const staleDenyIds = new Set(denyIds.map((pluginId) => normalizePluginId(pluginId))); nextPlugins.deny = nextPlugins.deny.filter((pluginId) => typeof pluginId !== "string" || !staleDenyIds.has(normalizePluginId(pluginId))); } const entryIds = hits.filter((hit) => hit.surface === "entries").map((hit) => hit.pluginId); if (entryIds.length > 0) { const entries = asObjectRecord(nextPlugins?.entries); if (entries) { const staleEntryIds = new Set(entryIds.map((pluginId) => normalizePluginId(pluginId))); for (const pluginId of Object.keys(entries)) if (staleEntryIds.has(normalizePluginId(pluginId))) delete entries[pluginId]; } } const slotHits = hits.filter((hit) => hit.surface === "slot" && hit.slotKey !== void 0); if (slotHits.length > 0) { const slots = asObjectRecord(nextPlugins?.slots); if (slots) for (const hit of slotHits) slots[hit.slotKey] = defaultSlotIdForKey(hit.slotKey); } const channelIds = hits.filter((hit) => hit.surface === "channel").map((hit) => hit.pluginId); if (channelIds.length > 0) removeDanglingChannelReferences(next, channelIds); const changes = []; if (allowIds.length > 0) changes.push(`- plugins.allow: removed ${allowIds.length} stale plugin id${allowIds.length === 1 ? "" : "s"} (${allowIds.join(", ")})`); if (denyIds.length > 0) changes.push(`- plugins.deny: removed ${denyIds.length} stale plugin id${denyIds.length === 1 ? "" : "s"} (${denyIds.join(", ")})`); if (entryIds.length > 0) changes.push(`- plugins.entries: removed ${entryIds.length} stale plugin entr${entryIds.length === 1 ? "y" : "ies"} (${entryIds.join(", ")})`); if (slotHits.length > 0) changes.push(`- plugins.slots: reset ${slotHits.length} stale plugin slot${slotHits.length === 1 ? "" : "s"} (${slotHits.map((hit) => `${hit.slotKey}: ${hit.pluginId} -> ${defaultSlotIdForKey(hit.slotKey)}`).join(", ")})`); if (channelIds.length > 0) { changes.push(`- channels: removed ${channelIds.length} stale channel config${channelIds.length === 1 ? "" : "s"} (${channelIds.join(", ")})`); const heartbeatCount = hits.filter((hit) => hit.surface === "heartbeat").length; if (heartbeatCount > 0) changes.push(`- agents heartbeat: removed ${heartbeatCount} stale heartbeat target${heartbeatCount === 1 ? "" : "s"} (${channelIds.join(", ")})`); const modelByChannelCount = hits.filter((hit) => hit.surface === "modelByChannel").length; if (modelByChannelCount > 0) changes.push(`- channels.modelByChannel: removed ${modelByChannelCount} stale channel model override${modelByChannelCount === 1 ? "" : "s"} (${channelIds.join(", ")})`); } return { config: next, changes }; } function removeDanglingChannelReferences(config, channelIds) { const staleChannelIds = new Set(channelIds.map((channelId) => normalizePluginId(channelId))); const channels = asObjectRecord(config.channels); if (channels) { for (const channelId of Object.keys(channels)) { if (CHANNEL_CONFIG_META_KEYS.has(channelId)) continue; if (staleChannelIds.has(normalizePluginId(channelId))) delete channels[channelId]; } const modelByChannel = asObjectRecord(channels.modelByChannel); if (modelByChannel) { for (const [providerId, channelMap] of Object.entries(modelByChannel)) { const channelsForProvider = asObjectRecord(channelMap); if (!channelsForProvider) continue; for (const channelId of Object.keys(channelsForProvider)) if (staleChannelIds.has(normalizePluginId(channelId))) delete channelsForProvider[channelId]; if (Object.keys(channelsForProvider).length === 0) delete modelByChannel[providerId]; } if (Object.keys(modelByChannel).length === 0) delete channels.modelByChannel; } } const defaultsHeartbeat = config.agents?.defaults?.heartbeat; if (defaultsHeartbeat && typeof defaultsHeartbeat.target === "string" && staleChannelIds.has(normalizePluginId(defaultsHeartbeat.target))) delete defaultsHeartbeat.target; const agents = Array.isArray(config.agents?.list) ? config.agents.list : []; for (const agent of agents) { const heartbeat = agent.heartbeat; if (heartbeat && typeof heartbeat.target === "string" && staleChannelIds.has(normalizePluginId(heartbeat.target))) delete heartbeat.target; } } //#endregion export { scanStalePluginConfig as i, isStalePluginAutoRepairBlocked as n, maybeRepairStalePluginConfig as r, collectStalePluginConfigWarnings as t };