UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

319 lines (318 loc) • 14.1 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { b as uniqueValues } from "./string-normalization-YMwDjufr.mjs"; import { r as defaultRuntime } from "./runtime-BC29JSZp.mjs"; import { t as exitCliAfterOutput } from "./one-shot-exit-CAsLf7Cw.mjs"; import { p as shortenHomePath } from "./utils-DUAwk2p5.mjs"; import { _ as secretRefKey, c as isSecretRef, o as formatExecSecretRefIdValidationMessage, u as isValidExecSecretRefId } from "./ref-contract-D0kWy5jy.mjs"; import { a as coerceSecretRef, p as resolveSecretInputRef } from "./types.secrets-BXEuin38.mjs"; import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs"; import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs"; import { i as collectUnsupportedSecretRefPolicyIssues } from "./validation-core-CFeB-M2c.mjs"; import { n as formatConfigIssueLines } from "./issue-format-IKggtHBm.mjs"; import { a as isConfigReadFailure, t as configFailureHeading } from "./io.invalid-config-DkKUeCUl.mjs"; import { o as loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot-D_ni58BI.mjs"; import { n as validateConfigObjectRawWithPlugins } from "./validation-Yn6JM19v.mjs"; import { t as assertDeferredPluginMigrationConfigEditAllowed } from "./deferred-plugin-migration-config-BshpW6Yw.mjs"; import { t as dedupeByKey } from "./dedupe-by-key-DnqEVrst.mjs"; import { n as discoverConfigSecretTargets } from "./target-registry-query-C2DhrDiw.mjs"; import "./target-registry-CvjdRZuO.mjs"; import { r as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-D5E-mA-L.mjs"; import { u as readConfigFileSnapshotForWrite } from "./io.runtime-FunTy0y1.mjs"; import { n as visitConfigValueTree } from "./value-tree-3oLf9xok.mjs"; import "./config-B-SAEPyK.mjs"; import { i as assertSecureExecCommandPath, r as resolveSecretProviderIntegrationConfig, t as isPluginIntegrationSecretProviderConfig } from "./provider-integrations-aGvb-1Ve.mjs"; import { i as getAtPath } from "./config-cli-path-D4WgN8Of.mjs"; import { n as formatPluginPackagingRuntimeOutputRecoveryHint } from "./config-recovery-hints-BpoTGCnY.mjs"; import { t as renderConfigValidationIssueLines } from "./issue-location-CQRu3JlV.mjs"; import { t as writeInvalidConfigCliJson } from "./config-validation-output-BnoXyhei.mjs"; //#region src/cli/config-cli-validation.ts function formatInvalidConfigRepairHint(snapshot, doctorMessage) { return isConfigReadFailure(snapshot) ? "Resolve the read error shown above, then retry." : isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? formatPluginPackagingRuntimeOutputRecoveryHint() : `Run \`${formatCliCommand("openclaw doctor --fix")}\` ${doctorMessage}`; } function ensureValidConfigSnapshotForCli(snapshot, runtime, options = {}) { if (snapshot.valid) return; if (options.json) { writeInvalidConfigCliJson(runtime, snapshot); exitCliAfterOutput(runtime, 1); } runtime.error(`${configFailureHeading(snapshot)}: ${shortenHomePath(snapshot.path)}`); for (const line of renderConfigValidationIssueLines(snapshot)) runtime.error(line); runtime.error(formatInvalidConfigRepairHint(snapshot, "to repair, then retry.")); exitCliAfterOutput(runtime, 1); } async function loadValidConfigForWrite(runtime = defaultRuntime) { const prepared = await readConfigFileSnapshotForWrite(); ensureValidConfigSnapshotForCli(prepared.snapshot, runtime); return prepared; } async function finishConfigValidationForCli(read) { const { snapshot, strictIssues } = read; if (!snapshot.valid || !snapshot.exists) return snapshot; if (!strictIssues) throw new Error("Config validation requires its prepared source result."); const issues = strictIssues.length === 0 ? await collectConfigSecretProviderErrors({ config: snapshot.runtimeConfig }) : strictIssues; return issues.length === 0 ? snapshot : { ...snapshot, valid: false, issues }; } function pathContains(parent, child) { return parent.length <= child.length && parent.every((part, index) => part === child[index]); } function selectConfigMutationSecrets(config, operations) { const paths = operations.map(({ setPath }) => setPath); const changedProviders = /* @__PURE__ */ new Set(); const changedDefaults = /* @__PURE__ */ new Set(); let allProviders = false; for (const path of paths) { if (path[0] !== "secrets") continue; if (path.length === 1 || path[1] === "providers") { const alias = path[2]; if (alias === void 0) allProviders = true; else changedProviders.add(alias); } if (path.length === 1 || path[1] === "defaults") changedDefaults.add(path[2] ?? "*"); } const refsByKey = /* @__PURE__ */ new Map(); const record = (ref) => refsByKey.set(secretRefKey(ref), ref); const defaults = config.secrets?.defaults; const ownedPaths = []; const overlaps = (targetPath) => paths.some((path) => pathContains(path, targetPath) || pathContains(targetPath, path)); for (const target of discoverConfigSecretTargets(config)) { ownedPaths.push(target.pathSegments); if (target.refPathSegments) ownedPaths.push(target.refPathSegments); const { explicitRef, ref } = resolveSecretInputRef({ value: target.value, refValue: target.refValue, defaults }); if (!ref) continue; const usesDefault = !isSecretRef(explicitRef ? target.refValue : target.value); if (allProviders || changedProviders.has(ref.provider) || overlaps(target.pathSegments) || target.refPathSegments && overlaps(target.refPathSegments) || usesDefault && (changedDefaults.has("*") || changedDefaults.has(ref.source))) record(ref); } const visit = (value, rootPath) => { visitConfigValueTree(value, (candidate, path) => { if (ownedPaths.some((ownedPath) => pathContains(ownedPath, path))) return false; const ref = coerceSecretRef(candidate, defaults); if (ref) { record(ref); return false; } return true; }, rootPath); }; for (const path of paths) visit(getAtPath(config, path).value, path); const refs = [...refsByKey.values()]; return { refs, providerAliases: allProviders ? void 0 : /* @__PURE__ */ new Set([...changedProviders, ...refs.map((ref) => ref.provider)]) }; } async function collectDryRunResolvabilityErrors(params) { const { resolveSecretRefValue } = await import("./resolve-DC0wplC4.mjs"); const failures = []; for (const ref of params.refs) try { await resolveSecretRefValue(ref, { config: params.config, env: process.env }); } catch (err) { failures.push({ kind: "resolvability", message: formatErrorMessage(err), ref: `${ref.source}:${ref.provider}:${ref.id}` }); } return failures; } function collectDryRunStaticErrorsForSkippedExecRefs(params) { const failures = []; for (const ref of params.refs) { const id = ref.id.trim(); const refLabel = `${ref.source}:${ref.provider}:${id}`; if (!id) { failures.push({ kind: "resolvability", message: "Error: Secret reference id is empty.", ref: refLabel }); continue; } if (!isValidExecSecretRefId(id)) { failures.push({ kind: "resolvability", message: `Error: ${formatExecSecretRefIdValidationMessage()} (ref: ${refLabel}).`, ref: refLabel }); continue; } const providerConfig = params.config.secrets?.providers?.[ref.provider]; if (!providerConfig) { failures.push({ kind: "resolvability", message: `Error: Secret provider "${ref.provider}" is not configured (ref: ${refLabel}).`, ref: refLabel }); continue; } if (providerConfig.source !== ref.source) failures.push({ kind: "resolvability", message: `Error: Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "${ref.source}".`, ref: refLabel }); } return failures; } function selectDryRunRefsForResolution(params) { const refsToResolve = []; const skippedExecRefs = []; for (const ref of params.refs) (ref.source === "exec" && !params.allowExecInDryRun ? skippedExecRefs : refsToResolve).push(ref); return { refsToResolve, skippedExecRefs }; } function collectStrictConfigErrors(config, pluginMetadataSnapshot, deferredPluginMigrations) { const validated = validateConfigObjectRawWithPlugins(config, { semanticValidation: "strict", pluginMetadataSnapshot, deferredPluginMigrations }); if (validated.ok) return []; return formatConfigIssueLines(validated.issues, "-", { normalizeRoot: true }).map((message) => ({ kind: "schema", message })); } function assertStrictConfigForMutation(config, pluginMetadataSnapshot, deferredPluginMigrations) { const errors = collectStrictConfigErrors(config, pluginMetadataSnapshot, deferredPluginMigrations); if (errors.length === 0) return; throw new Error(["Config validation failed.", ...errors.map((error) => `- ${error.message}`)].join("\n")); } async function collectConfigSecretProviderErrors(params) { const providers = params.config.secrets?.providers ?? {}; const issues = []; let manifestRegistry; for (const [alias, provider] of Object.entries(providers)) { if (params.selection?.providerAliases && !params.selection.providerAliases.has(alias)) continue; const providerPath = `secrets.providers.${alias}`; if (isPluginIntegrationSecretProviderConfig(provider)) { if (!params.selection) continue; manifestRegistry ??= loadPluginMetadataSnapshot({ config: params.config, env: process.env }).manifestRegistry; const resolved = resolveSecretProviderIntegrationConfig({ manifestRegistry, providerAlias: alias, providerConfig: provider, config: params.config, env: process.env }); if (!resolved.ok) issues.push({ path: providerPath, message: resolved.reason }); } else if (isRecord(provider) && "command" in provider) try { await assertSecureExecCommandPath({ command: provider.command, label: `${providerPath}.command`, trustedDirs: provider.trustedDirs }); } catch (err) { issues.push({ path: `${providerPath}.command`, message: formatErrorMessage(err) }); } } return issues; } /** Validates one final candidate and decides whether the runner may preview, skip, or write it. */ async function validateConfigMutation(params) { const { config, operations, options, pluginMetadataSnapshot } = params; assertDeferredPluginMigrationConfigEditAllowed({ sourceConfig: params.previousConfig, nextConfig: config, pending: params.deferredPluginMigrations ?? [], editedPaths: operations.map((operation) => operation.setPath) }); const policyIssues = formatConfigIssueLines(collectUnsupportedSecretRefPolicyIssues(config), "", { normalizeRoot: true }).map((line) => line.trim()); const selection = selectConfigMutationSecrets(config, operations); const providerErrors = formatConfigIssueLines(await collectConfigSecretProviderErrors({ config, selection }), "").map((message) => ({ kind: "schema", message })); if (!options.dryRun) { if (policyIssues.length > 0) throw new Error([ "Config policy validation failed: unsupported SecretRef usage was detected.", ...policyIssues.slice(0, 5).map((issue) => `- ${issue}`), ...policyIssues.length > 5 ? [`- ... ${policyIssues.length - 5} more`] : [] ].join("\n")); if (providerErrors.length > 0) throw new Error(["Config validation failed: SecretRef provider configuration is invalid.", ...providerErrors.map((error) => `- ${error.message}`)].join("\n")); if (params.unchanged) { assertStrictConfigForMutation(config, pluginMetadataSnapshot, params.deferredPluginMigrations); return { kind: "unchanged" }; } } const { checkTouchedTextModelRefs } = await import("./config-model-validation-CmjKJtaL.mjs"); const modelCheck = await checkTouchedTextModelRefs({ ...params.modelValidation ?? { config, previousConfig: params.previousConfig }, touchedPaths: operations.map(({ setPath }) => setPath), redactDependencyValues: true }); if (!options.dryRun) { if (modelCheck.errors[0]) throw new Error(modelCheck.errors[0]); return { kind: "write" }; } const inputModes = uniqueValues(operations.map(({ inputMode }) => inputMode)); const checksRefs = inputModes.some((mode) => mode !== "value"); const requiresFullSchema = operations.some((operation) => operation.inputMode === "unset" || (operation.inputMode === "json" || operation.inputMode === "builder") && operation.schemaValidated !== true); const { refsToResolve, skippedExecRefs } = selectDryRunRefsForResolution({ refs: checksRefs ? selection.refs : [], allowExecInDryRun: Boolean(options.allowExec) }); const errors = modelCheck.errors.map((message) => ({ kind: "model", message })); if (!requiresFullSchema) errors.push(...policyIssues.map((message) => ({ kind: "schema", message }))); errors.push(...providerErrors); if (requiresFullSchema) errors.push(...collectStrictConfigErrors(config, pluginMetadataSnapshot, params.deferredPluginMigrations)); if (checksRefs) errors.push(...collectDryRunStaticErrorsForSkippedExecRefs({ refs: skippedExecRefs, config }), ...await collectDryRunResolvabilityErrors({ refs: refsToResolve, config })); const failures = dedupeByKey(errors, (error) => error.kind === "resolvability" ? `${error.kind}\u0000${error.ref ?? ""}\u0000${error.message}` : `${error.kind}\u0000${error.message}`); return { kind: "dry-run", result: { ok: failures.length === 0, operations: operations.length, configPath: params.configPath, inputModes, checks: { schema: requiresFullSchema || policyIssues.length > 0 || providerErrors.length > 0, resolvability: checksRefs || modelCheck.refsTotal > 0, resolvabilityComplete: (checksRefs || modelCheck.refsTotal > 0) && skippedExecRefs.length === 0 && modelCheck.refsChecked === modelCheck.refsTotal }, refsChecked: refsToResolve.length + modelCheck.refsChecked, skippedExecRefs: skippedExecRefs.length, ...failures.length > 0 ? { errors: failures } : {} } }; } //#endregion export { loadValidConfigForWrite as a, formatInvalidConfigRepairHint as i, ensureValidConfigSnapshotForCli as n, validateConfigMutation as o, finishConfigValidationForCli as r, assertStrictConfigForMutation as t };