UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

402 lines (401 loc) 12.7 kB
import { t as isPlainObject } from "./plain-object-5a0EzLzX.js"; import "./utils-P__uGsPB.js"; import "./account-id-CETVCrTz.js"; import { t as canHotReloadGatewayAuthCredentials } from "./auth-resolve-O5AKX-sb.js"; import { c as getActivePluginHttpRouteRegistry, l as getActivePluginHttpRouteRegistryVersion } from "./runtime-BL4wZfTq.js"; import { i as listChannelPlugins } from "./registry-Cz6cv4VC.js"; import "./plugins-Cozj1Enf.js"; //#region src/gateway/config-reload-plan.ts const RELOAD_ACTIONS = [ "reloadHooks", "reloadInternalHooks", "refreshHooksPolicy", "restartGmailWatcher", "restartCron", "restartHeartbeat", "reconcileSystemJobs", "reloadPlugins", "disposeMcpRuntimes" ]; function isNoopGatewayReloadPlan(plan) { return !plan.restartGateway && plan.hotReasons.length === 0 && RELOAD_ACTIONS.every((action) => !plan[action]) && plan.restartChannels.size === 0 && (plan.restartServices?.size ?? 0) === 0 && (plan.restartChannelAccounts?.size ?? 0) === 0; } const PLUGIN_INSTALL_TIMESTAMP_KEYS = ["installedAt", "resolvedAt"]; const AUTH_CREDENTIAL_PATHS = ["gateway.auth.token", "gateway.auth.password"]; const SHARED_CHANNEL_PREFIXES = [ "agents.defaults.mediaMaxMb", "channels.defaults", "channels.modelByChannel", "messages.inbound", "messages.ackReactionScope", "commands", "accessGroups", "tts", "surfaces", "acp.stream", "diagnostics.flags" ]; function matchesReloadPrefix(path, prefix) { return path === prefix || path.startsWith(`${prefix}.`); } function expandReloadPolicies(policies) { return policies.flatMap(({ prefixes, ...policy }) => prefixes.map((prefix) => ({ ...policy, prefix }))).toSorted((a, b) => b.prefix.length - a.prefix.length); } const CORE_RELOAD_POLICIES = [ { prefixes: ["gateway.remote", "gateway.reload"], kind: "none" }, { prefixes: [ ...AUTH_CREDENTIAL_PATHS, "mcp.apps", "secrets.egressProxy", "plugins.load", "plugins.installs" ], kind: "restart" }, { prefixes: [ "gateway.http.endpoints", "gateway.http.securityHeaders.strictTransportSecurity", "gateway.tools", "gateway.cliAgents", "gateway.controlUi.enabled", "gateway.controlUi.environment", "gateway.controlUi.communityInvite", "gateway.controlUi.github", "gateway.controlUi.sessionObserver", "gateway.controlUi.embedSandbox", "gateway.controlUi.allowExternalEmbedUrls", "gateway.controlUi.automaticallyFetchFavicons", "gateway.controlUi.allowedOrigins", "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback", "gateway.nodes.browser", "gateway.nodes.pairing", "gateway.nodes.commands", "gateway.nodes.pluginTools.enabled", "gateway.nodes.allowSkills", "gateway.push.apns.relay", "gateway.terminal", "gateway.auth.rateLimit", "diagnostics.enabled", "discovery.mdns.mode", "mcp.apps.sandboxOrigin", "agents.defaults" ], kind: "hot" }, { prefixes: ["hooks.gmail"], kind: "hot", actions: ["restartGmailWatcher", "reloadHooks"] }, { prefixes: ["hooks.internal", "agents.defaults.workspace"], kind: "hot", actions: ["reloadInternalHooks"] }, { prefixes: ["hooks"], kind: "hot", actions: ["reloadHooks"] }, { prefixes: [ "agents.defaults.heartbeat", "agents.defaults.models", "agents.defaults.modelPolicy", "agents.defaults.model", "models", "agent.heartbeat" ], kind: "hot", actions: ["restartHeartbeat", "reconcileSystemJobs"] }, { prefixes: ["agents.entries"], kind: "hot", actions: [ "restartHeartbeat", "reconcileSystemJobs", "refreshHooksPolicy", "reloadInternalHooks" ] }, { prefixes: ["agents.defaults.sessionStore", "agents.ownership"], kind: "hot", actions: ["refreshHooksPolicy"] }, { prefixes: ["skills.workshop.autonomous.mode"], kind: "hot", actions: ["reconcileSystemJobs"] }, { prefixes: ["cron"], kind: "hot", actions: ["restartCron"] }, { prefixes: ["mcp", "gateway.publicOrigin"], kind: "hot", actions: ["disposeMcpRuntimes"] }, { prefixes: ["talk.provider", "talk.realtime.provider"], kind: "hot", actions: ["reloadPlugins"] } ]; const DEFAULT_RELOAD_POLICIES = [ { prefixes: [ "meta", "identity", "wizard", "logging", "agents", "bindings", "audio", "agent", "routing", "messages", "session", "talk", "skills", "secrets", "tui", "ui" ], kind: "none" }, { prefixes: [ "tools", "approvals.exec", "approvals.plugin", "auth.order", "auth.profiles", "broadcast", "memory.citations", "worktreeRoot", "cloudWorkers.projectProfiles", "security.audit.suppressions", "security.installPolicy", "diagnostics.cacheTrace.enabled", "acp.runtime.installCommand", "attachments.ttlHours", "update.checkOnStart", "update.channel", "update.auto.enabled", "telemetry.enabled", "telemetry.consentedAt" ], kind: "hot" }, { prefixes: ["plugins"], kind: "hot", actions: ["reloadPlugins", "disposeMcpRuntimes"] }, { prefixes: ["gateway", "discovery"], kind: "restart" } ]; let cachedCatalog; function getReloadPolicyCatalog() { const registry = getActivePluginHttpRouteRegistry(); const version = getActivePluginHttpRouteRegistryVersion(); if (cachedCatalog?.registry === registry && cachedCatalog.version === version) return cachedCatalog; const channelPlugins = listChannelPlugins(); const servicePolicies = (registry?.services ?? []).map(({ service }) => ({ prefixes: service.reload?.configPrefixes ?? [], services: [service.id] })); const channelPolicies = channelPlugins.flatMap((plugin) => [{ prefixes: plugin.reload?.configPrefixes ?? [], kind: "hot", channels: [plugin], accountScoped: plugin.reload?.accountScopedRestart }, { prefixes: plugin.reload?.noopPrefixes ?? [], kind: "none", channels: [plugin] }]); const channelRules = expandReloadPolicies(channelPolicies); const sharedPrefixes = /* @__PURE__ */ new Set([...SHARED_CHANNEL_PREFIXES, ...channelRules.filter(({ prefix }) => SHARED_CHANNEL_PREFIXES.some((root) => matchesReloadPrefix(prefix, root))).map(({ prefix }) => prefix)]); const ownedRules = expandReloadPolicies([ ...CORE_RELOAD_POLICIES, ...(registry?.reloads ?? []).flatMap(({ registration }) => [ ["restart", registration.restartPrefixes], ["hot", registration.hotPrefixes], ["none", registration.noopPrefixes] ].map(([kind, prefixes]) => ({ kind, prefixes: prefixes ?? [] }))), ...Array.from(sharedPrefixes, (prefix) => { const channels = channelPlugins.filter((plugin) => channelRules.find((rule) => rule.channels?.includes(plugin) && matchesReloadPrefix(prefix, rule.prefix))?.kind !== "none"); const hasService = servicePolicies.some(({ prefixes }) => prefixes.some((owner) => matchesReloadPrefix(prefix, owner))); return { prefixes: [prefix], kind: channels.length || hasService ? "hot" : "none", channels }; }), ...channelPolicies, ...channelPlugins.map((plugin) => ({ prefixes: [`plugins.entries.${plugin.id}`], kind: "hot", actions: ["reloadPlugins", "disposeMcpRuntimes"], channels: [plugin] })), { prefixes: ["session.scope", "session.store"], kind: "hot", actions: ["refreshHooksPolicy"] }, ...DEFAULT_RELOAD_POLICIES ]); const rules = [...ownedRules, ...servicePolicies.flatMap(({ prefixes }) => prefixes.map((prefix) => ({ ...ownedRules.find((owner) => matchesReloadPrefix(prefix, owner.prefix)), kind: "hot", prefix })))]; for (const rule of rules) rule.services = servicePolicies.filter((service) => service.prefixes.some((owner) => matchesReloadPrefix(rule.prefix, owner))).flatMap((service) => service.services); rules.sort((a, b) => b.prefix.length - a.prefix.length); cachedCatalog = { registry, version, rules, refinementPrefixes: rules.map((rule) => rule.prefix) }; return cachedCatalog; } function listConfigReloadRefinementPrefixes() { return getReloadPolicyCatalog().refinementPrefixes; } function matchRule(path) { return getReloadPolicyCatalog().rules.find(({ prefix }) => matchesReloadPrefix(path, prefix)); } 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 = /* @__PURE__ */ 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 extractAccountIdFromPath(channel, path) { const prefix = `channels.${channel}.accounts.`; const id = path.startsWith(prefix) ? path.slice(prefix.length).split(".", 1)[0] : void 0; return id && id !== "default" ? id : null; } function isResolvableChannelAccount(params) { try { if (!params.plugin.config.listAccountIds(params.config).includes(params.accountId)) return false; params.plugin.config.resolveAccount(params.config, params.accountId); return true; } catch { return false; } } function buildGatewayReloadPlan(changedPaths, options = {}) { const noopPaths = new Set(options.noopPaths); const forceChangedPaths = new Set(options.forceChangedPaths); const restartChannelAccounts = /* @__PURE__ */ new Map(); const plan = { changedPaths, restartGateway: false, restartReasons: [], hotReasons: [], reloadHooks: false, reloadInternalHooks: false, restartGmailWatcher: false, restartCron: false, restartHeartbeat: false, reconcileSystemJobs: false, reloadPlugins: false, restartChannels: /* @__PURE__ */ new Set(), restartServices: /* @__PURE__ */ new Set(), disposeMcpRuntimes: false, restartChannelAccounts, noopPaths: [] }; 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); const kind = rule?.kind ?? "restart"; const isCredentialRotation = rule && AUTH_CREDENTIAL_PATHS.includes(rule.prefix) && canHotReloadGatewayAuthCredentials(options.previousConfig, options.candidateConfig); if (kind === "restart" && !isCredentialRotation) { plan.restartGateway = true; plan.restartReasons.push(path); continue; } if (kind === "none") { plan.noopPaths.push(path); continue; } plan.hotReasons.push(path); for (const action of rule?.actions ?? []) plan[action] = true; for (const service of rule?.services ?? []) plan.restartServices?.add(service); for (const plugin of rule?.channels ?? []) { const accountId = rule?.accountScoped ? extractAccountIdFromPath(plugin.id, path) : null; if (accountId === null || options.candidateConfig && !isResolvableChannelAccount({ plugin, accountId, config: options.candidateConfig })) { plan.restartChannels.add(plugin.id); continue; } const accounts = restartChannelAccounts.get(plugin.id) ?? /* @__PURE__ */ new Set(); accounts.add(accountId); restartChannelAccounts.set(plugin.id, accounts); } } for (const channel of plan.restartChannels) restartChannelAccounts.delete(channel); return plan; } //#endregion export { listPluginInstallWholeRecordPaths as a, listPluginInstallTimestampMetadataPaths as i, isNoopGatewayReloadPlan as n, resolveConfigReloadMetadata as o, listConfigReloadRefinementPrefixes as r, buildGatewayReloadPlan as t };