UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

387 lines (386 loc) 9.66 kB
import { b as isPlainObject } from "./utils-CCC-BEJH.js"; import { c as getActivePluginRegistryVersion, n as getActivePluginChannelRegistryVersion, o as getActivePluginRegistry } from "./runtime-B2ROiq5l.js"; import { i as listChannelPlugins } from "./registry-MkxNn1Ue.js"; import "./plugins-t2ejWcVy.js"; import { isDeepStrictEqual } from "node:util"; //#region src/gateway/config-diff.ts /** Return dotted config paths whose values differ between two config snapshots. */ function diffConfigPaths(prev, next, prefix = "") { if (prev === next) return []; if (isPlainObject(prev) && isPlainObject(next)) { const keys = new Set([...Object.keys(prev), ...Object.keys(next)]); const paths = []; for (const key of keys) { const prevValue = prev[key]; const nextValue = next[key]; if (prevValue === void 0 && nextValue === void 0) continue; const childPaths = diffConfigPaths(prevValue, nextValue, prefix ? `${prefix}.${key}` : key); if (childPaths.length > 0) paths.push(...childPaths); } return paths; } if (Array.isArray(prev) && Array.isArray(next)) { if (isDeepStrictEqual(prev, next)) return []; } return [prefix || "<root>"]; } //#endregion //#region src/gateway/config-reload-plan.ts const PLUGIN_INSTALL_TIMESTAMP_KEYS = ["installedAt", "resolvedAt"]; const BASE_RELOAD_RULES = [ { prefix: "gateway.remote", kind: "none" }, { prefix: "gateway.reload", kind: "none" }, { prefix: "gateway.channelHealthCheckMinutes", kind: "hot", actions: ["restart-health-monitor"] }, { prefix: "gateway.channelStaleEventThresholdMinutes", kind: "hot", actions: ["restart-health-monitor"] }, { prefix: "gateway.channelMaxRestartsPerHour", kind: "hot", actions: ["restart-health-monitor"] }, { prefix: "diagnostics.stuckSessionWarnMs", kind: "none" }, { prefix: "diagnostics.stuckSessionAbortMs", kind: "none" }, { prefix: "diagnostics.memoryPressureSnapshot", kind: "hot" }, { prefix: "hooks.gmail", kind: "hot", actions: ["restart-gmail-watcher"] }, { prefix: "hooks", kind: "hot", actions: ["reload-hooks"] }, { prefix: "agents.defaults.heartbeat", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "agents.defaults.models", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "agents.defaults.model", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "models.pricing", kind: "restart" }, { prefix: "models", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "auth.cooldowns", kind: "hot" }, { prefix: "agents.list", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "agent.heartbeat", kind: "hot", actions: ["restart-heartbeat"] }, { prefix: "cron", kind: "hot", actions: ["restart-cron"] }, { prefix: "mcp", kind: "hot", actions: ["dispose-mcp-runtimes"] }, { prefix: "plugins.load", kind: "restart" }, { prefix: "plugins.installs", kind: "restart" } ]; const BASE_RELOAD_RULES_TAIL = [ { prefix: "meta", kind: "none" }, { prefix: "identity", kind: "none" }, { prefix: "wizard", kind: "none" }, { prefix: "logging", kind: "none" }, { prefix: "agents", kind: "none" }, { prefix: "tools", kind: "none" }, { prefix: "bindings", kind: "none" }, { prefix: "audio", kind: "none" }, { prefix: "agent", kind: "none" }, { prefix: "routing", kind: "none" }, { prefix: "messages", kind: "none" }, { prefix: "session", kind: "none" }, { prefix: "talk", kind: "none" }, { prefix: "skills", kind: "none" }, { prefix: "secrets", kind: "none" }, { prefix: "plugins", kind: "hot", actions: ["reload-plugins", "dispose-mcp-runtimes"] }, { prefix: "tui", kind: "none" }, { prefix: "ui", kind: "none" }, { prefix: "gateway", kind: "restart" }, { prefix: "discovery", kind: "restart" } ]; let cachedReloadRules = null; let cachedRegistry = null; let cachedActiveRegistryVersion = -1; let cachedChannelRegistryVersion = -1; function listReloadRules() { const registry = getActivePluginRegistry(); const activeRegistryVersion = getActivePluginRegistryVersion(); const channelRegistryVersion = getActivePluginChannelRegistryVersion(); if (registry !== cachedRegistry || activeRegistryVersion !== cachedActiveRegistryVersion || channelRegistryVersion !== cachedChannelRegistryVersion) { cachedReloadRules = null; cachedRegistry = registry; cachedActiveRegistryVersion = activeRegistryVersion; cachedChannelRegistryVersion = channelRegistryVersion; } if (cachedReloadRules) return cachedReloadRules; const channelReloadRules = listChannelPlugins().flatMap((plugin) => (plugin.reload?.configPrefixes ?? []).map((prefix) => ({ prefix, kind: "hot", actions: [`restart-channel:${plugin.id}`] })).concat((plugin.reload?.noopPrefixes ?? []).map((prefix) => ({ prefix, kind: "none" })))); const channelPluginStateRules = listChannelPlugins().flatMap((plugin) => [{ prefix: `plugins.entries.${plugin.id}`, kind: "hot", actions: [ "reload-plugins", "dispose-mcp-runtimes", `restart-channel:${plugin.id}` ] }]); const pluginReloadRules = (registry?.reloads ?? []).flatMap((entry) => (entry.registration.restartPrefixes ?? []).map((prefix) => ({ prefix, kind: "restart" })).concat((entry.registration.hotPrefixes ?? []).map((prefix) => ({ prefix, kind: "hot" })), (entry.registration.noopPrefixes ?? []).map((prefix) => ({ prefix, kind: "none" })))); const rules = [ ...BASE_RELOAD_RULES, ...pluginReloadRules, ...channelReloadRules, ...channelPluginStateRules, ...BASE_RELOAD_RULES_TAIL ]; cachedReloadRules = rules; return rules; } function matchRule(path) { for (const rule of listReloadRules()) if (path === rule.prefix || path.startsWith(`${rule.prefix}.`)) return rule; return null; } function resolveConfigReloadMetadata(path) { if (isPluginInstallTimestampPath(path)) return { kind: "none" }; return { kind: matchRule(path)?.kind ?? "restart" }; } function isPluginInstallTimestampPath(path) { return /^plugins\.installs\..+\.(installedAt|resolvedAt)$/.test(path); } function getPluginInstallRecords(config) { if (!isPlainObject(config)) return {}; const plugins = config.plugins; if (!isPlainObject(plugins)) return {}; const installs = plugins.installs; return isPlainObject(installs) ? installs : {}; } function listPluginInstallRecordDiffPaths(prevConfig, nextConfig, visit) { const prevInstalls = getPluginInstallRecords(prevConfig); const nextInstalls = getPluginInstallRecords(nextConfig); const ids = new Set([...Object.keys(prevInstalls), ...Object.keys(nextInstalls)]); const paths = []; for (const id of ids) visit({ id, prevRecord: prevInstalls[id], nextRecord: nextInstalls[id], paths }); return paths; } function listPluginInstallTimestampMetadataPaths(prevConfig, nextConfig) { return listPluginInstallRecordDiffPaths(prevConfig, nextConfig, ({ id, prevRecord, nextRecord, paths }) => { if (!isPlainObject(prevRecord) || !isPlainObject(nextRecord)) return; for (const key of PLUGIN_INSTALL_TIMESTAMP_KEYS) if (prevRecord[key] !== nextRecord[key]) paths.push(`plugins.installs.${id}.${key}`); }); } function listPluginInstallWholeRecordPaths(prevConfig, nextConfig) { return listPluginInstallRecordDiffPaths(prevConfig, nextConfig, ({ id, prevRecord, nextRecord, paths }) => { if (!isPlainObject(prevRecord) || !isPlainObject(nextRecord)) paths.push(`plugins.installs.${id}`); }); } function buildGatewayReloadPlan(changedPaths, options = {}) { const noopPaths = new Set(options.noopPaths); const forceChangedPaths = new Set(options.forceChangedPaths); const plan = { changedPaths, restartGateway: false, restartReasons: [], hotReasons: [], reloadHooks: false, restartGmailWatcher: false, restartCron: false, restartHeartbeat: false, restartHealthMonitor: false, reloadPlugins: false, restartChannels: /* @__PURE__ */ new Set(), disposeMcpRuntimes: false, noopPaths: [] }; const applyAction = (action) => { if (action.startsWith("restart-channel:")) { const channel = action.slice(16); plan.restartChannels.add(channel); return; } switch (action) { case "reload-hooks": plan.reloadHooks = true; break; case "restart-gmail-watcher": plan.restartGmailWatcher = true; break; case "restart-cron": plan.restartCron = true; break; case "restart-heartbeat": plan.restartHeartbeat = true; break; case "restart-health-monitor": plan.restartHealthMonitor = true; break; case "reload-plugins": plan.reloadPlugins = true; break; case "dispose-mcp-runtimes": plan.disposeMcpRuntimes = true; break; default: break; } }; for (const path of changedPaths) { if (!forceChangedPaths.has(path) && (noopPaths.size > 0 ? noopPaths.has(path) : isPluginInstallTimestampPath(path))) { plan.noopPaths.push(path); continue; } const rule = matchRule(path); if (!rule) { plan.restartGateway = true; plan.restartReasons.push(path); continue; } if (rule.kind === "restart") { plan.restartGateway = true; plan.restartReasons.push(path); continue; } if (rule.kind === "none") { plan.noopPaths.push(path); continue; } plan.hotReasons.push(path); for (const action of rule.actions ?? []) applyAction(action); } if (plan.restartGmailWatcher) plan.reloadHooks = true; return plan; } //#endregion export { diffConfigPaths as a, resolveConfigReloadMetadata as i, listPluginInstallTimestampMetadataPaths as n, listPluginInstallWholeRecordPaths as r, buildGatewayReloadPlan as t };