UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

348 lines (347 loc) • 18 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs"; import { t as cloneConfigWithResolutionFacts } from "./resolution-facts-CYe39-eJ.mjs"; import { t as coerceConfig } from "./io.read-helpers-CK7CM5YG.mjs"; import { t as discoverConfiguredPluginLoadPaths, u as findUninspectedPluginDiagnostic } from "./discovery-TE84EnHS.mjs"; import { n as formatConfigIssueLines } from "./issue-format-IKggtHBm.mjs"; import { n as validateConfigObjectRawWithPlugins } from "./validation-Yn6JM19v.mjs"; import { t as inheritLegacyDefaultAgentId } from "./legacy.default-agent-owner-DlHTJ13Y.mjs"; import { t as isChannelConfigMetadataKey } from "./config-metadata-aX1D2IMg.mjs"; import { i as isPluginSourceModulePath, u as tryNativeRequireModule } from "./native-module-require-CnCGzFSR.mjs"; import { t as getCachedPluginModuleLoader } from "./plugin-module-loader-cache-C4XxNbkC.mjs"; import { o as preparePluginLoaderAliases } from "./sdk-alias-BQkrpnVJ.mjs"; import { n as getDeferredPluginMigrationConfigFacts, o as setDeferredPluginMigrationConfigFacts } from "./deferred-plugin-migration-config-BshpW6Yw.mjs"; import { t as getBootstrapChannelPlugin } from "./bootstrap-registry-jyv-1f91.mjs"; import { i as collectToolPolicyConflictWarnings, r as LEGACY_CONFIG_MIGRATIONS } from "./legacy-i4b5YUoh.mjs"; import "./legacy-config-migrations.runtime.models-Cyqxv8yi.mjs"; import { n as containsAuthoredInclude, t as classifyOtelGrpcMigrationOwnership } from "./include-migration-ownership-CmcqCsxa.mjs"; import { o as restoreEnvVarRefsFromResolved } from "./io.types-DdhXCrAp.mjs"; import { r as projectAuthoredAgentRosterForWrite } from "./io.write-prepare-GxejXvUu.mjs"; import { n as createMergePatch } from "./merge-patch-B0Q0c4qF.mjs"; import { o as migratePluginConfigId } from "./update-config-7Dttrlxz.mjs"; import { n as protectActiveAuthProfileConfig } from "./doctor-auth-profile-config-CMG5xQll.mjs"; import { d as stripUnknownConfigKeys } from "./doctor-config-analysis-BpgA0HOT.mjs"; import { t as loadBundledChannelDoctorContractApi } from "./doctor-contract-api-DOxjkG0J.mjs"; import { i as isPluginDoctorMigrationDeferred, m as applyPluginDoctorCompatibilityMigration, n as applyPluginDoctorCompatibilityMigrations, r as collectDoctorConfigRepairPluginIds } from "./doctor-contract-registry-BxFlpr_g.mjs"; import { t as listDoctorConfiguredChannelIds } from "./configured-channel-ids-DbzC7xIV.mjs"; import { t as resolveChannelAccountBindingRepairInput } from "./legacy-config-binding-repair-input-CZe5BL4p.mjs"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { isDeepStrictEqual } from "node:util"; //#region src/commands/doctor/shared/channel-legacy-config-migrate.ts const log = createSubsystemLogger("plugins/doctor-contracts"); function migrateHeartbeatVisibility(raw, changes) { const channels = isRecord(raw.channels) ? raw.channels : null; if (!channels) return; const migrateEntry = (entry, path, preserveEmptyPluginBlock = false) => { const heartbeat = isRecord(entry.heartbeat) ? entry.heartbeat : null; const keys = heartbeat ? Object.keys(heartbeat) : []; if (!heartbeat || preserveEmptyPluginBlock && keys.length === 0 || keys.some((key) => key !== "showOk" && key !== "showAlerts" && key !== "useIndicator")) return; if (entry.heartbeatVisibility === void 0) { entry.heartbeatVisibility = entry.heartbeat; changes.push(`Moved ${path}.heartbeat → ${path}.heartbeatVisibility.`); } else changes.push(`Removed ${path}.heartbeat (${path}.heartbeatVisibility already set).`); delete entry.heartbeat; }; const defaults = isRecord(channels.defaults) ? channels.defaults : null; if (defaults) migrateEntry(defaults, "channels.defaults"); for (const [channelId, value] of Object.entries(channels)) { if (!channelId.trim() || isChannelConfigMetadataKey(channelId) || !isRecord(value)) continue; const preserveEmptyPluginBlock = channelId === "feishu"; migrateEntry(value, `channels.${channelId}`, preserveEmptyPluginBlock); const accounts = isRecord(value.accounts) ? value.accounts : null; if (!accounts) continue; for (const [accountId, account] of Object.entries(accounts)) if (isRecord(account)) migrateEntry(account, `channels.${channelId}.accounts.${accountId}`, preserveEmptyPluginBlock); } } function resolveBundledChannelCompatibilityNormalizer(channelId) { if (isPluginDoctorMigrationDeferred(channelId)) return; const contractNormalizer = loadBundledChannelDoctorContractApi(channelId)?.normalizeCompatibilityConfig; if (typeof contractNormalizer === "function") return contractNormalizer; return getBootstrapChannelPlugin(channelId)?.doctor?.normalizeCompatibilityConfig; } function collectPluginDoctorCompatibilityIds(params) { return [.../* @__PURE__ */ new Set([...params.unresolvedChannelIds, ...collectDoctorConfigRepairPluginIds(params.raw)])].toSorted(); } /** Apply bundled and plugin channel compatibility migrations to a legacy config object. */ function applyChannelDoctorCompatibilityMigrations(cfg, options) { let nextCfg = cfg; const loadPaths = nextCfg.plugins?.load?.paths ?? []; if (loadPaths.length > 0) { const warning = findUninspectedPluginDiagnostic(discoverConfiguredPluginLoadPaths({ loadPaths }).diagnostics); if (warning) { log.warn(warning.message); return { next: cfg, changes: [] }; } } const changes = []; const warnings = []; migrateHeartbeatVisibility(cfg, changes); const unresolvedChannelIds = []; for (const channelId of listDoctorConfiguredChannelIds(cfg, { configEntryPolicy: "raw", sort: "codepoint" })) { const normalizeCompatibilityConfig = resolveBundledChannelCompatibilityNormalizer(channelId); if (!normalizeCompatibilityConfig) { unresolvedChannelIds.push(channelId); continue; } const mutation = applyPluginDoctorCompatibilityMigration({ pluginId: channelId, config: nextCfg, normalize: normalizeCompatibilityConfig }); nextCfg = mutation.config; changes.push(...mutation.changes); warnings.push(...mutation.warnings ?? []); } const pluginIds = options?.pluginContracts === false ? [] : collectPluginDoctorCompatibilityIds({ raw: cfg, unresolvedChannelIds }); if (pluginIds.length > 0) { const compat = applyPluginDoctorCompatibilityMigrations(nextCfg, { config: cfg, pluginIds }); nextCfg = compat.config; changes.push(...compat.changes); warnings.push(...compat.warnings ?? []); } return { next: nextCfg, changes, ...warnings.length ? { warnings } : {} }; } //#endregion //#region src/commands/doctor/shared/legacy-config-compat.ts const require = createRequire(import.meta.url); function loadBindingRepair() { const source = isPluginSourceModulePath(fileURLToPath(import.meta.url)); const modulePath = fileURLToPath(new URL(source ? "./legacy-config-binding-repair.runtime.ts" : "./legacy-config-binding-repair.runtime.js", import.meta.url)); const native = source ? tryNativeRequireModule(modulePath, { aliasMap: preparePluginLoaderAliases({ modulePath, moduleUrl: import.meta.url }).resolveAlias }) : void 0; return native?.ok ? native.moduleExport : source ? getCachedPluginModuleLoader({ modulePath, importerUrl: import.meta.url, tryNative: false })(modulePath) : require(modulePath); } /** Apply all legacy doctor migrations to raw config, returning null when nothing changed. */ function applyLegacyDoctorMigrations(raw, options) { if (!raw || typeof raw !== "object") return { next: null, changes: [] }; const original = raw; const next = cloneConfigWithResolutionFacts(original); const changes = []; for (const migration of LEGACY_CONFIG_MIGRATIONS) migration.apply(next, changes, options.context); const compat = applyChannelDoctorCompatibilityMigrations(next, { pluginContracts: options.pluginContracts !== false }); changes.push(...compat.changes); const ownership = options.pluginContracts !== false && resolveChannelAccountBindingRepairInput(compat.next) ? loadBindingRepair().repairUnownedChannelAccountBindings({ config: compat.next, sourceConfigBeforeMigrations: options.sourceConfigBeforeMigrations }) : { config: compat.next, changes: [] }; changes.push(...ownership.changes); const warnings = [ ...compat.warnings ?? [], ...ownership.warnings ?? [], ...collectToolPolicyConflictWarnings(ownership.config) ]; return { next: changes.length > 0 ? inheritLegacyDefaultAgentId(original, ownership.config) : null, changes, ...warnings.length ? { warnings } : {} }; } //#endregion //#region src/commands/doctor/shared/legacy-config-migrate.ts /** Apply legacy migrations and validate the resulting OpenClaw config shape when possible. */ function migrateLegacyConfig(raw, options) { const { context } = options; const { next, changes, warnings } = applyLegacyDoctorMigrations(raw, options); const diagnostics = { changes, ...warnings?.length ? { warnings } : {} }; if (!next) return { config: null, ...diagnostics }; const resolvedCandidate = context ? applyLegacyDoctorMigrations(context.resolvedRaw, options).next ?? context.resolvedRaw : next; const validated = validateConfigObjectRawWithPlugins(resolvedCandidate, { deferredPluginMigrations: getDeferredPluginMigrationConfigFacts(context?.resolvedRaw ?? raw) }); if (!validated.ok) { changes.push("Migration applied; other validation issues remain — run doctor to review."); return { config: next, ...diagnostics, partiallyValid: true }; } return { config: validated.config, sourceConfig: next, ...diagnostics }; } //#endregion //#region src/commands/doctor/shared/config-flow-steps.ts /** Apply legacy config migrations and update preview/fix state for doctor config flow. */ function applyLegacyCompatibilityStep(params) { if (params.snapshot.legacyIssues.length === 0) return { state: params.state, issueLines: [], changeLines: [] }; const issueLines = formatConfigIssueLines(params.snapshot.legacyIssues, "-"); const otelOwnership = classifyOtelGrpcMigrationOwnership({ snapshot: params.snapshot, authoredConfig: params.snapshot.parsed, resolvedConfig: params.snapshot.sourceConfig }); if (otelOwnership?.kind === "manual") { const otelPath = "diagnostics.otel.protocol"; const targets = otelOwnership.targetPaths.length > 0 ? ` Inspect these candidate source files and remove or replace ${otelPath} = "grpc" from every definition: ${otelOwnership.targetPaths.join(", ")}.` : ` Remove or replace ${otelPath} = "grpc" in the owning $include directive or included file.`; return { state: params.state, issueLines: [...issueLines, `- ${otelPath}: Doctor cannot safely rewrite this $include ownership.${targets} No config files were changed.`], changeLines: [], blocksWrite: true }; } const hasAuthoredIncludes = containsAuthoredInclude(params.snapshot.parsed); const { config: migrated, sourceConfig: migratedSource, changes, warnings, partiallyValid } = migrateLegacyConfig(params.snapshot.sourceConfig, { sourceConfigBeforeMigrations: params.snapshot.sourceConfigBeforeMigrations, context: { authoredRaw: params.snapshot.parsed, resolvedRaw: params.snapshot.sourceConfig } }); const migrationCandidate = migratedSource ?? migrated; const hasLegacyChanges = changes.length > 0 || !isDeepStrictEqual(params.snapshot.sourceConfigBeforeMigrations ?? (hasAuthoredIncludes ? params.snapshot.sourceConfig : params.snapshot.parsed), params.snapshot.sourceConfig); return { state: { ...params.state, ...migrationCandidate ? { cfg: migrationCandidate, candidate: migrationCandidate } : {}, pendingChanges: params.state.pendingChanges || hasLegacyChanges, fixHints: params.shouldRepair || !hasLegacyChanges ? params.state.fixHints : [...params.state.fixHints, `Run "${params.doctorFixCommand}" to ${partiallyValid ? "finish fixing" : "migrate"} legacy config keys.`] }, issueLines: [...issueLines, ...warnings ?? []], changeLines: changes, partiallyValid: partiallyValid === true ? true : void 0 }; } /** Strip unknown config keys while preserving active auth profile settings. */ function applyUnknownConfigKeyStep(params) { const unknown = stripUnknownConfigKeys(params.state.candidate); if (unknown.removed.length === 0) return { state: params.state, removed: [], repairs: [], warnings: [] }; const protectedAuth = protectActiveAuthProfileConfig({ before: params.state.candidate, after: unknown.config }); return { state: { cfg: params.shouldRepair ? protectedAuth.config : params.state.cfg, candidate: protectedAuth.config, pendingChanges: true, fixHints: params.shouldRepair ? params.state.fixHints : [...params.state.fixHints, `Run "${params.doctorFixCommand}" to remove these keys.`] }, removed: unknown.removed, repairs: protectedAuth.repairs, warnings: protectedAuth.warnings }; } /** Keep the matched planning read independent of later write receipts and environment changes. */ function prepareDoctorConfigReferenceSource(snapshot) { if (!snapshot.authoredConfig || !snapshot.sourceConfigBeforeMigrations) return; return { authored: structuredClone(snapshot.authoredConfig), resolved: cloneConfigWithResolutionFacts(snapshot.sourceConfigBeforeMigrations), parsed: structuredClone(snapshot.parsed) }; } /** A moved template must still have its original read-time value after migration. */ function retainValuePreservingMigrationRefs(template, migratedResolved, source) { const values = /* @__PURE__ */ new Map(); const ambiguous = /* @__PURE__ */ new Set(); const collect = (authored, resolved) => { if (typeof authored === "string" && /\$\{[A-Z_][A-Z0-9_]*\}/.test(authored)) { if (values.has(authored) && !isDeepStrictEqual(values.get(authored), resolved)) ambiguous.add(authored); values.set(authored, resolved); } else if (authored && typeof authored === "object") for (const [key, value] of Object.entries(authored)) collect(value, resolved && typeof resolved === "object" ? resolved[key] : void 0); }; collect(source.authored, source.resolved); const retain = (authored, resolved) => { if (typeof authored === "string" && /\$\{[A-Z_][A-Z0-9_]*\}/.test(authored)) return values.has(authored) && !ambiguous.has(authored) && isDeepStrictEqual(values.get(authored), resolved) ? authored : void 0; if (Array.isArray(authored)) return authored.map((value, index) => retain(value, Array.isArray(resolved) ? resolved[index] : void 0)); if (authored && typeof authored === "object") return Object.fromEntries(Object.entries(authored).map(([key, value]) => [key, retain(value, resolved && typeof resolved === "object" ? resolved[key] : void 0)])); return authored; }; return retain(template, migratedResolved); } /** Restore unchanged and moved references without substituting a later environment. */ function restoreDoctorConfigEnvRefs(candidate, source, explicitSetPaths, migrationOptions = {}) { if (!source) return candidate; const canonicalAuthored = projectAuthoredAgentRosterForWrite({ rootAuthoredConfig: source.authored, sourceConfigBeforeMigrations: source.resolved }); const canonicalResolved = projectAuthoredAgentRosterForWrite({ rootAuthoredConfig: source.resolved, sourceConfigBeforeMigrations: source.resolved }); const unchanged = restoreEnvVarRefsFromResolved(candidate, canonicalAuthored, canonicalResolved, explicitSetPaths); const options = { sourceConfigBeforeMigrations: source.resolved, context: { authoredRaw: source.parsed, resolvedRaw: source.resolved } }; const migratedAuthored = applyLegacyDoctorMigrations(canonicalAuthored, options); const migratedResolved = applyLegacyDoctorMigrations(canonicalResolved, options); const authoredView = migratedAuthored.next ?? canonicalAuthored; const resolvedView = migratedResolved.next ?? canonicalResolved; if (!isRecord(authoredView) || !isRecord(resolvedView)) throw new Error("Doctor reference migrations must preserve config object roots."); const migratedAuthoredConfig = coerceConfig(authoredView); const migratedResolvedConfig = coerceConfig(resolvedView); const referenceTemplate = createMergePatch(canonicalAuthored, migratedAuthoredConfig); const resolvedTemplate = createMergePatch(canonicalResolved, migratedResolvedConfig); const restored = restoreEnvVarRefsFromResolved(unchanged, retainValuePreservingMigrationRefs(referenceTemplate, resolvedTemplate, source), resolvedTemplate, explicitSetPaths); let movedAuthored = migratedAuthoredConfig; let movedResolved = migratedResolvedConfig; const pluginIdMigrations = new Map(Object.entries(migrationOptions.appliedPluginIdMigrations ?? {})); for (const [legacyId, owner] of source.installedPluginIdRecovery ?? []) pluginIdMigrations.set(legacyId, owner.pluginId); for (const [legacyId, pluginId] of pluginIdMigrations) { movedAuthored = migratePluginConfigId(movedAuthored, legacyId, pluginId); movedResolved = migratePluginConfigId(movedResolved, legacyId, pluginId); } const pluginReferences = createMergePatch(migratedAuthoredConfig.plugins?.entries ?? {}, movedAuthored.plugins?.entries ?? {}); const pluginValues = createMergePatch(migratedResolvedConfig.plugins?.entries ?? {}, movedResolved.plugins?.entries ?? {}); const recovered = restoreEnvVarRefsFromResolved(restored, { plugins: { entries: pluginReferences } }, { plugins: { entries: pluginValues } }, explicitSetPaths); if (!isRecord(recovered)) throw new Error("Doctor reference restoration must preserve the config object root."); const recoveredConfig = coerceConfig(recovered); setDeferredPluginMigrationConfigFacts(recoveredConfig, getDeferredPluginMigrationConfigFacts(candidate)); return recoveredConfig; } //#endregion export { migrateLegacyConfig as a, restoreDoctorConfigEnvRefs as i, applyUnknownConfigKeyStep as n, applyLegacyDoctorMigrations as o, prepareDoctorConfigReferenceSource as r, applyChannelDoctorCompatibilityMigrations as s, applyLegacyCompatibilityStep as t };