UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

305 lines (304 loc) 16.8 kB
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js"; import { i as isNixMode } from "./paths-mvMm5bYV.js"; import { l as measureDiagnosticsTimelineSpan } from "./plugin-metadata-snapshot-Ctk9Oarq.js"; import "./auth-DXSWhsnk.js"; import { n as resolveGatewayAuth } from "./auth-resolve-BSKl_nHz.js"; import { A as applyConfigOverrides, f as readConfigFileSnapshotWithPluginMetadata } from "./io-Gi7-pyU-.js"; import { n as formatConfigIssueLines } from "./issue-format-CwCoGNbt.js"; import { n as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-Dve6SNpn.js"; import { n as formatPluginPackagingRuntimeOutputRecoveryHint, t as formatInvalidConfigRecoveryHint } from "./config-recovery-hints-DCnttk3l.js"; import { t as applyPluginAutoEnable } from "./plugin-auto-enable-pAtqig-B.js"; import { o as getActiveSecretsRuntimeSnapshot, s as getLiveSecretsRuntimeAuthStores, t as activateSecretsRuntimeSnapshotState, u as setPreparedSecretsRuntimeSnapshotRefreshContext } from "./runtime-state-B3MH_XLp.js"; import { n as evaluateGatewayAuthSurfaceStates, t as GATEWAY_AUTH_SURFACE_PATHS } from "./runtime-gateway-auth-surfaces-B6IqufiU.js"; import { i as assertGatewayAuthNotKnownWeak, n as mergeGatewayAuthConfig, r as mergeGatewayTailscaleConfig, t as ensureGatewayStartupAuth } from "./startup-auth-BtAuEBV-.js"; import { a as prepareSecretsRuntimeFastPathSnapshot, o as resolveRefreshAgentDirs } from "./runtime-fast-path-DMpi5I8r.js"; import { isDeepStrictEqual } from "node:util"; //#region src/gateway/server-startup-config.ts /** Timeline attributes kept small and deterministic for startup secret preparation spans. */ function secretsPrepareTimelineAttributes(config, activationParams) { return { activate: activationParams.activate, gatewayAuthSecretRef: hasActiveGatewayAuthSecretRef(config), reason: activationParams.reason }; } /** Load and validate the config snapshot, applying runtime-only plugin auto-enable changes. */ async function loadGatewayStartupConfigSnapshot(params) { const measure = params.measure ?? (async (_name, run) => await run()); const snapshotRead = params.initialSnapshotRead ?? await measure("config.snapshot.read", () => readConfigFileSnapshotWithPluginMetadata({ measure })); const configSnapshot = snapshotRead.snapshot; const pluginMetadataSnapshot = snapshotRead.pluginMetadataSnapshot; const wroteConfig = false; if (configSnapshot.legacyIssues.length > 0 && isNixMode) throw new Error("Legacy config entries detected while running in Nix mode. Update your Nix config to the latest schema and restart."); if (configSnapshot.exists) assertValidGatewayStartupConfigSnapshot(configSnapshot, { includeDoctorHint: true }); const autoEnable = params.minimalTestGateway ? { config: configSnapshot.config, changes: [] } : await measure("config.snapshot.auto-enable", () => applyPluginAutoEnable({ config: configSnapshot.sourceConfig, env: process.env, ...pluginMetadataSnapshot?.manifestRegistry ? { manifestRegistry: pluginMetadataSnapshot.manifestRegistry } : {}, discovery: pluginMetadataSnapshot?.discovery })); if (autoEnable.changes.length === 0) return { snapshot: configSnapshot, wroteConfig, ...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {} }; params.log.info(`gateway: auto-enabled plugins for this runtime without writing config:\n${autoEnable.changes.map((entry) => `- ${entry}`).join("\n")}`); return { snapshot: withRuntimeConfig(configSnapshot, autoEnable.config), wroteConfig, ...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {} }; } function withRuntimeConfig(snapshot, runtimeConfig) { return { ...snapshot, runtimeConfig, config: runtimeConfig }; } /** Create the serialized secrets activation function used by startup and reload paths. */ function createRuntimeSecretsActivator(params) { let secretsDegraded = false; let secretsActivationTail = Promise.resolve(); let secretsRuntimePromise = null; let authProfilesPromise = null; const startupManifestRegistry = params.manifestRegistry ?? params.pluginMetadataSnapshot?.manifestRegistry; const loadSecretsRuntime = () => { secretsRuntimePromise ??= import("./runtime-BzZlvYH7.js"); return secretsRuntimePromise; }; const loadAuthProfiles = () => { authProfilesPromise ??= import("./auth-profiles-CRytMogm.js"); return authProfilesPromise; }; const runWithSecretsActivationLock = async (operation) => { const run = secretsActivationTail.then(operation, operation); secretsActivationTail = run.then(() => void 0, () => void 0); return await run; }; const loadActivateRuntimeSecretsSnapshot = async () => { if (params.activateRuntimeSecretsSnapshot) return params.activateRuntimeSecretsSnapshot; return (await loadSecretsRuntime()).activateSecretsRuntimeSnapshot; }; const finishPreparedSnapshot = async (prepared, activationParams, options) => { assertRuntimeGatewayAuthNotKnownWeak(prepared.config); if (activationParams.activate) { (options?.activateRuntimeSecretsSnapshot ?? await loadActivateRuntimeSecretsSnapshot())(prepared); logGatewayAuthSurfaceDiagnostics(prepared, params.logSecrets); } for (const warning of prepared.warnings) params.logSecrets.warn(`[${warning.code}] ${warning.message}`); if (secretsDegraded) { const recoveredMessage = "Secret resolution recovered; runtime remained on last-known-good during the outage."; params.logSecrets.info(`[SECRETS_RELOADER_RECOVERED] ${recoveredMessage}`); params.emitStateEvent("SECRETS_RELOADER_RECOVERED", recoveredMessage, prepared.config); } secretsDegraded = false; return prepared; }; const handleSecretsActivationError = (err, activationParams, eventConfig) => { const details = String(err); if (!secretsDegraded) { params.logSecrets.error?.(`[SECRETS_RELOADER_DEGRADED] ${details}`); if (activationParams.reason !== "startup") params.emitStateEvent("SECRETS_RELOADER_DEGRADED", `Secret resolution failed; runtime remains on last-known-good snapshot. ${details}`, eventConfig); } else params.logSecrets.warn(`[SECRETS_RELOADER_DEGRADED] ${details}`); secretsDegraded = true; if (activationParams.reason === "startup") throw new Error(`Startup failed: required secrets are unavailable. ${details}`, { cause: err }); throw err; }; const activateRuntimeSecrets = (async (config, activationParams) => await runWithSecretsActivationLock(async () => { try { const startupPreflight = activationParams.reason === "startup" || activationParams.reason === "restart-check"; if (activationParams.reason === "startup" && activationParams.activate && !params.prepareRuntimeSecretsSnapshot && !params.activateRuntimeSecretsSnapshot) { const fastPath = prepareSecretsRuntimeFastPathSnapshot({ config: pruneSkippedStartupSecretSurfaces(config), ...startupManifestRegistry ? { manifestRegistry: startupManifestRegistry } : {} }); if (fastPath) { const coercePreflightSnapshot = (value, sourceConfig) => { if (!value || typeof value !== "object") return null; const candidate = value; return isDeepStrictEqual(candidate.sourceConfig, sourceConfig) ? candidate : null; }; const prepareFastPathRuntimeSnapshot = async (secretsRuntime, sourceConfig, includeAuthStoreRefs) => await secretsRuntime.prepareSecretsRuntimeSnapshot({ config: sourceConfig, env: fastPath.refreshContext.env, agentDirs: resolveRefreshAgentDirs(sourceConfig, fastPath.refreshContext), includeAuthStoreRefs: includeAuthStoreRefs ?? fastPath.refreshContext.includeAuthStoreRefs, loadablePluginOrigins: fastPath.refreshContext.loadablePluginOrigins, ...fastPath.refreshContext.manifestRegistry ? { manifestRegistry: fastPath.refreshContext.manifestRegistry } : {}, ...fastPath.usesAuthStoreFallback || !fastPath.refreshContext.loadAuthStore ? {} : { loadAuthStore: fastPath.refreshContext.loadAuthStore } }); return await finishPreparedSnapshot(fastPath.snapshot, activationParams, { activateRuntimeSecretsSnapshot: (snapshot) => activateSecretsRuntimeSnapshotState({ snapshot, refreshContext: fastPath.refreshContext, refreshHandler: { preflight: async ({ sourceConfig, includeAuthStoreRefs }) => { const secretsRuntime = await loadSecretsRuntime(); if (!getActiveSecretsRuntimeSnapshot()) return false; return await prepareFastPathRuntimeSnapshot(secretsRuntime, sourceConfig, includeAuthStoreRefs); }, refresh: async ({ sourceConfig, includeAuthStoreRefs, preflightResult }) => { const secretsRuntime = await loadSecretsRuntime(); const activeSnapshot = getActiveSecretsRuntimeSnapshot(); const oneShotSkipAuthStoreRefs = includeAuthStoreRefs === false && fastPath.refreshContext.includeAuthStoreRefs; const refreshed = coercePreflightSnapshot(preflightResult, sourceConfig) ?? await prepareFastPathRuntimeSnapshot(secretsRuntime, sourceConfig, includeAuthStoreRefs); if (oneShotSkipAuthStoreRefs && activeSnapshot) { refreshed.authStores = getLiveSecretsRuntimeAuthStores(); setPreparedSecretsRuntimeSnapshotRefreshContext(refreshed, fastPath.refreshContext); } secretsRuntime.activateSecretsRuntimeSnapshot(refreshed); return true; } } }) }); } } const loadAuthStore = startupPreflight ? (await loadAuthProfiles()).loadAuthProfileStoreWithoutExternalProfiles : void 0; const secretsRuntime = params.prepareRuntimeSecretsSnapshot && params.activateRuntimeSecretsSnapshot ? null : await loadSecretsRuntime(); const prepareRuntimeSecretsSnapshot = params.prepareRuntimeSecretsSnapshot ?? secretsRuntime.prepareSecretsRuntimeSnapshot; return await finishPreparedSnapshot(await measureDiagnosticsTimelineSpan("secrets.prepare", () => prepareRuntimeSecretsSnapshot({ config: pruneSkippedStartupSecretSurfaces(config), ...startupManifestRegistry ? { manifestRegistry: startupManifestRegistry } : {}, ...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {}, ...loadAuthStore ? { loadAuthStore } : {} }), { attributes: secretsPrepareTimelineAttributes(config, activationParams), config, env: process.env, omitErrorMessage: true, phase: activationParams.reason }), activationParams); } catch (err) { return handleSecretsActivationError(err, activationParams, config); } })); activateRuntimeSecrets.activatePreparedSnapshot = async (snapshot, activationParams) => await runWithSecretsActivationLock(async () => { try { return await finishPreparedSnapshot(snapshot, activationParams); } catch (err) { return handleSecretsActivationError(err, activationParams, snapshot.sourceConfig); } }); return activateRuntimeSecrets; } /** Throw a formatted startup error when the loaded config snapshot is invalid. */ function assertValidGatewayStartupConfigSnapshot(snapshot, options = {}) { if (snapshot.valid) return; const issues = snapshot.issues.length > 0 ? formatConfigIssueLines(snapshot.issues, "", { normalizeRoot: true }).join("\n") : "Unknown validation issue."; const recoveryHint = options.includeDoctorHint && isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? `\n${formatPluginPackagingRuntimeOutputRecoveryHint()}` : options.includeDoctorHint ? `\n${formatInvalidConfigRecoveryHint()}` : ""; throw new Error(`Invalid config at ${snapshot.path}.\n${issues}${recoveryHint}`); } /** Prepare the effective Gateway startup config after auth, overrides, and secrets activation. */ async function prepareGatewayStartupConfig(params) { const measure = params.measure ?? (async (_name, run) => await run()); await measure("config.auth.snapshot-validate", () => assertValidGatewayStartupConfigSnapshot(params.configSnapshot)); const runtimeConfig = await measure("config.auth.runtime-overrides", () => applyConfigOverrides(params.configSnapshot.config)); const startupPreflightConfig = await measure("config.auth.startup-overrides", () => applyGatewayAuthOverridesForStartupPreflight(runtimeConfig, { auth: params.authOverride, tailscale: params.tailscaleOverride })); const needsAuthSecretPreflight = await measure("config.auth.secret-surface", () => hasActiveGatewayAuthSecretRef(startupPreflightConfig)); let preflightPrepared; const preflightConfig = await measure("config.auth.secret-preflight", async () => { if (!needsAuthSecretPreflight) return startupPreflightConfig; preflightPrepared = await params.activateRuntimeSecrets(startupPreflightConfig, { reason: "startup", activate: false }); return preflightPrepared.config; }, { omitErrorMessage: true }); const canReusePreflightPreparedSnapshot = (config) => Boolean(preflightPrepared && params.activateRuntimeSecrets.activatePreparedSnapshot && isDeepStrictEqual(pruneSkippedStartupSecretSurfaces(config), preflightPrepared.sourceConfig)); const activateStartupSecrets = async (config) => { if (preflightPrepared && canReusePreflightPreparedSnapshot(config)) return await params.activateRuntimeSecrets.activatePreparedSnapshot(preflightPrepared, { reason: "startup", activate: true }); return await params.activateRuntimeSecrets(config, { reason: "startup", activate: true }); }; const preflightAuthOverride = await measure("config.auth.preflight-override", () => typeof preflightConfig.gateway?.auth?.token === "string" || typeof preflightConfig.gateway?.auth?.password === "string" ? { ...params.authOverride, ...typeof preflightConfig.gateway?.auth?.token === "string" ? { token: preflightConfig.gateway.auth.token } : {}, ...typeof preflightConfig.gateway?.auth?.password === "string" ? { password: preflightConfig.gateway.auth.password } : {} } : params.authOverride); const authBootstrap = await measure("config.auth.ensure", () => ensureGatewayStartupAuth({ cfg: runtimeConfig, env: process.env, authOverride: preflightAuthOverride, tailscaleOverride: params.tailscaleOverride, warn: params.log?.warn, persist: params.persistStartupAuth ?? false, baseHash: params.configSnapshot.hash })); const runtimeStartupConfig = await measure("config.auth.runtime-startup-overrides", () => applyGatewayAuthOverridesForStartupPreflight(authBootstrap.cfg, { auth: params.authOverride, tailscale: params.tailscaleOverride })); const activatedConfig = (await measure("config.auth.secrets-activate", () => activateStartupSecrets(runtimeStartupConfig), { omitErrorMessage: true })).config; return { ...authBootstrap, cfg: activatedConfig }; } function hasActiveGatewayAuthSecretRef(config) { const states = evaluateGatewayAuthSurfaceStates({ config, defaults: config.secrets?.defaults, env: process.env }); return GATEWAY_AUTH_SURFACE_PATHS.some((path) => { const state = states[path]; return state.hasSecretRef && state.active; }); } function pruneSkippedStartupSecretSurfaces(config) { if (!(isTruthyEnvValue(process.env.OPENCLAW_SKIP_CHANNELS) || isTruthyEnvValue(process.env.OPENCLAW_SKIP_PROVIDERS)) || !config.channels) return config; return { ...config, channels: void 0 }; } function assertRuntimeGatewayAuthNotKnownWeak(config) { assertGatewayAuthNotKnownWeak(resolveGatewayAuth({ authConfig: config.gateway?.auth, env: process.env, tailscaleMode: config.gateway?.tailscale?.mode ?? "off" })); } function logGatewayAuthSurfaceDiagnostics(prepared, logSecrets) { const states = evaluateGatewayAuthSurfaceStates({ config: prepared.sourceConfig, defaults: prepared.sourceConfig.secrets?.defaults, env: process.env }); const inactiveWarnings = /* @__PURE__ */ new Map(); for (const warning of prepared.warnings) { if (warning.code !== "SECRETS_REF_IGNORED_INACTIVE_SURFACE") continue; inactiveWarnings.set(warning.path, warning.message); } for (const path of GATEWAY_AUTH_SURFACE_PATHS) { const state = states[path]; if (!state.hasSecretRef) continue; const stateLabel = state.active ? "active" : "inactive"; const details = (!state.active && inactiveWarnings.get(path) ? inactiveWarnings.get(path) : void 0) ?? state.reason; logSecrets.info(`[SECRETS_GATEWAY_AUTH_SURFACE] ${path} is ${stateLabel}. ${details}`); } } function applyGatewayAuthOverridesForStartupPreflight(config, overrides) { if (!overrides.auth && !overrides.tailscale) return config; return { ...config, gateway: { ...config.gateway, auth: mergeGatewayAuthConfig(config.gateway?.auth, overrides.auth), tailscale: mergeGatewayTailscaleConfig(config.gateway?.tailscale, overrides.tailscale) } }; } //#endregion export { assertValidGatewayStartupConfigSnapshot, createRuntimeSecretsActivator, loadGatewayStartupConfigSnapshot, prepareGatewayStartupConfig };