UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,909 lines 89.5 kB
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { nt as isConfigSetJsonParseOnly, tt as isConfigMachineOutput } from "./argv-DNjh_yaF.js";
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString, p as normalizeStringifiedOptionalString } from "./string-coerce-CIXf7egm.js";
import { b as uniqueValues, d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { a as writeRuntimeJson, o as writeRuntimeStdout, r as defaultRuntime, t as ExitError } from "./runtime-CF2WjnNZ.js";
import { t as exitCliAfterOutput } from "./one-shot-exit-BkFPnjbM.js";
import { l as readConfigFileSnapshotForWrite, u as readConfigFileSnapshotWithPluginMetadata } from "./io.runtime-B9iJRs3w.js";
import { l as readFileDescriptorBoundedSync } from "./boundary-file-read-uaJcf6X6.js";
import { t as hasErrnoCode } from "./errno-CkbDOfLk.js";
import { S as resolveConfigEnvVars, x as containsEnvVarReference } from "./redact-BtvPPfTi.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { D as tryResolveLegacyCompatibilityAgentId, a as listAgentEntriesWithSource, i as listAgentEntries, n as hasAgentRosterProperty, s as readAgentRosterProperty } from "./agent-scope-config-DcbEhP0R.js";
import { f as resolveConfigPath, t as CONFIG_PATH } from "./paths-D2sRr1a_.js";
import "./session-key-BnWWjqNc.js";
import { a as parseConcreteConfigPathWithProvenance, i as parseConcreteConfigPathTokens, o as toDotPath, r as parseConcreteConfigPath } from "./dot-path-BOSboevO.js";
import { d as isSecretRef, p as isValidEnvSecretRefId, s as coerceSecretRef, v as resolveSecretInputRef } from "./types.secrets-kC0nOetj.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { c as isValidSecretProviderAlias, f as secretRefKey, i as formatExecSecretRefIdValidationMessage, o as isValidExecSecretRefId, p as validateExecSecretRefId, s as isValidFileSecretRefId } from "./ref-contract-D92DqQ-r.js";
import { T as SecretProviderSchema } from "./zod-schema.core-D6k6NKKA.js";
import { a as loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot-w-4EjxI4.js";
import { n as parseLegacyAgentRoster, t as migratePersistedImplicitMainRoster } from "./legacy.roster-CQ9vDbV2.js";
import { g as resolveConfiguredModelRef, i as buildModelAliasIndex, y as resolveModelRefFromString } from "./model-selection-shared-BlLyx1r2.js";
import { r as DEFAULT_PROVIDER } from "./defaults-CdX9UGcX.js";
import { t as splitTrailingAuthProfile } from "./model-ref-profile-BIKs-96s.js";
import { a as resolveAgentModelPrimaryValue, n as normalizeAgentModelRefForConfig } from "./model-input-BuGMCNOz.js";
import { c as resolveAgentExplicitModelPrimary, u as resolveAgentModelFallbacksOverride } from "./agent-scope-DbtJyKUL.js";
import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js";
import { W as AUTO_MANAGED_CONFIG_META_PATHS, c as resolveManagedUnsetPathsForWrite, m as validateConfigObjectRawWithPlugins, v as collectUnsupportedSecretRefPolicyIssues } from "./io.types-BUCjdS5v.js";
import { u as rejectConfigNonFiniteNumbers, x as visitConfigValueTree } from "./io.read-helpers-ZKp-UiGx.js";
import { t as ConfigMutationConflictError } from "./mutation-conflict-Be0wSyDG.js";
import { i as normalizeConfigIssues, n as formatConfigIssueLines } from "./issue-format-CQvxWNW8.js";
import { c as resolveConfigSecretTargetByPath, n as discoverConfigSecretTargets } from "./target-registry-query-qVhyD-Hq.js";
import "./target-registry-BHDUOLUU.js";
import { n as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-CsUZ07YX.js";
import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js";
import "./config-Cs0XXL3x.js";
import { t as formatDocsLink } from "./links-ClIwBcy4.js";
import { r as theme } from "./theme-vjDs9tao.js";
import { a as assertSecureExecCommandPath, c as resolveSecretProviderIntegrationConfig, n as resolveSecretRefValue, o as isPluginIntegrationSecretProviderConfig } from "./resolve-224YoYfx.js";
import { a as success, n as info, o as warn, t as danger } from "./globals-CTaGxEqj.js";
import { n as formatPluginPackagingRuntimeOutputRecoveryHint } from "./config-recovery-hints-BS5G0aQ-.js";
import { t as renderConfigValidationIssueLines } from "./issue-location-Dbpi1Rol.js";
import { r as formatCliJsonFailure } from "./failure-output-Dc5ce9_F.js";
import { t as quoteCliArg } from "./quote-cli-arg-BEt71TUh.js";
import { n as redactConfigObject } from "./redact-snapshot-DcfZQ78H.js";
import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.js";
import { a as isConfigSchemaPath, c as setAtPath, i as getAtPath, l as unsetAtPath, n as formatConfigSetPath, o as mergeAtPath, r as formatConfigUnsetMissingPathMessage, s as parseConfigSetValue, t as assertNonDestructiveReplacement, u as validatePathSegments } from "./config-cli-path-BHk6DaYo.js";
import { r as readBestEffortRuntimeConfigSchema, t as buildRuntimeConfigSchemaFromRegistry } from "./runtime-schema-D3-BdLtQ.js";
import { t as prepareConfigWriteTopology } from "./io.write-topology-BvvklbCP.js";
import { t as diffConfigPaths } from "./config-diff-D4CS9rJs.js";
import { t as buildGatewayReloadPlan } from "./config-reload-plan-DUT3qlnP.js";
import { t as resolveGatewayReloadSettings } from "./config-reload-settings-q1wYjpRM.js";
import { t as normalizeSubmittedConfigModelRefs } from "./model-input-normalization-D35tM3yN.js";
import { n as setCommandJsonMode } from "./json-mode-jHy4Wzfh.js";
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import JSON5 from "json5";
//#region src/cli/config-set-input.ts
const CONFIG_MUTATION_FILE_MAX_BYTES = 8388608;
function readConfigMutationFileSync(filePath, sourceLabel) {
	let fd;
	try {
		fd = fs.openSync(filePath, "r");
	} catch (error) {
		if (hasErrnoCode(error, "ENOENT")) throw new Error(`${sourceLabel} not found: ${filePath}. Check the path and try again.`, { cause: error });
		throw error;
	}
	try {
		if (!fs.fstatSync(fd).isFile()) throw new Error(`${sourceLabel} must be a regular file: ${filePath}. Choose a JSON5 input file and try again.`);
		try {
			return readFileDescriptorBoundedSync(fd, CONFIG_MUTATION_FILE_MAX_BYTES).toString("utf8");
		} catch (error) {
			if (error instanceof RangeError) throw new RangeError(`${sourceLabel} exceeds the 8 MiB supported maximum (${CONFIG_MUTATION_FILE_MAX_BYTES} bytes): ${filePath}`, { cause: error });
			throw error;
		}
	} finally {
		fs.closeSync(fd);
	}
}
function hasBatchMode(opts) {
	return Boolean(normalizeOptionalString(opts.batchJson) || normalizeOptionalString(opts.batchFile));
}
function hasRefBuilderOptions(opts) {
	return Boolean(opts.refProvider || opts.refSource || opts.refId);
}
function hasProviderBuilderOptions(opts) {
	return Boolean(opts.providerSource || opts.providerAllowlist?.length || opts.providerPath || opts.providerMode || opts.providerTimeoutMs || opts.providerMaxBytes || opts.providerCommand || opts.providerArg?.length || opts.providerNoOutputTimeoutMs || opts.providerMaxOutputBytes || opts.providerJsonOnly || opts.providerEnv?.length || opts.providerPassEnv?.length || opts.providerTrustedDir?.length);
}
function parseJson5Raw(raw, label) {
	let parsed;
	try {
		parsed = JSON5.parse(raw);
	} catch (err) {
		throw new Error(`Failed to parse ${label}: ${String(err)}`, { cause: err });
	}
	rejectConfigNonFiniteNumbers(parsed);
	return parsed;
}
function parseBatchEntries(raw, sourceLabel) {
	const parsed = parseJson5Raw(raw, sourceLabel);
	if (!Array.isArray(parsed)) throw new Error(`${sourceLabel} must be a JSON array.`);
	if (parsed.length === 0) throw new Error(`${sourceLabel} must contain at least one config update.`);
	const out = [];
	for (const [index, entry] of parsed.entries()) {
		if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`${sourceLabel}[${index}] must be an object.`);
		const typed = entry;
		const path = normalizeOptionalString(typed.path) ?? "";
		if (!path) throw new Error(`${sourceLabel}[${index}].path is required.`);
		const hasValue = Object.hasOwn(typed, "value");
		const hasRef = Object.hasOwn(typed, "ref");
		const hasProvider = Object.hasOwn(typed, "provider");
		if (Number(hasValue) + Number(hasRef) + Number(hasProvider) !== 1) throw new Error(`${sourceLabel}[${index}] must include exactly one of: value, ref, provider.`);
		out.push({
			path,
			...hasValue ? { value: typed.value } : {},
			...hasRef ? { ref: typed.ref } : {},
			...hasProvider ? { provider: typed.provider } : {}
		});
	}
	return out;
}
function parseConfigSetCurrentExpectation(opts) {
	const expectAbsent = opts.expectCurrentAbsent === true;
	const hasExpectedJson = opts.expectCurrentJson !== void 0;
	if (!expectAbsent && !hasExpectedJson) return;
	if (expectAbsent && hasExpectedJson) throw new Error("config set mode error: choose either --expect-current-absent or --expect-current-json, not both.");
	if (opts.dryRun) throw new Error("config set mode error: conditional expectations cannot be combined with --dry-run.");
	if (opts.batchJson !== void 0 || opts.batchFile !== void 0) throw new Error("config set mode error: conditional expectations require one path operation and cannot be combined with batch mode.");
	if (expectAbsent) return { kind: "absent" };
	const expectedJson = opts.expectCurrentJson;
	if (expectedJson === void 0) throw new Error("config set mode error: missing conditional expectation.");
	let value;
	try {
		value = JSON.parse(expectedJson);
		rejectConfigNonFiniteNumbers(value);
	} catch (error) {
		throw new Error("config set mode error: --expect-current-json must be valid JSON.", { cause: error });
	}
	return {
		kind: "json",
		value
	};
}
function parseBatchSource(opts) {
	const batchJson = normalizeOptionalString(opts.batchJson);
	const batchFile = normalizeOptionalString(opts.batchFile);
	const hasInline = Boolean(batchJson);
	const hasFile = Boolean(batchFile);
	if (!hasInline && !hasFile) return null;
	if (hasInline && hasFile) throw new Error("Use either --batch-json or --batch-file, not both.");
	if (hasInline) return parseBatchEntries(batchJson, "--batch-json");
	const pathname = normalizeStringifiedOptionalString(opts.batchFile) ?? "";
	if (!pathname) throw new Error("--batch-file must not be empty.");
	return parseBatchEntries(readConfigMutationFileSync(pathname, "--batch-file"), "--batch-file");
}
//#endregion
//#region src/cli/config-set-parser.ts
/** Resolve the config-set input mode or return the exact flag-conflict error. */
function resolveConfigSetMode(params) {
	if (params.hasBatchMode) {
		if (params.hasRefBuilderOptions || params.hasProviderBuilderOptions) return {
			ok: false,
			error: "batch mode (--batch-json/--batch-file) cannot be combined with ref builder (--ref-*) or provider builder (--provider-*) flags."
		};
		return {
			ok: true,
			mode: "batch"
		};
	}
	if (params.hasRefBuilderOptions && params.hasProviderBuilderOptions) return {
		ok: false,
		error: "choose exactly one mode: ref builder (--ref-provider/--ref-source/--ref-id) or provider builder (--provider-*), not both."
	};
	if (params.hasRefBuilderOptions) return {
		ok: true,
		mode: "ref_builder"
	};
	if (params.hasProviderBuilderOptions) return {
		ok: true,
		mode: "provider_builder"
	};
	return {
		ok: true,
		mode: params.strictJson ? "json" : "value"
	};
}
//#endregion
//#region src/cli/config-cli-input.ts
const CONFIG_PATCH_STDIN_MAX_BYTES = 1048576;
function modeError(message) {
	return /* @__PURE__ */ new Error(`config set mode error: ${message}`);
}
function configPatchModeError(message) {
	return /* @__PURE__ */ new Error(`config patch mode error: ${message}`);
}
function parseSecretRefSource(raw, label) {
	const source = raw.trim();
	if (source === "env" || source === "file" || source === "exec" || source === "store") return source;
	throw new Error(`${label} must be one of: env, file, exec, store.`);
}
function parseSecretRefBuilder(params) {
	const provider = params.provider.trim();
	if (!provider) throw new Error(`${params.fieldPrefix}.provider is required.`);
	if (!isValidSecretProviderAlias(provider)) throw new Error(`${params.fieldPrefix}.provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`);
	const source = parseSecretRefSource(params.source, `${params.fieldPrefix}.source`);
	const id = params.id.trim();
	if (!id) throw new Error(`${params.fieldPrefix}.id is required.`);
	if (source === "env" && !isValidEnvSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must match /^[A-Z][A-Z0-9_]{0,127}$/ for env refs.`);
	if (source === "store" && !isValidEnvSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must match /^[A-Z][A-Z0-9_]{0,127}$/ for store refs.`);
	if (source === "file" && !isValidFileSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must be an absolute JSON pointer (or "value" for singleValue mode).`);
	if (source === "exec" && !validateExecSecretRefId(id).ok) throw new Error(formatExecSecretRefIdValidationMessage());
	return {
		source,
		provider,
		id
	};
}
function parseOptionalPositiveInteger(raw, flag) {
	if (raw === void 0) return;
	const trimmed = raw.trim();
	if (!trimmed) throw new Error(`${flag} must not be empty.`);
	const parsed = parseStrictPositiveInteger(trimmed);
	if (parsed === void 0) throw new Error(`${flag} must be a positive integer.`);
	return parsed;
}
function parseProviderEnvEntries(entries) {
	if (!entries || entries.length === 0) return;
	const env = {};
	for (const entry of entries) {
		const separator = entry.indexOf("=");
		if (separator <= 0) throw new Error("--provider-env expects KEY=*** entries.");
		const key = entry.slice(0, separator).trim();
		if (!key) throw new Error("--provider-env key must not be empty.");
		env[key] = entry.slice(separator + 1);
	}
	return Object.keys(env).length > 0 ? env : void 0;
}
function validateProviderAliasPath(path) {
	if (path.length !== 3 || path[0] !== "secrets" || path[1] !== "providers") throw new Error("Provider builder mode requires path \"secrets.providers.<alias>\" (example: secrets.providers.vault).");
	const alias = path[2] ?? "";
	if (!isValidSecretProviderAlias(alias)) throw new Error(`Provider alias "${alias}" must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`);
}
function buildProviderFromBuilder(opts) {
	const sourceRaw = opts.providerSource?.trim();
	if (!sourceRaw) throw new Error("--provider-source is required in provider builder mode.");
	const source = parseSecretRefSource(sourceRaw, "--provider-source");
	const timeoutMs = parseOptionalPositiveInteger(opts.providerTimeoutMs, "--provider-timeout-ms");
	const maxBytes = parseOptionalPositiveInteger(opts.providerMaxBytes, "--provider-max-bytes");
	const noOutputTimeoutMs = parseOptionalPositiveInteger(opts.providerNoOutputTimeoutMs, "--provider-no-output-timeout-ms");
	const maxOutputBytes = parseOptionalPositiveInteger(opts.providerMaxOutputBytes, "--provider-max-output-bytes");
	const providerEnv = parseProviderEnvEntries(opts.providerEnv);
	let provider;
	if (source === "env") {
		const allowlist = normalizeStringEntries(opts.providerAllowlist);
		for (const envName of allowlist) if (!isValidEnvSecretRefId(envName)) throw new Error(`--provider-allowlist entry "${envName}" must match /^[A-Z][A-Z0-9_]{0,127}$/.`);
		provider = {
			source: "env",
			...allowlist.length > 0 ? { allowlist } : {}
		};
	} else if (source === "file") {
		const filePath = opts.providerPath?.trim();
		if (!filePath) throw new Error("--provider-path is required when --provider-source file is used.");
		const modeRaw = opts.providerMode?.trim();
		if (modeRaw && modeRaw !== "singleValue" && modeRaw !== "json") throw new Error("--provider-mode must be one of: singleValue, json.");
		const mode = modeRaw === "singleValue" || modeRaw === "json" ? modeRaw : void 0;
		provider = {
			source: "file",
			path: filePath,
			...mode ? { mode } : {},
			...timeoutMs !== void 0 ? { timeoutMs } : {},
			...maxBytes !== void 0 ? { maxBytes } : {}
		};
	} else if (source === "store") provider = { source: "store" };
	else {
		const command = opts.providerCommand?.trim();
		if (!command) throw new Error("--provider-command is required when --provider-source exec is used.");
		provider = {
			source: "exec",
			command,
			...opts.providerArg?.length ? { args: opts.providerArg.map((entry) => entry.trim()) } : {},
			...timeoutMs !== void 0 ? { timeoutMs } : {},
			...noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs } : {},
			...maxOutputBytes !== void 0 ? { maxOutputBytes } : {},
			...opts.providerJsonOnly ? { jsonOnly: true } : {},
			...providerEnv ? { env: providerEnv } : {},
			...opts.providerPassEnv?.length ? { passEnv: normalizeStringEntries(opts.providerPassEnv) } : {},
			...opts.providerTrustedDir?.length ? { trustedDirs: normalizeStringEntries(opts.providerTrustedDir) } : {}
		};
	}
	const validated = SecretProviderSchema.safeParse(provider);
	if (!validated.success) {
		const issue = validated.error.issues[0];
		throw new Error(`Provider builder config invalid at ${issue?.path?.join(".") ?? "<provider>"}: ${issue?.message ?? "Invalid provider config."}`);
	}
	return validated.data;
}
function parseSecretRefFromUnknown(value, label) {
	if (!isRecord(value)) throw new Error(`${label} must be an object with source/provider/id.`);
	if (typeof value.provider !== "string" || typeof value.source !== "string" || typeof value.id !== "string") throw new Error(`${label} must include string fields: source, provider, id.`);
	return parseSecretRefBuilder({
		provider: value.provider,
		source: value.source,
		id: value.id,
		fieldPrefix: label
	});
}
function buildAssignmentOperation(params) {
	const resolved = resolveConfigSecretTargetByPath(params.requestedPath, params.pathTokens);
	const coercedRef = coerceSecretRef(params.value);
	return {
		inputMode: params.inputMode,
		requestedPath: params.requestedPath,
		...params.pathTokens ? { pathTokens: params.pathTokens } : {},
		...params.quotedNumericSegments ? { quotedNumericSegments: params.quotedNumericSegments } : {},
		setPath: coercedRef && resolved?.entry.secretShape === "sibling_ref" && resolved.refPathSegments ? resolved.refPathSegments : params.requestedPath,
		value: params.value,
		...params.validatedRef && resolved ? { schemaValidated: true } : {}
	};
}
function parseBatchOperations(entries) {
	return entries.map((entry, index) => {
		const { tokens: pathTokens, quotedNumericSegments } = parseConcreteConfigPathWithProvenance(entry.path);
		const path = pathTokens.map(String);
		if (entry.ref !== void 0) return buildAssignmentOperation({
			requestedPath: path,
			pathTokens,
			quotedNumericSegments,
			value: parseSecretRefFromUnknown(entry.ref, `batch[${index}].ref`),
			inputMode: "json",
			validatedRef: true
		});
		if (entry.provider !== void 0) {
			validateProviderAliasPath(path);
			const validated = SecretProviderSchema.safeParse(entry.provider);
			if (!validated.success) {
				const issue = validated.error.issues[0];
				throw new Error(`batch[${index}].provider invalid at ${issue?.path?.join(".") ?? "<provider>"}: ${issue?.message ?? ""}`);
			}
			return {
				inputMode: "json",
				requestedPath: path,
				pathTokens,
				quotedNumericSegments,
				setPath: path,
				value: validated.data,
				schemaValidated: true
			};
		}
		return buildAssignmentOperation({
			requestedPath: path,
			pathTokens,
			quotedNumericSegments,
			value: entry.value,
			inputMode: "json"
		});
	});
}
function buildConfigSetOperations(params) {
	const strictJson = Boolean(params.opts.strictJson || params.opts.json);
	const modeResolution = resolveConfigSetMode({
		hasBatchMode: hasBatchMode(params.opts),
		hasRefBuilderOptions: hasRefBuilderOptions(params.opts),
		hasProviderBuilderOptions: hasProviderBuilderOptions(params.opts),
		strictJson
	});
	if (!modeResolution.ok) throw modeError(modeResolution.error);
	if (params.opts.allowExec && !params.opts.dryRun) throw modeError("--allow-exec requires --dry-run.");
	if (params.opts.merge && params.opts.replace) throw modeError("choose either --merge or --replace, not both.");
	const batchEntries = parseBatchSource(params.opts);
	if (batchEntries) {
		if (params.path !== void 0 || params.value !== void 0) throw modeError("batch mode does not accept <path> or <value> arguments.");
		return parseBatchOperations(batchEntries);
	}
	const pathProvided = typeof params.path === "string" && params.path.trim().length > 0;
	const parsedConcretePath = pathProvided ? parseConcreteConfigPathWithProvenance(params.path) : null;
	const pathTokens = parsedConcretePath?.tokens ?? null;
	const parsedPath = pathTokens?.map(String) ?? null;
	if (modeResolution.mode === "ref_builder") {
		if (!pathProvided || !parsedPath) throw modeError("ref builder mode requires <path>.");
		if (params.value !== void 0) throw modeError("ref builder mode does not accept <value>.");
		if (!params.opts.refProvider || !params.opts.refSource || !params.opts.refId) throw modeError("ref builder mode requires --ref-provider <alias>, --ref-source <env|file|exec|store>, and --ref-id <id>.");
		return [buildAssignmentOperation({
			requestedPath: parsedPath,
			pathTokens: pathTokens ?? void 0,
			quotedNumericSegments: parsedConcretePath?.quotedNumericSegments,
			value: parseSecretRefBuilder({
				provider: params.opts.refProvider,
				source: params.opts.refSource,
				id: params.opts.refId,
				fieldPrefix: "ref"
			}),
			inputMode: "builder",
			validatedRef: true
		})];
	}
	if (modeResolution.mode === "provider_builder") {
		if (!pathProvided || !parsedPath) throw modeError("provider builder mode requires <path>.");
		if (params.value !== void 0) throw modeError("provider builder mode does not accept <value>.");
		const value = buildProviderFromBuilder(params.opts);
		validateProviderAliasPath(parsedPath);
		return [{
			inputMode: "builder",
			requestedPath: parsedPath,
			...pathTokens ? { pathTokens } : {},
			...parsedConcretePath ? { quotedNumericSegments: parsedConcretePath.quotedNumericSegments } : {},
			setPath: parsedPath,
			value,
			schemaValidated: true
		}];
	}
	if (!pathProvided || !parsedPath) throw modeError("value/json mode requires <path> when batch mode is not used.");
	if (params.value === void 0) throw modeError("value/json mode requires <value>.");
	return [buildAssignmentOperation({
		requestedPath: parsedPath,
		pathTokens: pathTokens ?? void 0,
		quotedNumericSegments: parsedConcretePath?.quotedNumericSegments,
		value: parseConfigSetValue(params.value, strictJson),
		inputMode: modeResolution.mode === "json" ? "json" : "value"
	})];
}
async function readStdinText() {
	if (process.stdin.isTTY) throw configPatchModeError("--stdin refuses to read from an interactive terminal; pipe input or use --file <path>.");
	process.stdin.setEncoding("utf8");
	return (await readByteStreamWithLimit(process.stdin, {
		maxBytes: CONFIG_PATCH_STDIN_MAX_BYTES,
		onOverflow: ({ maxBytes }) => configPatchModeError(`--stdin input exceeds ${maxBytes} bytes; use --file <path> for larger patches.`)
	})).toString("utf8");
}
async function readConfigPatchInput(opts) {
	const file = normalizeOptionalString(opts.file);
	const stdin = Boolean(opts.stdin);
	if (Boolean(file) === stdin) throw configPatchModeError("provide exactly one of --file <path> or --stdin.");
	const sourceLabel = stdin ? "--stdin" : "--file";
	let raw;
	if (stdin) raw = await readStdinText();
	else raw = readConfigMutationFileSync(file, "--file");
	let parsed;
	try {
		parsed = JSON5.parse(raw);
	} catch (err) {
		throw new Error(`Failed to parse ${sourceLabel} as JSON5: ${String(err)}`, { cause: err });
	}
	rejectConfigNonFiniteNumbers(parsed);
	return parsed;
}
function buildDeleteOperation(path) {
	return {
		...buildUnsetOperation(path),
		inputMode: "json"
	};
}
function buildUnsetOperation(path, pathTokens) {
	return {
		inputMode: "unset",
		requestedPath: path,
		...pathTokens ? { pathTokens } : {},
		setPath: path,
		value: void 0,
		mutation: "delete"
	};
}
function buildApplyValueOperation(params) {
	const ref = isRecord(params.value) ? coerceSecretRef(params.value) : null;
	return {
		...buildAssignmentOperation({
			requestedPath: params.path,
			value: ref ? parseSecretRefFromUnknown(params.value, `patch.${toDotPath(params.path)}`) : params.value,
			inputMode: "json",
			validatedRef: Boolean(ref)
		}),
		...params.mutation ? { mutation: params.mutation } : {}
	};
}
function buildConfigPatchOperations(params) {
	if (!isRecord(params.patch)) throw configPatchModeError("input must be a JSON5 object patch.");
	const operations = [];
	const pathKey = (path) => JSON.stringify(path);
	const replacePathsByLength = /* @__PURE__ */ new Map();
	for (const replacePath of params.replacePaths) {
		const sameLength = replacePathsByLength.get(replacePath.length);
		const candidate = {
			path: replacePath,
			key: pathKey(replacePath)
		};
		if (sameLength) sameLength.push(candidate);
		else replacePathsByLength.set(replacePath.length, [candidate]);
	}
	const matchedReplacePathKeys = /* @__PURE__ */ new Set();
	visitConfigValueTree(params.patch, (value, path) => {
		const segment = path.at(-1);
		if (segment !== void 0) validatePathSegments([segment]);
		const replacementPath = replacePathsByLength.get(path.length)?.find((candidate) => candidate.path.every((candidateSegment, index) => candidateSegment === path[index]));
		if (path.length > 0 && replacementPath) {
			matchedReplacePathKeys.add(replacementPath.key);
			const operationPath = [...path];
			operations.push(value === null ? buildDeleteOperation(operationPath) : buildApplyValueOperation({
				path: operationPath,
				value,
				mutation: "replace"
			}));
			return false;
		}
		if (path.length > 0 && value === null) {
			operations.push(buildDeleteOperation([...path]));
			return false;
		}
		if (path.length > 0 && isRecord(value) && coerceSecretRef(value)) {
			operations.push(buildApplyValueOperation({
				path: [...path],
				value
			}));
			return false;
		}
		if (isRecord(value)) {
			if (path.length > 0 && Object.keys(value).length === 0) {
				operations.push(buildApplyValueOperation({
					path: [...path],
					value,
					mutation: "merge"
				}));
				return false;
			}
			return true;
		}
		if (path.length === 0) throw configPatchModeError("input must contain at least one config key.");
		operations.push(buildApplyValueOperation({
			path: [...path],
			value
		}));
		return false;
	});
	const unusedReplacePath = params.replacePaths.find((replacePath) => !matchedReplacePathKeys.has(pathKey(replacePath)));
	if (unusedReplacePath) throw configPatchModeError(`--replace-path ${toDotPath(unusedReplacePath)} did not match any value in the input patch.`);
	if (operations.length === 0) throw configPatchModeError("input patch did not contain any config updates.");
	return operations;
}
async function readConfigPatchOperations(opts) {
	return buildConfigPatchOperations({
		patch: await readConfigPatchInput(opts),
		replacePaths: (opts.replacePath ?? []).map(parseConcreteConfigPath)
	});
}
function formatPluginInstallConfigSetError() {
	return [
		"plugins.installs is managed by the plugin index and cannot be edited with config set.",
		"",
		"Use plugin commands instead:",
		`  ${formatCliCommand("openclaw plugins install <spec>")}`,
		`  ${formatCliCommand("openclaw plugins update <plugin-id>")}`,
		`  ${formatCliCommand("openclaw plugins uninstall <plugin-id>")}`
	].join("\n");
}
//#endregion
//#region src/cli/config-cli-model-normalization.ts
function normalizeConfigMutationModelRefs(cfg) {
	const pluginMetadata = loadPluginMetadataSnapshot({
		config: cfg,
		env: process.env
	});
	return normalizeSubmittedConfigModelRefs(cfg, pluginMetadata.owners.modelIdNormalizationPolicies);
}
function normalizeConfigMutationExplicitSetPath(path) {
	const modelKeyIndex = path[0] === "agents" && path[1] === "defaults" && path[2] === "models" ? 3 : path[0] === "agents" && (path[1] === "entries" || path[1] === "list") && path[3] === "models" ? 4 : void 0;
	if (modelKeyIndex !== void 0 && path.length > modelKeyIndex) {
		const modelId = expectDefined(path[modelKeyIndex], `path entry at ${modelKeyIndex}`);
		const normalizedModelId = normalizeAgentModelRefForConfig(modelId);
		return normalizedModelId === modelId ? path : [
			...path.slice(0, modelKeyIndex),
			normalizedModelId,
			...path.slice(modelKeyIndex + 1)
		];
	}
	return path;
}
//#endregion
//#region src/cli/config-cli-roster.ts
/** Keeps the documented list input and keyed input on one mutable roster. */
var ConfigMutationAgentRoster = class {
	constructor(root, sourceConfigBeforeMigrations) {
		this.root = root;
		const sourceRoster = readAgentRosterProperty(sourceConfigBeforeMigrations);
		this.legacyOrder = sourceRoster?.kind === "list" ? parseLegacyAgentRoster(sourceRoster.value)?.order : void 0;
	}
	prepare(operation, merge) {
		const path = operation.setPath;
		if (path[0] !== "agents") return;
		if (path.length === 1 && isRecord(operation.value) && Object.hasOwn(operation.value, "list") && Object.hasOwn(operation.value, "entries")) throw new Error("Set either agents.list or agents.entries in one value, not both.");
		const submittedRoster = path.length === 1 ? readAgentRosterProperty({ agents: operation.value }) : void 0;
		const kind = path.length === 1 ? submittedRoster?.kind : path[1];
		const roster = readAgentRosterProperty(this.root);
		if (!roster) this.legacyOrder = void 0;
		const agents = this.root.agents;
		if (kind !== "list" && kind !== "entries" || !isRecord(agents)) return;
		if (path.length <= 2 && !merge && (kind === "entries" || operation.mutation !== "delete")) {
			delete agents[kind === "entries" ? "list" : "entries"];
			this.legacyOrder = void 0;
			return;
		}
		if (kind === "entries") {
			if (roster?.kind === "list") this.canonicalize(true);
			return;
		}
		if (roster?.kind !== "entries") return;
		if (!isRecord(roster.value) || Object.values(roster.value).some((entry) => !isRecord(entry) || Object.hasOwn(entry, "id"))) throw new Error("Cannot address agents.list while agents.entries contains invalid entries; correct the keyed roster first.");
		const entries = roster.value;
		const order = [.../* @__PURE__ */ new Set([...this.legacyOrder ?? [], ...Object.keys(entries)])].filter((id) => Object.hasOwn(entries, id));
		const list = [];
		for (const id of order) list.push({
			...entries[id],
			id
		});
		agents.list = list;
		delete agents.entries;
	}
	writePath(path) {
		if (path[0] !== "agents" || path[1] !== "list") return path;
		if (path.length === 2) {
			const roster = readAgentRosterProperty(this.root);
			return roster?.kind === "list" && parseLegacyAgentRoster(roster.value) ? ["agents", "entries"] : path;
		}
		const entry = getAtPath(this.root, path.slice(0, 3));
		const id = (entry.found ? parseLegacyAgentRoster([entry.value]) : void 0)?.order[0];
		return id === void 0 ? path : [
			"agents",
			"entries",
			id,
			...path.slice(3)
		];
	}
	finish() {
		this.canonicalize(false);
	}
	canonicalize(required) {
		const agents = this.root.agents;
		const roster = readAgentRosterProperty(this.root);
		if (!isRecord(agents) || roster?.kind !== "list") return;
		const parsed = parseLegacyAgentRoster(roster.value);
		if (!parsed) {
			if (required) throw new Error("Cannot address agents.entries while agents.list contains invalid or duplicate ids; correct the list first.");
			return;
		}
		this.legacyOrder = parsed.order;
		agents.entries = parsed.entries;
		delete agents.list;
	}
};
//#endregion
//#region src/cli/config-model-validation.ts
function isPathPrefix(prefix, path) {
	return prefix.length <= path.length && prefix.every((segment, index) => path[index] === segment);
}
function pathMayAffectTextModelRefs(path) {
	if (path[0] !== "agents" || path.length === 1) return path[0] === "agents";
	if (path[1] === "defaults") return path.length === 2 || path[2] === "model";
	return path[1] === "entries" || path[1] === "list";
}
function collectTextModelConfigRefs(params) {
	if (typeof params.model === "string") {
		const value = params.model.trim();
		return [{
			path: params.path,
			value,
			...params.agentId ? { agentId: params.agentId } : {},
			fallback: false
		}];
	}
	if (!params.model || typeof params.model !== "object" || Array.isArray(params.model)) return [];
	const model = params.model;
	const refs = [];
	if (typeof model.primary === "string") {
		const value = model.primary.trim();
		refs.push({
			path: `${params.path}.primary`,
			value,
			...params.agentId ? { agentId: params.agentId } : {},
			fallback: false
		});
	}
	if (Array.isArray(model.fallbacks)) for (const [index, fallback] of model.fallbacks.entries()) {
		if (typeof fallback !== "string") continue;
		refs.push({
			path: `${params.path}.fallbacks.${index}`,
			value: fallback.trim(),
			...params.agentId ? { agentId: params.agentId } : {},
			fallback: true
		});
	}
	return refs;
}
function collectTextModelRefs(config) {
	const refs = collectTextModelConfigRefs({
		model: config.agents?.defaults?.model,
		path: "agents.defaults.model"
	});
	for (const { entry: agent, source } of listAgentEntriesWithSource(config)) {
		const agentId = agent.id;
		const agentPath = source.kind === "entries" ? `agents.entries.${source.key}` : `agents.list.${source.index}`;
		refs.push(...collectTextModelConfigRefs({
			model: agent.model,
			path: `${agentPath}.model`,
			agentId
		}));
	}
	for (const ref of refs) {
		if (ref.fallback) continue;
		const authProfileId = splitTrailingAuthProfile(ref.value).profile;
		if (authProfileId) ref.authProfileId = authProfileId;
	}
	return refs;
}
function modelRefComparisonKey(ref) {
	if (ref.agentId) {
		const modelOffset = ref.path.indexOf(".model");
		const relativePath = modelOffset >= 0 ? ref.path.slice(modelOffset + 1) : ref.path;
		return `agent:${normalizeAgentId(ref.agentId)}:${relativePath}`;
	}
	return `path:${ref.path}`;
}
function collectTouchedTextModelRefs(params) {
	const listedAgentEntries = listAgentEntriesWithSource(params.config);
	const defaultPrimaryPath = [
		"agents",
		"defaults",
		"model",
		"primary"
	];
	const defaultPrimaryTouched = params.touchedPaths.some((touchedPath) => isPathPrefix(touchedPath, defaultPrimaryPath) || isPathPrefix(defaultPrimaryPath, touchedPath));
	const refs = collectTextModelRefs(params.config);
	const previousRefs = params.previousConfig ? collectTextModelRefs(params.previousConfig) : void 0;
	const previousRefsByIdentity = previousRefs ? new Map(previousRefs.map((ref) => [modelRefComparisonKey(ref), ref])) : void 0;
	const previousDefaultAgentId = params.previousConfig ? tryResolveLegacyCompatibilityAgentId(params.previousConfig) : void 0;
	const defaultPrimaryProviderChanged = defaultPrimaryTouched && (!previousRefs || previousDefaultAgentId === void 0 || resolveDefaultModelForAgent({ cfg: params.config }).provider !== resolveDefaultModelForAgent({
		cfg: params.previousConfig,
		agentId: previousDefaultAgentId
	}).provider);
	const touchedRefs = refs.filter((ref) => {
		if (ref.fallback && defaultPrimaryProviderChanged) {
			const previousRef = previousRefsByIdentity?.get(modelRefComparisonKey(ref));
			const nextResolved = resolveCanonicalFallbackRef(params.config, ref.value);
			const previousResolved = params.previousConfig && previousRef ? resolveCanonicalFallbackRef(params.previousConfig, previousRef.value) : void 0;
			if (!nextResolved || !previousResolved || nextResolved.provider !== previousResolved.provider || nextResolved.model !== previousResolved.model) {
				ref.dependency = true;
				return true;
			}
		}
		const refPath = ref.path.split(".");
		const touched = params.touchedPaths.some((touchedPath) => isPathPrefix(touchedPath, refPath) || isPathPrefix(refPath, touchedPath));
		if (!touched || !previousRefsByIdentity) return touched;
		const previousRef = previousRefsByIdentity.get(modelRefComparisonKey(ref));
		const ownerChanged = previousRef?.agentId !== ref.agentId;
		if (ownerChanged) ref.dependency = true;
		return previousRef?.value !== ref.value || ownerChanged;
	});
	const defaultRefs = refs.filter((ref) => ref.agentId === void 0);
	if (defaultRefs.length === 0) return touchedRefs;
	for (const { entry, source } of listedAgentEntries) {
		const agentId = entry.id;
		const agentEntryPath = [
			"agents",
			source.kind,
			source.kind === "entries" ? source.key : String(source.index)
		];
		const agentModelPath = [...agentEntryPath, "model"];
		if (!params.touchedPaths.some((touchedPath) => isPathPrefix(touchedPath, agentEntryPath) || isPathPrefix(agentEntryPath, touchedPath) || isPathPrefix(touchedPath, agentModelPath) || isPathPrefix(agentModelPath, touchedPath))) continue;
		for (const defaultRef of defaultRefs) {
			const inherits = defaultRef.fallback ? resolveAgentModelFallbacksOverride(params.config, agentId) === void 0 : resolveAgentExplicitModelPrimary(params.config, agentId) === void 0;
			const previouslyInherited = (params.previousConfig ? listAgentEntries(params.previousConfig) : []).some((previousEntry) => normalizeAgentId(previousEntry.id) === normalizeAgentId(agentId)) && params.previousConfig ? defaultRef.fallback ? resolveAgentModelFallbacksOverride(params.previousConfig, agentId) === void 0 : resolveAgentExplicitModelPrimary(params.previousConfig, agentId) === void 0 : false;
			if (inherits && !previouslyInherited) touchedRefs.push({
				...defaultRef,
				agentId,
				dependency: true
			});
		}
	}
	return touchedRefs;
}
function resolveCanonicalPrimaryRef(config, value) {
	const validationConfig = {
		...config,
		agents: {
			...config.agents,
			defaults: {
				...config.agents?.defaults,
				model: value
			}
		}
	};
	const resolved = resolveConfiguredModelRef({
		cfg: validationConfig,
		defaultProvider: DEFAULT_PROVIDER,
		defaultModel: "",
		allowPluginNormalization: true
	});
	return resolved.model ? resolved : void 0;
}
function resolveFallbackRef(config, value) {
	const defaultProvider = resolveDefaultModelForAgent({ cfg: config }).provider;
	return resolveModelRefFromString({
		cfg: config,
		raw: value,
		defaultProvider,
		aliasIndex: buildModelAliasIndex({
			cfg: config,
			defaultProvider,
			allowPluginNormalization: true
		}),
		allowPluginNormalization: true
	});
}
function resolveCanonicalFallbackRef(config, value) {
	return resolveFallbackRef(config, value)?.ref;
}
function hasUnresolvedInheritedFallbackProvider(config, ref, unresolvedPaths) {
	if (!ref.fallback || ref.value.includes("/") || !unresolvedPaths.has("agents.defaults.model") && !unresolvedPaths.has("agents.defaults.model.primary")) return false;
	const primary = resolveAgentModelPrimaryValue(config.agents?.defaults?.model);
	if (!primary) return false;
	const primaryModel = splitTrailingAuthProfile(primary).model;
	const slash = primaryModel.indexOf("/");
	const provider = slash > 0 ? primaryModel.slice(0, slash) : primaryModel;
	const fallback = resolveFallbackRef(config, ref.value);
	return Boolean(fallback && !fallback.alias && containsEnvVarReference(provider));
}
function expandInheritedDefaultRefs(config, refs) {
	const agentEntries = listAgentEntries(config);
	const defaultAgentId = tryResolveLegacyCompatibilityAgentId(config);
	const expanded = [];
	const seen = /* @__PURE__ */ new Set();
	const push = (ref) => {
		const key = `${ref.path}\u0000${ref.agentId ?? ""}`;
		if (!seen.has(key)) {
			seen.add(key);
			expanded.push(ref);
		}
	};
	for (const ref of refs) {
		if (ref.agentId !== void 0) {
			push(ref);
			continue;
		}
		if (defaultAgentId) {
			if (!agentEntries.some((entry) => normalizeAgentId(entry.id) === normalizeAgentId(defaultAgentId)) || (ref.fallback ? resolveAgentModelFallbacksOverride(config, defaultAgentId) === void 0 : resolveAgentExplicitModelPrimary(config, defaultAgentId) === void 0)) push(ref);
		}
		for (const { id: agentId } of agentEntries) {
			if (defaultAgentId && normalizeAgentId(agentId) === normalizeAgentId(defaultAgentId)) continue;
			if (ref.fallback ? resolveAgentModelFallbacksOverride(config, agentId) === void 0 : resolveAgentExplicitModelPrimary(config, agentId) === void 0) push({
				...ref,
				agentId
			});
		}
	}
	return expanded;
}
function modelRefEnvSourcePath(path) {
	return path.replace(/\.list\.(\d+)/u, ".list[$1]").replace(/\.fallbacks\.(\d+)$/u, ".fallbacks[$1]");
}
function validateModelRefSyntax(config, ref, unresolvedPaths) {
	if (!ref.value) return "Model reference is empty";
	if (unresolvedPaths.has(modelRefEnvSourcePath(ref.path))) return "Model reference contains an unresolved environment variable";
	return (ref.fallback ? resolveCanonicalFallbackRef(config, ref.value) : resolveCanonicalPrimaryRef(config, ref.value)) ? void 0 : "Invalid model reference or configured model alias target";
}
async function createRuntimeModelRefResolver() {
	const [agentScope, modelSelection] = await Promise.all([import("./agent-scope-BHLMs19p.js"), import("./model-selection-DEDE6TxI.js")]);
	let modelModules;
	const loadModelModules = () => modelModules ??= Promise.all([import("./model-CokXeqgU.js"), import("./prepared-model-runtime-D7Vr1U41.js")]);
	return async ({ config, ref }) => {
		const resolvedRef = ref.fallback ? resolveCanonicalFallbackRef(config, ref.value) : resolveCanonicalPrimaryRef(config, ref.value);
		if (!resolvedRef) return `Unknown model: ${ref.value}`;
		const { provider, model } = resolvedRef;
		if (modelSelection.isCliProvider(provider, config)) return;
		const targetAgentId = ref.agentId ?? agentScope.tryResolveLegacyCompatibilityAgentId(config) ?? agentScope.resolveDefaultAgentId(config);
		const agentDir = agentScope.resolveAgentDir(config, targetAgentId);
		const workspaceDir = agentScope.resolveAgentWorkspaceDir(config, targetAgentId);
		const [modelRuntime, preparedRuntime] = await loadModelModules();
		const lease = await preparedRuntime.acquireReadOnlyPreparedModelRuntime({
			agentId: targetAgentId,
			agentDir,
			config,
			workspaceDir,
			loadRuntimePlugins: true,
			runtimePluginSelections: [{
				provider,
				modelId: model,
				agentId: targetAgentId
			}]
		});
		try {
			const stores = lease.snapshot.createStores();
			const resolution = await modelRuntime.resolveModelAsync(provider, model, agentDir, config, {
				...stores,
				agentId: targetAgentId,
				allowBundledStaticCatalogFallback: true,
				...ref.authProfileId ? { authProfileId: ref.authProfileId } : {},
				preparedModelRuntime: lease.snapshot,
				workspaceDir
			});
			return resolution.model ? void 0 : resolution.error ?? `Unknown model: ${provider}/${model}`;
		} finally {
			lease.release();
		}
	};
}
function formatModelRefError(ref, error, authoredValue = ref.value, options) {
	const safeError = options?.suppressDetail || authoredValue !== ref.value ? "Unable to resolve authored model reference" : error;
	const detail = safeError.endsWith(".") ? safeError : `${safeError}.`;
	return `Cannot set model reference "${authoredValue}" at ${ref.path}: ${detail} Run ${formatCliCommand("openclaw models list")} to list available models.`;
}
async function checkTouchedTextModelRefs(params) {
	if (!params.touchedPaths.some(pathMayAffectTextModelRefs)) return {
		refsChecked: 0,
		refsTotal: 0,
		errors: []
	};
	const config = hasAgentRosterProperty(params.config) ? params.config : migratePersistedImplicitMainRoster(params.config).config;
	const previousConfig = params.previousConfig && !hasAgentRosterProperty(params.previousConfig) ? migratePersistedImplicitMainRoster(params.previousConfig).config : params.previousConfig;
	const authoredRefs = collectTouchedTextModelRefs({
		...params,
		config,
		previousConfig
	});
	const authoredValuesByPath = new Map(collectTextModelRefs(params.config).map((ref) => [ref.path, ref.value]));
	const previousAuthoredValuesByPath = new Map(collectTextModelRefs(params.previousConfig ?? {}).map((ref) => [ref.path, ref.value]));
	let validationConfig;
	let validationPreviousConfig;
	const unresolvedPaths = /* @__PURE__ */ new Set();
	try {
		const env = params.env ?? process.env;
		validationConfig = resolveConfigEnvVars(params.config, env, { onMissing: ({ configPath }) => unresolvedPaths.add(configPath) });
		validationPreviousConfig = params.previousConfig ? resolveConfigEnvVars(params.previousConfig, env, { onMissing: () => {} }) : void 0;
	} catch (cause) {
		const detail = cause instanceof Error ? cause.message : String(cause);
		return {
			refsChecked: 0,
			refsTotal: authoredRefs.length,
			errors: [`Unable to validate changed model references before writing: ${detail}`]
		};
	}
	const validationValuesByPath = new Map(collectTextModelRefs(validationConfig).map((ref) => [ref.path, ref.value]));
	const modelEnvWasExpanded = [...authoredValuesByPath].some(([path, value]) => validationValuesByPath.get(path) !== value);
	const formatError = (ref, error) => {
		const redactDependency = Boolean(params.redactDependencyValues && ref.dependency);
		return formatModelRefError(ref, error, redactDependency ? "<configured model reference>" : authoredValuesByPath.get(ref.path), { suppressDetail: modelEnvWasExpanded || redactDependency });
	};
	const validationRefsByPath = new Map(collectTextModelRefs(validationConfig).map((ref) => [ref.path, ref]));
	const validationRosterConfig = hasAgentRosterProperty(validationConfig) ? validationConfig : migratePersistedImplicitMainRoster(validationConfig).config;
	const validationPreviousRosterConfig = validationPreviousConfig && !hasAgentRosterProperty(validationPreviousConfig) ? migratePersistedImplicitMainRoster(validationPreviousConfig).config : validationPreviousConfig;
	const refsByKey = new Map(collectTouchedTextModelRefs({
		config: validationRosterConfig,
		previousConfig: validationPreviousRosterConfig,
		touchedPaths: params.touchedPaths
	}).map((ref) => [modelRefComparisonKey(ref), ref]));
	for (const authoredRef of authoredRefs) {
		if (authoredRef.dependency && previousAuthoredValuesByPath.get(authoredRef.path) === authoredRef.value) continue;
		const validationRef = validationRefsByPath.get(authoredRef.path);
		if (!validationRef) continue;
		const key = modelRefComparisonKey(validationRef);
		const expandedRef = refsByKey.get(key);
		refsByKey.set(key, {
			...validationRef,
			...authoredRef.dependency || expandedRef?.dependency ? { dependency: true } : {}
		});
	}
	const refs = expandInheritedDefaultRefs(validationRosterConfig, [...refsByKey.values()]);
	if (refs.length === 0) return {
		refsChecked: 0,
		refsTotal: 0,
		errors: []
	};
	const validatedRefs = refs.filter((ref) => !hasUnresolvedInheritedFallbackProvider(config, ref, unresolvedPaths)).map((ref) => ({
		ref,
		error: validateModelRefSyntax(validationConfig, ref, unresolvedPaths)
	}));
	const syntaxFailures = validatedRefs.filter((entry) => Boolean(entry.error));
	const refsToResolve = validatedRefs.filter((entry) => !entry.error).map((entry) => entry.ref);
	const errors = syntaxFailures.map(({ ref, error }) => formatError(ref, error));
	if (refsToResolve.length === 0) return {
		refsChecked: syntaxFailures.length,
		refsTotal: refs.length,
		errors
	};
	let resolveModelRef = params.resolveModelRef;
	if (!resolveModelRef) try {
		resolveModelRef = await (params.createModelRefResolver ?? createRuntimeModelRefResolver)();
	} catch (cause) {
		const detail = modelEnvWasExpanded || Boolean(params.redactDependencyValues && refs.some((ref) => ref.dependency)) ? "model resolver setup failed" : cause instanceof Error ? cause.message : String(cause);
		return {
			refsChecked: syntaxFailures.length,
			refsTotal: refs.length,
			errors: [...errors, `Unable to validate changed model references before writing: ${detail}`]
		};
	}
	let refsChecked = syntaxFailures.length;
	for (const ref of refsToResolve) {
		let error;
		try {
			error = await resolveModelRef({
				config: validationConfig,
				ref
			});
			refsChecked += 1;
		} catch (cause) {
			const detail = cause instanceof Error ? cause.message : String(cause);
			errors.push(formatError(ref, `Unable to validate model reference: ${detail}`));
			continue;
		}
		if (!error) continue;
		errors.push(formatError(ref, error));
	}
	return {
		refsChecked,
		refsTotal: refs.length,
		errors
	};
}
//#endregion
//#region src/cli/config-cli-validation.ts
function formatInvalidConfigRepairHint(snapshot, doctorMessage) {
	return isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? formatPluginPackagingRuntimeOutputRecoveryHint() : `Run \`${formatCliCommand("openclaw doctor --fix")}\` ${doctorMessage}`;
}
function ensureValidConfigSnapshotForCli(snapshot, runtime, options = {}) {
	if (snapshot.valid) return;
	if (options.json) {
		writeRuntimeJson(runtime, {
			...formatCliJsonFailure(`OpenClaw config is invalid: ${shortenHomePath(snapshot.path)}`),
			issues: normalizeConfigIssues(snapshot.issues)
		});
		exitCliAfterOutput(runtime, 1);
	}
	runtime.error(`OpenClaw config is invalid: ${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 strictlyValidateConfigSnapshotForCli(snapshot, pluginMetadataSnapshot) {
	if (!snapshot.valid) return snapshot;
	const validated = validateConfigObjectRawWithPlugins(snapshot.sourceConfig, {
		semanticValidation: "strict",
		pluginMetadataSnapshot
	});
	const issues = validated.ok ? await collectConfigSecretProviderErrors({ config: snapshot.runtimeConfig }) : validated.issues;
	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 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) {
	const validated = validateConfigObjectRawWithPlugins(config, {
		semanticValidation: "strict",
		pluginMetadataSnapshot
	});
	if (validated.ok) return [];
	return formatConfigIssueLines(validated.issues, "-", { normalizeRoot: true }).map((message) => ({
		kind: "schema",
		message
	}));
}
function assertStrictConfigForMutation(config, pluginMetadataSnapshot) {
	const errors = collectStrictConfigErrors(config, pluginMetadataSnapshot);
	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;
}
function dedupeDryRunErrors(errors) {
	const deduped = [];
	const seen = /* @__PURE__ */ new Set();
	for (const error of errors) {
		const key = error.kind === "resolvability" ? `${error.kind}\u0000${error.ref ?? ""}\u0000${error.message}` : `${error.kind}\u0000${error.message}`;
		if (!seen.has(key)) {
			seen.add(key);
			deduped.push(error);
		}
	}
	return deduped;
}
/** 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;
	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);
			return { kind: "unchanged" };
		}
	}
	const modelCheck = await checkTouchedTextModelRefs({
		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.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));
	if (checksRefs) errors.push(...collectDryRunStaticErrorsForSkippedExecRefs({
		refs: skippedExecRefs,
		config
	}), ...await collectDryRunResolvabilityErrors({
		refs: refsToResolve,
		config
	}));
	const failures = dedupeDryRunErrors(errors);
	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
//#region src/cli/config-set-dryrun.ts
var ConfigSetDryRunValidationError = class extends Error {
	constructor(result) {
		super("config set dry-run validation failed");
		this.result = result;
		this.name = "ConfigSetDryRunValidationError";
	}
};
function printConfigDryRunResult(result, runtime, json) {
	if (!result.ok) {
		if (json) throw new ConfigSetDryRunValidationError(result);
		throw new Error(formatDryRunFailureMessage({
			errors: result.errors ?? [],
			skippedExecRefs: result.skippedExecRefs
		}));
	}
	if (json) {
		writeRuntimeJson(runtime, result);
		return;
	}
	if (!result.checks.schema && !result.checks.resolvability) runtime.log(info("Dry run note: value mode does not run schema/resolvability checks. Use --strict-json, builder flags, or batch mode to enable validation checks."));
	if (result.skippedExecRefs > 0) runtime.log(info(formatSkippedExecRefs(result.skippedExecRefs)));
	runtime.log(info(`Dry run successful: ${result.operations} update(s) validated against ${shortenHomePath(result.configPath)}.`));
}
function formatSkippedExecRefs(count) {
	return `Dry run note: skipped ${count} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during dry-run.`;
}
function formatDryRunFailureMessage(params) {
	const missingPathErrors = params.errors.filter((error) => error.kind === "missing-path");
	const schemaErrors = params.errors.filter((error) => error.kind === "schema");
	const resolveErrors = params.errors.filter((error) => error.kind === "resolvability");
	const modelErrors = params.errors.filter((error) => error.kind === "model");
	const lines = missingPathErrors.map((error) => error.message);
	if (schemaErrors.length > 0) lines.push("Dry run failed: config schema validation failed.", ...schemaErrors.map((error) => `- ${error.message}`));
	if (resolveErrors.length > 0) {
		lines.push(`Dry run failed: ${resolveErrors.length} SecretRef assignment(s) could not be resolved.`, ...resolveErrors.slice(0, 5).map((error) => `- ${error.ref ?? "<unknown-ref>"} -> ${error.message}`));
		if (resolveErrors.length > 5) lines.push(`- ... ${resolveErrors.length - 5} more`);
	}
	if (modelErrors.length > 0) lines.push("Dry run failed: model reference validation failed.", ...modelErrors.map((error) => `- ${error.message}`));
	if (params.skippedExecRefs > 0) lines.push(formatSkippedExecRefs(params.skippedExecRefs));
	return lines.join("\n");
}
//#endregion
//#region src/cli/config-cli-runner.ts
const GATEWAY_AUTH_MODE_PATH = [
	"gateway",
	"auth",
	"mode"
];
const PLUGIN_INSTALL_RECORD_PATH_PREFIX = ["plugins", "installs"];
function pathStartsWith(path, prefix) {
	return prefix.every((segment, index) => path[index] === segment);
}
function pathEquals(path, expected) {
	return path.length === expected.length && path.every((segment, index) => segment === expected[index]);
}
function valueHasAutoManagedChild(value, childPath) {
	let cursor = value;
	for (const segment of childPath) {
		if (!isRecord(cursor)) return false;
		if (!Object.hasOwn(cursor, segment)) return false;
		cursor = cursor[segment];
	}
	return cursor !== void 0;
}
function operationClobbersAncestorChild(operation, managedPath, merge) {
	if (operation.mutation === "delete") return true;
	const childPath = managedPath.slice(operation.requestedPath.length);
	return operation.mutation === "merge" || merge && operation.mutation !== "replace" ? valueHasAutoManagedChild(operation.value, childPath) : true;
}
function findAutoManagedMetaTargets(operations, merge) {
	const matches = [];
	const seen = /* @__PURE__ */ new Set();
	const record = (path) => {
		const key = toDotPath(path);
		if (!seen.has(key)) {
			seen.add(key);
			matches.push([...path]);
		}
	};
	for (const operation of operations) {
		if (AUTO_MANAGED_CONFIG_META_PATHS.some((path) => pathStartsWith(operation.requestedPath, path))) {
			record(operation.requestedPath);
			continue;
		}
		for (const managedPath of AUTO_MANAGED_CONFIG_META_PATHS) if (operation.requestedPath.length < managedPath.length && pathStartsWith(managedPath, operation.requestedPath) && operationClobbersAncestorChild(operation, managedPath, merge)) record(managedPath);
	}
	return matches;
}
function formatAutoManagedMetaError(paths) {
	const targets = paths.map(toDotPath);
	return [
		`${targets.length === 1 ? targets[0] : targets.join(", ")} is auto-managed by OpenClaw and cannot be edited; the value would be overwritten on the next config write.`,
		"",
		"These fields are stamped on every config write to record the OpenClaw version and timestamp that produced the file."
	].join("\n");
}
function pruneInactiveGatewayAuthCredentials(params) {
	const touchedMode = params.operations.some(({ requestedPath }) => pathEquals(requestedPath, GATEWAY_AUTH_MODE_PATH));
	const gateway = params.root.gateway;
	if (!touchedMode || !isRecord(gateway)) return [];
	const auth = gateway.auth;
	if (!isRecord(auth)) return [];
	const mode = typeof auth.mode === "string" ? auth.mode.trim() : "";
	const removedPaths = [];
	const remove = (key) => {
		if (Object.hasOwn(auth, key)) {
			delete auth[key];
			removedPaths.push(`gateway.auth.${key}`);
		}
	};
	if (mode === "token") remove("password");
	else if (mode === "password") remove("token");
	else if (mode === "trusted-proxy") {
		remove("token");
		remove("password");
	}
	return removedPaths;
}
function collectChangedLeafPaths(value, prefix) {
	if (!isRecord(value)) return [prefix];
	const entries = Object.entries(value);
	return entries.length === 0 ? [prefix] : entries.flatMap(([key, child]) => collectChangedLeafPaths(child, prefix ? `${prefix}.${key}` : key));
}
function expandActualChangedPaths(actualPaths, requestedPaths, before, after) {
	const expanded = /* @__PURE__ */ new Set();
	for (const actualPath of actualPaths) {
		const descendants = requestedPaths.filter((requested) => requested !== actualPath && requested.startsWith(`${actualPath}.`));
		if (descendants.length > 0) {
			descendants.forEach((path) => expanded.add(path));
			continue;
		}
		const path = actualPath === "<root>" ? [] : actualPath.split(".");
		const beforeValue = getAtPath(before, path);
		const afterValue = getAtPath(after, path);
		const changedValue = beforeValue.found && !afterValue.found ? beforeValue : afterValue;
		(beforeValue.found !== afterValue.found ? collectChangedLeafPaths(changedValue.value, actualPath) : [actualPath]).forEach((entry) => expanded.add(entry));
	}
	return [...expanded];
}
function configApplyHintForOperations(operations, beforeConfig, afterConfig) {
	const requestedPaths = operations.map(({ requestedPath }) => toDotPath(requestedPath));
	const paths = expandActualChangedPaths(diffConfigPaths(beforeConfig, afterConfig), requestedPaths, beforeConfig, afterConfig);
	if (paths.length === 0) return "No gateway restart needed.";
	if (paths.some((path) => path === "plugins.entries" || path.startsWith("plugins.entries."))) return "Restart the gateway to apply.";
	const plan = buildGatewayReloadPlan(paths, { candidateConfig: afterConfig });
	if (plan.restartGateway || plan.hotReasons.length > 0 && resolveGatewayReloadSettings(afterConfig).mode === "off") return "Restart the gateway to apply.";
	return plan.hotReasons.length > 0 ? "Change will apply without restarting the gateway." : "No gateway restart needed.";
}
async function loadMutationSchema() {
	try {
		return (await readBestEffortRuntimeConfigSchema()).schema;
	} catch {
		return;
	}
}
function assertConfigSetCurrentExpectation(params) {
	const current = getAtPath(params.authoredConfig, params.operation.setPath);
	if (!(params.expectation.kind === "absent" ? !current.found : current.found && isDeepStrictEqual(current.value, params.expectation.value))) throw new ConfigMutationConflictError("conditional config set expectation did not match the authored config", { retryable: false });
}
function assertConfigSetCurrentExpectationPath(params) {
	if (!pathEquals(params.operation.requestedPath, params.writePath)) throw new Error("conditional config set requires a direct, non-redirected config path");
}
async function runConfigOperations(params) {
	const { runtime, operations, options } = params;
	if (operations.some(({ requestedPath }) => pathStartsWith(requestedPath, PLUGIN_INSTALL_RECORD_PATH_PREFIX))) throw new Error(formatPluginInstallConfigSetError());
	const autoManagedTargets = findAutoManagedMetaTargets(operations, options.merge);
	if (autoManagedTargets.length > 0) throw new Error(formatAutoManagedMetaError(autoManagedTargets));
	const mutationStart = await loadValidConfigForWrite(runtime);
	const { snapshot } = mutationStart;
	const currentExpectation = params.currentExpectation;
	let assertCurrentExpectation;
	if (currentExpectation) {
		const expectationOperation = operations[0];
		if (!expectationOperation) throw new Error("conditional config set requires one resolved operation");
		assertCurrentExpectation = () => {
			assertConfigSetCurrentExpectation({
				authoredConfig: snapshot.resolved,
				operation: expectationOperation,
				expectation: currentExpectation
			});
		};
	}
	const next = structuredClone(snapshot.resolved);
	const currentConfig = normalizeConfigMutationModelRefs(snapshot.resolved);
	const mutationSchema = await loadMutationSchema();
	const roster = new ConfigMutationAgentRoster(next, snapshot.sourceConfigBeforeMigrations);
	let unsetPaths = [];
	const explicitSetPaths = [];
	const appliedOperations = [];
	const recordOperation = (operation) => {
		const writePath = roster.writePath(operation.setPath);
		const setPath = normalizeConfigMutationExplicitSetPath(operation.setPath[1] === "list" && writePath[1] === "entries" && writePath.length === 4 && writePath[3] === "id" ? writePath.slice(0, 3) : writePath);
		appliedOperations.push({
			...operation,
			setPath
		});
		return writePath;
	};
	for (const operation of operations) {
		const merge = operation.mutation === "merge" || options.merge && operation.mutation !== "replace";
		roster.prepare(operation, Boolean(merge));
		if (currentExpectation) assertConfigSetCurrentExpectationPath({
			operation,
			writePath: roster.writePath(operation.setPath)
		});
		if (operation.mutation === "delete") {
			const writePath = recordOperation(operation);
			const unsetResult = unsetAtPath(next, operation.setPath);
			if (!unsetResult.removed && operation.inputMode === "unset") {
				const requestedPath = formatConfigSetPath(operation.requestedPath, operation.pathTokens);
				const runtimeOnly = getAtPath(snapshot.runtimeConfig, operation.setPath).found;
				const message = formatConfigUnsetMissingPathMessage({
					path: requestedPath,
					runtimeOnly
				});
				if (options.dryRun && options.json) throw new ConfigSetDryRunValidationError({
					ok: false,
					operations: 1,
					configPath: snapshot.path,
					inputModes: ["unset"],
					checks: {
						schema: false,
						resolvability: false,
						resolvabilityComplete: false
					},
					refsChecked: 0,
					skippedExecRefs: 0,
					errors: [{
						kind: "missing-path",
						message: runtimeOnly ? message : `Config path not found: ${requestedPath}. Nothing was changed.`
					}]
				});
				if (!options.dryRun) assertStrictConfigForMutation(currentConfig, mutationStart.writeOptions.basePluginMetadataSnapshot);
				throw new Error(message);
			}
			if (!unsetResult.removed || unsetResult.leafContainer !== "array") unsetPaths.push(writePath);
			continue;
		}
		const pathOptions = {
			numericObjectKeys: params.successMode === "patch",
			pathTokens: operation.pathTokens,
			quotedNumericSegments: operation.quotedNumericSegments,
			schema: mutationSchema
		};
		if (merge) mergeAtPath(next, operation.setPath, operation.value, pathOptions);
		else {
			assertNonDestructiveReplacement({
				root: next,
				path: operation.setPath,
				value: operation.value,
				allowReplace: options.replace || operation.mutation === "replace"
			});
			setAtPath(next, operation.setPath, operation.value, pathOptions);
		}
		explicitSetPaths.push(recordOperation(operation));
	}
	roster.finish();
	unsetPaths = unsetPaths.filter((path) => !getAtPath(next, path).found);
	const removedGatewayAuthPaths = pruneInactiveGatewayAuthCredentials({
		root: next,
		operations
	});
	let nextConfig = normalizeConfigMutationModelRefs(next);
	const normalizedExplicitSetPaths = explicitSetPaths.map(normalizeConfigMutationExplicitSetPath);
	if (options.dryRun) nextConfig = prepareConfigWriteTopology({
		snapshot,
		pluginMetadataSnapshot: mutationStart.writeOptions.basePluginMetadataSnapshot,
		nextConfig,
		options: { explicitSetPaths: normalizedExplicitSetPaths },
		unsetPaths: resolveManagedUnsetPathsForWrite(unsetPaths),
		env: process.env
	}).nextConfig;
	const validation = await validateConfigMutation({
		config: nextConfig,
		previousConfig: currentConfig,
		operations: appliedOperations,
		options,
		configPath: snapshot.path,
		unchanged: params.successMode === "set" && isDeepStrictEqual(currentConfig, nextConfig),
		pluginMetadataSnapshot: mutationStart.writeOptions.basePluginMetadataSnapshot
	});
	if (validation.kind === "dry-run") {
		printConfigDryRunResult(validation.result, runtime, options.json);
		return;
	}
	if (validation.kind === "unchanged") {
		assertCurrentExpectation?.();
		runtime.log(info("No change"));
		return;
	}
	await replaceConfigFile({
		nextConfig,
		snapshot,
		...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {},
		writeOptions: {
			...mutationStart.writeOptions,
			auditOrigin: "cli",
			...assertCurrentExpectation || params.beforePersistentApply ? { assertConfigPathForWrite: () => {
				mutationStart.writeOptions.assertConfigPathForWrite?.();
				assertCurrentExpectation?.();
				params.beforePersistentApply?.();
			} } : {},
			...unsetPaths.length > 0 ? { unsetPaths } : {},
			...normalizedExplicitSetPaths.length > 0 ? { explicitSetPaths: normalizedExplicitSetPaths } : {}
		}
	});
	if (removedGatewayAuthPaths.length > 0) runtime.log(info(`Removed inactive ${removedGatewayAuthPaths.join(", ")} for gateway.auth.mode=${nextConfig.gateway?.auth?.mode ?? "<unset>"}.`));
	const hint = configApplyHintForOperations(operations, currentConfig, nextConfig);
	if (params.successMode === "set" && operations.length === 1) {
		const operation = operations[0];
		const action = operation?.mutation === "delete" ? "Removed" : "Updated";
		const requestedPath = formatConfigSetPath(operation?.requestedPath ?? [], operation?.pathTokens, nextConfig);
		runtime.log(info(`${action} ${requestedPath}. ${hint}`));
	} else if (params.successMode === "set") runtime.log(info(`Updated ${operations.length} config paths. ${hint}`));
	else runtime.log(info(`Applied ${operations.length} config update(s). ${hint}`));
}
function handleConfigMutationError(params) {
	if (params.err instanceof ExitError) throw params.err;
	const isConflict = params.err instanceof ConfigMutationConflictError;
	const detail = formatErrorMessage(params.err);
	const message = isConflict ? `The config file changed while this command was writing (${detail}), so nothing was changed. Re-run the same command to pick up the new file and try again.` : detail;
	if (params.options.dryRun && params.options.json) {
		if (params.err instanceof ConfigSetDryRunValidationError) {
			writeRuntimeJson(params.runtime, params.err.result);
			exitCliAfterOutput(params.runtime, 1);
		}
		const result = {
			ok: false,
			operations: 0,
			configPath: resolveConfigPath(),
			inputModes: [],
			checks: {
				schema: false,
				resolvability: false,
				resolvabilityComplete: false
			},
			refsChecked: 0,
			skippedExecRefs: 0,
			errors: [{
				kind: isConflict ? "conflict" : "schema",
				message
			}]
		};
		writeRuntimeJson(params.runtime, result);
		params.runtime.error(danger(message));
		exitCliAfterOutput(params.runtime, 1);
	}
	params.runtime.error(danger(message));
	exitCliAfterOutput(params.runtime, 1);
}
//#endregion
//#region src/cli/config-cli.ts
const CONFIG_SET_DESCRIPTION = [
	"Set config values by path (value mode, ref/provider builder mode, or batch JSON mode).",
	"Examples:",
	formatCliCommand("openclaw config set gateway.port 19001 --strict-json"),
	formatCliCommand("openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN"),
	formatCliCommand("openclaw config set secrets.providers.vault --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json"),
	formatCliCommand("openclaw config set --batch-file ./config-set.batch.json --dry-run")
].join("\n");
const CONFIG_PATCH_DESCRIPTION = [
	"Patch config from a JSON5 object in one validated write.",
	"Objects merge recursively, arrays/scalars replace, and null deletes a path.",
	"Examples:",
	formatCliCommand("openclaw config patch --file ./openclaw.patch.json5 --dry-run"),
	formatCliCommand("openclaw config patch --stdin")
].join("\n");
async function runConfigSet(opts) {
	const runtime = opts.runtime ?? defaultRuntime;
	try {
		const currentExpectation = parseConfigSetCurrentExpectation(opts.cliOptions);
		const operations = buildConfigSetOperations({
			path: opts.path,
			value: opts.value,
			opts: opts.cliOptions
		});
		if (currentExpectation && operations.length !== 1) throw new Error("config set mode error: conditional expectations require exactly one resolved operation.");
		await runConfigOperations({
			runtime,
			operations,
			options: opts.cliOptions,
			successMode: "set",
			...currentExpectation ? { currentExpectation } : {},
			...opts.beforePersistentApply ? { beforePersistentApply: opts.beforePersistentApply } : {}
		});
	} catch (err) {
		handleConfigMutationError({
			err,
			runtime,
			options: opts.cliOptions
		});
	}
}
async function runConfigPatch(opts) {
	const runtime = opts.runtime ?? defaultRuntime;
	try {
		if (opts.cliOptions.allowExec && !opts.cliOptions.dryRun) throw configPatchModeError("--allow-exec requires --dry-run.");
		if (opts.cliOptions.json && !opts.cliOptions.dryRun) throw configPatchModeError("--json requires --dry-run.");
		await runConfigOperations({
			runtime,
			operations: await readConfigPatchOperations(opts.cliOptions),
			options: opts.cliOptions,
			successMode: "patch"
		});
	} catch (err) {
		handleConfigMutationError({
			err,
			runtime,
			options: opts.cliOptions
		});
	}
}
async function runConfigGet(opts) {
	const runtime = opts.runtime ?? defaultRuntime;
	try {
		const parsedPath = parseConcreteConfigPath(opts.path);
		const { snapshot, pluginMetadataSnapshot } = await readConfigFileSnapshotWithPluginMetadata({ observe: false });
		ensureValidConfigSnapshotForCli(snapshot, runtime, { json: opts.json });
		if (!pluginMetadataSnapshot) throw new Error("Config plugin metadata unavailable; refusing to display config values.");
		const { schema, uiHints } = buildRuntimeConfigSchemaFromRegistry(pluginMetadataSnapshot.manifestRegistry, snapshot.sourceConfig);
		const res = getAtPath(redactConfigObject(snapshot.config, uiHints), parsedPath);
		if (!res.found) {
			const message = isConfigSchemaPath(schema, parsedPath) ? `Config path is valid but unset: ${opts.path}. The runtime default applies until you set an authored value with ${formatCliCommand(`openclaw config set ${quoteCliArg(opts.path)} <value>`)}.` : `Unknown config path: ${opts.path}. Run ${formatCliCommand("openclaw config schema")} to inspect valid paths.`;
			if (opts.json) {
				writeRuntimeJson(runtime, formatCliJsonFailure(message));
				exitCliAfterOutput(runtime, 1);
			}
			runtime.error(danger(message));
			exitCliAfterOutput(runtime, 1);
		}
		if (opts.json) writeRuntimeJson(runtime, res.value ?? null);
		else if (typeof res.value === "string" || typeof res.value === "number" || typeof res.value === "boolean") writeRuntimeStdout(runtime, `${String(res.value)}\n`);
		else writeRuntimeJson(runtime, res.value ?? null);
	} catch (err) {
		if (err instanceof ExitError) throw err;
		if (opts.json) {
			writeRuntimeJson(runtime, formatCliJsonFailure(err));
			exitCliAfterOutput(runtime, 1);
		}
		runtime.error(danger(formatErrorMessage(err)));
		exitCliAfterOutput(runtime, 1);
	}
}
async function runConfigUnset(opts) {
	const runtime = opts.runtime ?? defaultRuntime;
	const cliOptions = opts.cliOptions ?? {};
	try {
		if (cliOptions.allowExec && !cliOptions.dryRun) throw new Error("--allow-exec can only be used with --dry-run.");
		if (cliOptions.json && !cliOptions.dryRun) throw new Error("--json can only be used with --dry-run.");
		const pathTokens = parseConcreteConfigPathTokens(opts.path);
		await runConfigOperations({
			runtime,
			operations: [buildUnsetOperation(pathTokens.map(String), pathTokens)],
			options: cliOptions,
			successMode: "set"
		});
	} catch (err) {
		handleConfigMutationError({
			err,
			runtime,
			options: cliOptions
		});
	}
}
async function runConfigFile(opts) {
	const runtime = opts.runtime ?? defaultRuntime;
	try {
		const path = resolveConfigPath();
		if (opts.json) {
			writeRuntimeJson(runtime, { path });
			return;
		}
		writeRuntimeStdout(runtime, `${path}\n`);
	} catch (err) {
		runtime.error(danger(formatErrorMessage(err)));
		exitCliAfterOutput(runtime, 1);
	}
}
async function runConfigSchema(opts = {}) {
	const runtime = opts.runtime ?? defaultRuntime;
	try {
		const schema = structuredClone((await readBestEffortRuntimeConfigSchema()).schema);
		schema.properties = {
			$schema: { type: "string" },
			...schema.properties
		};
		writeRuntimeJson(runtime, schema);
	} catch (err) {
		runtime.error(danger(`Config schema error: ${formatErrorMessage(err)}`));
		exitCliAfterOutput(runtime, 1);
	}
}
async function runConfigValidate(opts = {}) {
	const runtime = opts.runtime ?? defaultRuntime;
	let outputPath = CONFIG_PATH ?? "openclaw.json";
	try {
		const read = await readConfigFileSnapshotWithPluginMetadata({ observe: false });
		const snapshot = await strictlyValidateConfigSnapshotForCli(read.snapshot, read.pluginMetadataSnapshot);
		outputPath = snapshot.path;
		const shortPath = shortenHomePath(outputPath);
		if (!snapshot.exists) {
			if (opts.json) writeRuntimeJson(runtime, {
				...formatCliJsonFailure("file not found"),
				valid: false,
				path: outputPath
			}, 0);
			else {
				runtime.error(danger(`Config file not found: ${shortPath}`));
				runtime.error(`Create one with ${formatCliCommand("openclaw onboard")} or run ${formatCliCommand("openclaw doctor --fix")}.`);
			}
			exitCliAfterOutput(runtime, 1);
		}
		if (!snapshot.valid) {
			const issues = normalizeConfigIssues(snapshot.issues);
			if (opts.json) writeRuntimeJson(runtime, {
				...formatCliJsonFailure(`OpenClaw config is invalid: ${shortPath}`),
				valid: false,
				path: outputPath,
				issues
			});
			else {
				runtime.error(danger(`OpenClaw config is invalid: ${shortPath}`));
				for (const line of renderConfigValidationIssueLines(snapshot, danger("×"))) runtime.error(`  ${line}`);
				runtime.error("");
				runtime.error(formatInvalidConfigRepairHint(snapshot, "to repair, or fix the keys above manually."));
				runtime.error(`Inspect with ${formatCliCommand("openclaw config validate")}.`);
			}
			exitCliAfterOutput(runtime, 1);
		}
		const warnings = normalizeConfigIssues(snapshot.warnings);
		if (opts.json) writeRuntimeJson(runtime, {
			valid: true,
			path: outputPath,
			warnings
		}, 0);
		else {
			runtime.log(success(`Config valid: ${shortPath}`));
			if (warnings.length > 0) {
				runtime.log(warn(`${warnings.length} warning(s):`));
				for (const line of formatConfigIssueLines(warnings, warn("!"), { normalizeRoot: true })) runtime.log(`  ${line}`);
			}
		}
	} catch (err) {
		if (err instanceof ExitError) throw err;
		if (opts.json) writeRuntimeJson(runtime, {
			...formatCliJsonFailure(err),
			valid: false,
			path: outputPath
		}, 0);
		else runtime.error(danger(`Config validation error: ${formatErrorMessage(err)}`));
		exitCliAfterOutput(runtime, 1);
	}
}
function collectOption(value, previous) {
	return [...previous, value];
}
function registerConfigCli(program) {
	const cmd = program.command("config").description("Non-interactive config helpers (get/set/patch/unset/file/schema/validate). Run without subcommand for guided setup.").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/config", "docs.openclaw.ai/cli/config")}\n`).option("--section <section>", "Configuration sections for guided setup (repeatable). Use with no subcommand.", collectOption, []).action(async (opts) => {
		const { configureCommandFromSectionsArg } = await import("./configure-CH-QPBaz.js");
		await configureCommandFromSectionsArg(opts.section, defaultRuntime);
	});
	setCommandJsonMode(cmd, "output", ({ argv }) => isConfigMachineOutput(argv));
	cmd.command("get").description("Get a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").option("--json", "Output JSON", false).action(async (path, opts) => {
		await runConfigGet({
			path,
			json: Boolean(opts.json)
		});
	});
	setCommandJsonMode(cmd.command("set"), "parse-only", ({ argv }) => isConfigSetJsonParseOnly(argv)).description(CONFIG_SET_DESCRIPTION).argument("[path]", "Config path (dot or bracket notation)").argument("[value]", "Value (JSON/JSON5 or raw string)").option("--strict-json", "Strict JSON parsing (error instead of raw string fallback)", false).option("--json", "Legacy alias for --strict-json", false).option("--expect-current-absent", "Write only when the authored path is absent", false).option("--expect-current-json <json>", "Write only when the authored path exactly matches this strict JSON value").option("--dry-run", "Validate changes without writing openclaw.json (checks run in builder/json/batch modes; exec SecretRefs are skipped unless --allow-exec is set)", false).option("--allow-exec", "Dry-run only: allow exec SecretRef resolvability checks (may execute provider commands)", false).option("--merge", "Merge object/map values instead of replacing the target path", false).option("--replace", "Allow full replacement of protected map/list paths such as agents.defaults.models", false).option("--ref-provider <alias>", "SecretRef builder: provider alias").option("--ref-source <source>", "SecretRef builder: source (env|file|exec|store)").option("--ref-id <id>", "SecretRef builder: ref id").option("--provider-source <source>", "Provider builder: source (env|file|exec|store)").option("--provider-allowlist <envVar>", "Provider builder (env): allowlist entry (repeatable)", collectOption, []).option("--provider-path <path>", "Provider builder (file): path").option("--provider-mode <mode>", "Provider builder (file): mode (singleValue|json)").option("--provider-timeout-ms <ms>", "Provider builder (file|exec): timeout ms").option("--provider-max-bytes <bytes>", "Provider builder (file): max bytes").option("--provider-command <path>", "Provider builder (exec): absolute command path").option("--provider-arg <arg>", "Provider builder (exec): command arg (repeatable)", collectOption, []).option("--provider-no-output-timeout-ms <ms>", "Provider builder (exec): no-output timeout ms").option("--provider-max-output-bytes <bytes>", "Provider builder (exec): max output bytes").option("--provider-json-only", "Provider builder (exec): require JSON output", false).option("--provider-env <key=value>", "Provider builder (exec): env assignment (repeatable)", collectOption, []).option("--provider-pass-env <envVar>", "Provider builder (exec): pass host env var (repeatable)", collectOption, []).option("--provider-trusted-dir <path>", "Provider builder (exec): trusted directory (repeatable)", collectOption, []).option("--batch-json <json>", "Batch mode: JSON array of set operations").option("--batch-file <path>", "Batch mode: read JSON array of set operations from file").action(async (path, value, opts) => {
		await runConfigSet({
			path,
			value,
			cliOptions: opts
		});
	});
	cmd.command("patch").description(CONFIG_PATCH_DESCRIPTION).option("--file <path>", "Read a JSON5 config patch object from file").option("--stdin", "Read a JSON5 config patch object from stdin", false).option("--dry-run", "Validate changes without writing openclaw.json (checks schema and SecretRef resolvability; exec SecretRefs are skipped unless --allow-exec is set)", false).option("--allow-exec", "Dry-run only: allow exec SecretRef resolvability checks (may execute provider commands)", false).option("--json", "Output dry-run result as JSON", false).option("--replace-path <path>", "Replace the object or array at this dot/bracket path instead of recursively applying it (repeatable)", collectOption, []).action(async (opts) => {
		await runConfigPatch({ cliOptions: opts });
	});
	cmd.command("unset").description("Remove a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").option("--dry-run", "validate the removal without writing the config file").option("--allow-exec", "allow exec SecretRef providers during --dry-run").option("--json", "print dry-run result as JSON").action(async (path, options) => {
		await runConfigUnset({
			path,
			cliOptions: options
		});
	});
	cmd.command("file").description("Print the active config file path").option("--json", "Output JSON", false).action((opts) => runConfigFile(opts));
	cmd.command("schema").description("Print the JSON schema for openclaw.json").option("--json", "Output JSON", false).action(runConfigSchema);
	cmd.command("validate").description("Validate the current config against the schema without starting the gateway").option("--json", "Output validation result as JSON", false).action(async (opts) => {
		await runConfigValidate({ json: Boolean(opts.json) });
	});
}
//#endregion
export { parseConcreteConfigPath as parseConfigSetPath, registerConfigCli, runConfigGet, runConfigPatch, runConfigSet, runConfigUnset };