UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

3,363 lines 148 kB
import { s as asFiniteNumber, u as asPositiveFiniteNumber } from "./number-coercion-CLj0HTDM.js";
import { t as sanitizeForLog } from "./ansi-DrXAcdMD.js";
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { a as asOptionalRecord, c as isRecord, n as asNullableObjectRecord } from "./record-coerce-DItp3I4t.js";
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { t as isPlainObject } from "./plain-object-5a0EzLzX.js";
import { t as isBlockedObjectKey } from "./prototype-keys-CuYw53fZ.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import "./utils-P__uGsPB.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { N as materializeModelPolicyAllowlist, P as createModelPolicyRefValidator, a as listAgentEntriesWithSource, c as resolveAgentConfig, g as resolveAmbientOwnerAgentId, i as listAgentEntries, m as resolveAgentWorkspaceDir, o as listAgentIds, w as tryResolveAmbientOwnerAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { r as normalizeProviderId } from "./provider-id-DMd-TDFp.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js";
import "./session-key-BnWWjqNc.js";
import { i as tryGetLegacyDefaultAgentId, t as inheritLegacyDefaultAgentId } from "./legacy.default-agent-owner-BGwEdQRe.js";
import { t as parseConfigPathArrayIndex } from "./path-array-index-CvEcUJa-.js";
import { s as coerceSecretRef, v as resolveSecretInputRef } from "./types.secrets-kC0nOetj.js";
import { n as replaceFileAtomic } from "./replace-file-BAJ-TWzD.js";
import { d as resolveSecretRefProviderSourceMismatch } from "./ref-contract-D92DqQ-r.js";
import { n as isBuiltInModelProviderOverlayId } from "./model-provider-config-DX4Bzd5F.js";
import { E as SecretRefSchema, R as evaluateDmPolicyAllowFromDependency } from "./zod-schema.core-D6k6NKKA.js";
import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js";
import { a as summarizeAllowedValues, i as appendAllowedValuesHint, n as validateJsonSchemaValue, r as validatePluginSchemaValue } from "./schema-validator-CLfGeb79.js";
import { r as normalizeConfiguredProviderCatalogModelId, t as collectManifestModelIdNormalizationPolicies } from "./provider-model-id-normalization-BFjPahr6.js";
import { n as resolveManifestCommandAliasOwnerInRegistry } from "./manifest-command-aliases-DKcke8h_.js";
import { t as VERSION } from "./version-v1kuAkGj.js";
import { t as createDedupeCache } from "./dedupe-gst1CUro.js";
import { o as writeConfigMachineState } from "./config-machine-state-BCereLZr.js";
import { a as GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA } from "./ids-BVZRYG0I.js";
import { r as hasKind } from "./slots-CQdAEuat.js";
import { a as isRetiredPluginId, d as resolveEffectivePluginActivationState, i as isExplicitPluginDisableMarker, l as normalizePluginsConfig, n as createPluginActivationSource, p as resolveMemorySlotDecision, s as normalizePluginId } from "./config-state-BkU1frVq.js";
import { t as isPluginEnabledByDefaultForPlatform } from "./default-enablement-CEIbpabL.js";
import { b as resolveOfficialExternalPluginInstallSources, i as getOfficialExternalPluginCatalogEntry, n as getOfficialExternalChannelHostSchemaAllOf, r as getOfficialExternalChannelSecretContract } from "./official-external-plugin-catalog-Dzu7dwBN.js";
import { i as loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader-SXWwf_BU.js";
import { r as shouldWarnOnTouchedVersion } from "./version-BXayNebU.js";
import { r as collectConfiguredModelRefs } from "./configured-model-refs-DEXTV_K3.js";
import { t as isPathCaseInsensitive } from "./path-case-go_nTKsI.js";
import { i as materializeLegacyDefaultAgentRoles, t as migratePersistedImplicitMainRoster } from "./legacy.roster-CQ9vDbV2.js";
import { n as planManifestModelCatalogSuppressions } from "./manifest-planner-bVm3dG3U.js";
import { o as getRuntimeConfigSnapshot } from "./runtime-snapshot-BaQikjTR.js";
import "./defaults-CdX9UGcX.js";
import { r as normalizeAgentModelSelectionForConfig, t as normalizeAgentModelMapForConfig } from "./model-input-BuGMCNOz.js";
import "./agent-scope-DbtJyKUL.js";
import { n as shouldSuppressMissingCodexPluginDiagnostics } from "./codex-plugin-diagnostics-BiW7Kshh.js";
import { h as resolveConfigSnapshotHash, l as parseConfigJson5, o as hashConfigRaw } from "./io.read-helpers-ZKp-UiGx.js";
import { t as ConfigMutationConflictError } from "./mutation-conflict-Be0wSyDG.js";
import { t as resolveConfigWidePluginManifestRegistry } from "./io.plugin-metadata-BruF5izX.js";
import "./model-catalog-BiOJ2EJd.js";
import { i as listChannelIdsForOwnershipMigration } from "./channel-presence-policy-_eDSlojg.js";
import { t as resolveWebSearchInstallCatalogEntries } from "./web-search-install-catalog-mZmYPP1I.js";
import { n as discoverConfigSecretTargets } from "./target-registry-query-qVhyD-Hq.js";
import "./target-registry-BHDUOLUU.js";
import { g as cloneSchema, i as NodeHostMcpServerNameSchema, r as McpServerNameSchema, t as OpenClawSchema } from "./zod-schema-DnirJMRA.js";
import { s as ChannelHeartbeatVisibilitySchema } from "./zod-schema.channels-config-atVR1nln.js";
import { t as resolvePluginActivationSourceConfig } from "./activation-source-config-DnARnJdS.js";
import { t as canStartConfiguredChannelPlugin } from "./channel-startup-policy-B767T942.js";
import { a as resolveConfiguredChannelAutoEnableCandidates, n as materializePluginAutoEnableCandidatesInternal } from "./plugin-auto-enable.materialize-8JtnUPNO.js";
import { t as mergeModelCost } from "./model-cost-BNQWuFdo.js";
import { r as resolveBundledProviderPolicySurface } from "./provider-public-artifacts-Bone7gkJ.js";
import { n as normalizeTalkConfig } from "./talk-DRVl_bf5.js";
import { i as normalizeTrustedSafeBinDirs, u as normalizeSafeBinProfileFixtures } from "./exec-safe-bin-trust-BL7TeFlZ.js";
import { a as resolveChannelDmAllowFrom, o as resolveChannelDmPolicy } from "./dm-access-DF6nqjVk.js";
import { f as isLoopbackIpAddress, i as isCanonicalDottedDecimalIPv4 } from "./ip-BkT2Is0E.js";
import { n as resolveSandboxScope, t as resolveSandboxDockerEnv } from "./config-contract-CFOz6uqW.js";
import { n as getContainerEnvFileEntryIssue } from "./container-env-file-BC1o9FW_.js";
import { i as isPathWithinRoot, n as isAvatarDataUrl, o as isWindowsAbsolutePath, r as isAvatarHttpUrl, t as hasAvatarUriScheme } from "./avatar-policy-D8FQ_Wte.js";
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import path from "node:path";
import os from "node:os";
//#region src/config/agent-dirs.ts
/** Error thrown when multiple configured agents resolve to the same state directory. */
var DuplicateAgentDirError = class extends Error {
	constructor(duplicates) {
		super(formatDuplicateAgentDirError(duplicates));
		this.name = "DuplicateAgentDirError";
		this.duplicates = duplicates;
	}
};
function realpathAgentDir(agentDir, seen = /* @__PURE__ */ new Set()) {
	const resolved = path.resolve(agentDir);
	if (seen.has(resolved)) return resolved;
	seen.add(resolved);
	const missingSegments = [];
	let cursor = resolved;
	for (;;) try {
		return path.join(fs.realpathSync.native(cursor), ...missingSegments.toReversed());
	} catch (error) {
		const code = error.code;
		if (code !== "ENOENT" && code !== "ENOTDIR") return resolved;
		try {
			if (fs.lstatSync(cursor).isSymbolicLink()) {
				const target = path.resolve(path.dirname(cursor), fs.readlinkSync(cursor));
				return realpathAgentDir(path.join(target, ...missingSegments.toReversed()), seen);
			}
		} catch {}
		const parent = path.dirname(cursor);
		if (parent === cursor) return resolved;
		missingSegments.push(path.basename(cursor));
		cursor = parent;
	}
}
function canonicalizeAgentDir(agentDir) {
	const resolved = realpathAgentDir(agentDir);
	return isPathCaseInsensitive(resolved) ? normalizeLowercaseStringOrEmpty(resolved) : resolved;
}
function collectReferencedAgentIds(cfg) {
	const ids = /* @__PURE__ */ new Set();
	const agents = listAgentEntries(cfg);
	const defaultAgentId = agents.find((agent) => agent?.default)?.id;
	if (defaultAgentId) ids.add(normalizeAgentId(defaultAgentId));
	for (const entry of agents) if (entry?.id) ids.add(normalizeAgentId(entry.id));
	const bindings = cfg.bindings;
	if (Array.isArray(bindings)) for (const binding of bindings) {
		const id = binding?.agentId;
		if (typeof id === "string" && id.trim()) ids.add(normalizeAgentId(id));
	}
	return [...ids];
}
function resolveEffectiveAgentDir(cfg, agentId, deps) {
	const id = normalizeAgentId(agentId);
	const trimmed = (resolveAgentConfig(cfg, id)?.agentDir)?.trim();
	const env = deps?.env ?? process.env;
	if (trimmed) return resolveUserPath(trimmed, env, deps?.homedir);
	const root = resolveStateDir(env, deps?.homedir);
	return path.join(root, "agents", id, "agent");
}
/** Finds agent ids whose effective agentDir would share auth/session state. */
function findDuplicateAgentDirs(cfg, deps) {
	const byDir = /* @__PURE__ */ new Map();
	for (const agentId of collectReferencedAgentIds(cfg)) {
		const agentDir = resolveEffectiveAgentDir(cfg, agentId, deps);
		const key = canonicalizeAgentDir(agentDir);
		const entry = byDir.get(key);
		if (entry) entry.agentIds.push(agentId);
		else byDir.set(key, {
			agentDir,
			agentIds: [agentId]
		});
	}
	return [...byDir.values()].filter((v) => v.agentIds.length > 1);
}
/** Formats duplicate agentDir conflicts with the remediation operators should take. */
function formatDuplicateAgentDirError(dups) {
	return [
		"Duplicate agentDir detected (multi-agent config).",
		"Each agent must have a unique agentDir; sharing it causes auth/session state collisions and token invalidation.",
		"",
		"Conflicts:",
		...dups.map((d) => `- ${d.agentDir}: ${d.agentIds.map((id) => `"${id}"`).join(", ")}`),
		"",
		"Fix: remove the shared agents.entries.*.agentDir override (or give each agent its own directory).",
		"Auth profiles live in each agent's SQLite store, so a shared agentDir is not how credentials are shared: give each agent its own directory and either leave its store empty to inherit the main agent's profiles, or log it in with `openclaw models auth login`."
	].join("\n");
}
//#endregion
//#region src/config/io.state.ts
const CONFIG_IO_WARNING_CACHE_MAX_SIZE = 4096;
const loggedInvalidConfigs = createDedupeCache({
	ttlMs: 0,
	maxSize: CONFIG_IO_WARNING_CACHE_MAX_SIZE
});
const loggedConfigWarningFingerprints = /* @__PURE__ */ new Map();
const warnedFutureTouchedVersions = createDedupeCache({
	ttlMs: 0,
	maxSize: CONFIG_IO_WARNING_CACHE_MAX_SIZE
});
const autoOwnerDisplaySecretByPath = /* @__PURE__ */ new Map();
/** Retains a warning fingerprint as most-recently used while enforcing the shared bound. */
function setBoundedConfigIoWarningEntry(map, key, value) {
	map.delete(key);
	map.set(key, value);
	pruneMapToMaxSize(map, CONFIG_IO_WARNING_CACHE_MAX_SIZE);
}
//#endregion
//#region src/config/io.meta.ts
/** Metadata keys automatically stamped on config writes. */
const AUTO_MANAGED_CONFIG_META_PATHS = [["meta", "lastTouchedVersion"], [
	"meta",
	"migrations",
	"modelPolicyAllowlist"
]];
function stampConfigWriteMetadata(cfg, _now = (/* @__PURE__ */ new Date()).toISOString(), version = VERSION, previousConfig) {
	const migrationStamped = previousConfig === void 0 ? cfg : materializeModelPolicyAllowlist(cfg, previousConfig).config;
	return {
		...migrationStamped,
		meta: {
			...migrationStamped.meta,
			lastTouchedVersion: version
		}
	};
}
/** Persist machine-owned metadata only after the matching config file commit succeeds. */
function recordConfigWriteMetadata(now = (/* @__PURE__ */ new Date()).toISOString(), _version = VERSION) {
	writeConfigMachineState("config.lastTouchedAt", now);
}
//#endregion
//#region src/config/io.write-safety.ts
function assertBaseSnapshotStillCurrent(snapshot, configPath, ioFs) {
	if (snapshot.path !== configPath) throw new ConfigMutationConflictError("config path changed since last load", { retryable: false });
	if (snapshot.readError) return;
	const expectedHash = resolveConfigSnapshotHash(snapshot);
	let currentRaw = null;
	let currentExists = true;
	try {
		currentRaw = ioFs.readFileSync(configPath, "utf-8");
	} catch (error) {
		if (error?.code !== "ENOENT") throw error;
		currentExists = false;
	}
	const currentHash = currentExists ? hashConfigRaw(currentRaw) : null;
	if (currentExists !== snapshot.exists || currentExists && expectedHash !== null && currentHash !== expectedHash) throw new ConfigMutationConflictError("config changed since last load");
}
async function tightenStateDirPermissionsIfNeeded(params) {
	if (process.platform === "win32") return;
	const stateDir = resolveStateDir(params.env, params.homedir);
	const configDir = path.dirname(params.configPath);
	if (path.resolve(configDir) !== path.resolve(stateDir)) return;
	try {
		if (((await params.fsModule.promises.stat(configDir)).mode & 63) !== 0) await params.fsModule.promises.chmod(configDir, 448);
	} catch {}
}
async function rollbackConfigFileWriteIfUnchanged(params) {
	let currentRaw = null;
	try {
		currentRaw = await params.fsModule.promises.readFile(params.configPath, "utf-8");
	} catch (error) {
		if (error?.code !== "ENOENT") throw error;
	}
	if (hashConfigRaw(currentRaw) !== params.committedHash) return false;
	if (params.previousSnapshot.exists && typeof params.previousSnapshot.raw === "string") {
		await replaceFileAtomic({
			filePath: params.configPath,
			content: params.previousSnapshot.raw,
			dirMode: 448,
			mode: 384,
			tempPrefix: path.basename(params.configPath),
			copyFallbackOnPermissionError: true,
			fileSystem: params.fsModule
		});
		return true;
	}
	if (params.previousSnapshot.exists) return false;
	try {
		await params.fsModule.promises.unlink(params.configPath);
	} catch (error) {
		if (error?.code !== "ENOENT") throw error;
	}
	return true;
}
function normalizeStatNumber(value) {
	return asFiniteNumber(value) ?? null;
}
function normalizeStatId(value) {
	if (typeof value === "bigint") return value.toString();
	return typeof value === "number" && Number.isFinite(value) ? String(value) : null;
}
function resolveConfigStatMetadata(stat) {
	return {
		dev: normalizeStatId(stat?.dev ?? null),
		ino: normalizeStatId(stat?.ino ?? null),
		mode: normalizeStatNumber(stat ? stat.mode & 511 : null),
		nlink: normalizeStatNumber(stat?.nlink ?? null),
		uid: normalizeStatNumber(stat?.uid ?? null),
		gid: normalizeStatNumber(stat?.gid ?? null)
	};
}
function resolveConfigWriteSuspiciousReasons(params) {
	const reasons = [];
	if (!params.existsBefore) return reasons;
	if (params.unreadableBefore) reasons.push("unreadable-config-before-write");
	if (typeof params.sizeBaselineBytes === "number" && typeof params.nextBytes === "number" && params.sizeBaselineBytes >= 512 && params.nextBytes < Math.floor(params.sizeBaselineBytes * .5)) reasons.push(`size-drop:${params.sizeBaselineBytes}->${params.nextBytes}`);
	if (!params.hasMetaBefore) reasons.push("missing-meta-before-write");
	if (params.gatewayModeBefore && !params.gatewayModeAfter) reasons.push("gateway-mode-removed");
	return reasons;
}
function resolveConfigWriteBlockingReasons(suspicious, options = {}) {
	return suspicious.filter((reason) => reason === "unreadable-config-before-write" || reason.startsWith("size-drop:") && options.allowConfigSizeDrop !== true || reason === "gateway-mode-removed");
}
function formatConfigArtifactTimestamp(ts) {
	return ts.replaceAll(":", "-").replaceAll(".", "-");
}
function stampConfigVersion(cfg, version, previousConfig) {
	return stampConfigWriteMetadata(cfg, (/* @__PURE__ */ new Date()).toISOString(), version, previousConfig);
}
function resolveConfigSizeBaselineBytes(params) {
	if (params.raw === null) return null;
	const rawBytes = Buffer.byteLength(params.raw, "utf-8");
	const parsed = parseConfigJson5(params.raw, params.json5);
	if (!parsed.ok || !isRecord(parsed.parsed)) return rawBytes;
	const canonical = JSON.stringify(stampConfigVersion(parsed.parsed, params.lastTouchedVersionOverride), null, 2).trimEnd().concat("\n");
	return Buffer.byteLength(canonical, "utf-8");
}
//#endregion
//#region src/config/legacy.context-budget.ts
const MODEL_CONTEXT_TOKENS_REPLACEMENT = "models.providers.<provider>.models[].contextTokens";
function hasLegacyContextBudgetConfig(root) {
	const providers = isRecord(root.models) ? root.models.providers : void 0;
	if (isRecord(providers) && Object.values(providers).some((provider) => isRecord(provider) && (Object.hasOwn(provider, "contextTokens") || Object.hasOwn(provider, "contextWindow")))) return true;
	const agents = root.agents;
	if (!isRecord(agents)) return false;
	if (isRecord(agents.defaults) && Object.hasOwn(agents.defaults, "contextTokens")) return true;
	if (isRecord(agents.entries) && Object.values(agents.entries).some((entry) => isRecord(entry) && Object.hasOwn(entry, "contextTokens"))) return true;
	return Array.isArray(agents.list) && agents.list.some((entry) => isRecord(entry) && Object.hasOwn(entry, "contextTokens"));
}
function removeAgentContextTokens(root, changes, warnings) {
	const agents = root.agents;
	if (!isRecord(agents)) return;
	const removeContextTokens = (record, path) => {
		if (!isRecord(record) || !Object.hasOwn(record, "contextTokens")) return;
		delete record.contextTokens;
		changes.push({
			path,
			message: `Removed ${path}.`
		});
		warnings.push({
			path,
			message: `${path} cannot be represented per model; use ${MODEL_CONTEXT_TOKENS_REPLACEMENT} instead.`
		});
	};
	removeContextTokens(agents.defaults, "agents.defaults.contextTokens");
	const entries = agents.entries;
	if (isRecord(entries)) for (const [agentId, entry] of Object.entries(entries)) removeContextTokens(entry, `agents.entries.${agentId}.contextTokens`);
	if (Array.isArray(agents.list)) for (const [index, entry] of agents.list.entries()) removeContextTokens(entry, `agents.list[${index}].contextTokens`);
}
function migrateProviderContextBudgets(root, changes, warnings) {
	const providers = isRecord(root.models) ? root.models.providers : void 0;
	if (!isRecord(providers)) return;
	for (const [providerId, provider] of Object.entries(providers)) {
		if (!isRecord(provider)) continue;
		for (const key of ["contextTokens", "contextWindow"]) {
			if (!Object.hasOwn(provider, key)) continue;
			const sourcePath = `models.providers.${providerId}.${key}`;
			if (Array.isArray(provider.models) && provider.models.length > 0) {
				for (const [index, model] of provider.models.entries()) {
					if (!isRecord(model) || model[key] !== void 0) continue;
					model[key] = provider[key];
					changes.push({
						path: sourcePath,
						message: `${sourcePath} → models.providers.${providerId}.models[${index}].${key}.`
					});
				}
				delete provider[key];
				changes.push({
					path: sourcePath,
					message: `Removed ${sourcePath} after baking it into explicit model entries.`
				});
				continue;
			}
			delete provider[key];
			changes.push({
				path: sourcePath,
				message: `Removed ${sourcePath}.`
			});
			warnings.push({
				path: sourcePath,
				message: `${sourcePath} had no explicit model entries to receive its value; use ${MODEL_CONTEXT_TOKENS_REPLACEMENT} instead.`
			});
		}
	}
}
function migrateLegacyContextBudgetConfig(raw) {
	if (!isRecord(raw) || !hasLegacyContextBudgetConfig(raw)) return {
		config: raw,
		changed: false,
		changes: [],
		warnings: []
	};
	const next = structuredClone(raw);
	const changes = [];
	const warnings = [];
	migrateProviderContextBudgets(next, changes, warnings);
	removeAgentContextTokens(next, changes, warnings);
	return changes.length > 0 ? {
		config: next,
		changed: true,
		changes,
		warnings
	} : {
		config: raw,
		changed: false,
		changes,
		warnings
	};
}
//#endregion
//#region src/config/agent-list-projection.ts
/** Attach the non-serialized list projection used by legacy runtime consumers. */
function attachAgentListProjection(config) {
	const agents = config.agents;
	if (!agents || typeof agents !== "object" || Array.isArray(agents)) return config;
	Object.defineProperty(agents, "list", {
		configurable: true,
		enumerable: false,
		value: listAgentEntries(config),
		writable: false
	});
	return config;
}
//#endregion
//#region src/config/official-external-channel-secret-schema.ts
/** Widens official external channel schemas for host-resolved SecretRef fields. */
const SECRET_REF_SCHEMA = SecretRefSchema.toJSONSchema({
	io: "input",
	target: "draft-07",
	unrepresentable: "any"
});
function asSchemaObject(value) {
	return asOptionalRecord(value);
}
function widenProperties(properties, fields) {
	if (!properties) return;
	for (const field of fields) {
		const current = asSchemaObject(properties[field]);
		if (current) properties[field] = { anyOf: [current, cloneSchema(SECRET_REF_SCHEMA)] };
	}
}
/** Keeps external plugin schemas honest while allowing host-resolved secret inputs. */
function widenOfficialExternalChannelSecretSchema(params) {
	const contract = getOfficialExternalChannelSecretContract(params.channelId);
	const hostSchemaAllOf = getOfficialExternalChannelHostSchemaAllOf(params.channelId);
	if (!contract && hostSchemaAllOf.length === 0 || !params.schema) return params.schema;
	const next = cloneSchema(params.schema);
	if (contract) {
		const fields = contract.fields.map((field) => field.field);
		widenProperties(next.properties, fields);
		widenProperties(asSchemaObject(asSchemaObject(next.properties?.accounts)?.additionalProperties)?.properties, fields);
	}
	if (hostSchemaAllOf.length > 0) next.allOf = [...Array.isArray(next.allOf) ? next.allOf : [], ...hostSchemaAllOf.map((clause) => cloneSchema(clause))];
	return next;
}
//#endregion
//#region src/config/channel-config-metadata.ts
/**
* Converts plugin manifest metadata into deterministic config UI metadata for docs, validation, and runtime schema.
* When multiple plugin origins expose the same id/channel, the closest origin owns the surfaced schema.
*/
const PLUGIN_ORIGIN_RANK = {
	config: 0,
	workspace: 1,
	global: 2,
	bundled: 3
};
const CHANNEL_HEARTBEAT_VISIBILITY_JSON_SCHEMA = ChannelHeartbeatVisibilitySchema.unwrap().toJSONSchema({ target: "draft-07" });
function normalizeCoreOwnedChannelSchema(schema) {
	const normalized = structuredClone(schema);
	let changed = false;
	const normalizeNode = (node, accountMap = false, rootScope = true) => {
		let withinRootScope = rootScope && (node === normalized || typeof node.$id !== "string");
		if (typeof node.$ref === "string") {
			const match = withinRootScope ? /^#\/(\$defs|definitions)\/([A-Za-z0-9_.-]+)$/.exec(node.$ref) : null;
			const definitions = match?.[1] ? normalized[match[1]] : void 0;
			const target = isRecord(definitions) && match?.[2] ? definitions[match[2]] : void 0;
			if (!isRecord(target) || Object.keys(node).some((key) => ![
				"$ref",
				"$defs",
				"definitions",
				"$id",
				"$schema"
			].includes(key)) || [
				"$id",
				"$anchor",
				"$dynamicAnchor",
				"$recursiveAnchor",
				"$schema",
				"$ref"
			].some((key) => Object.hasOwn(target, key))) return;
			const owner = { ...node };
			Object.assign(node, structuredClone(target), owner);
			delete node.$ref;
			changed = true;
			withinRootScope = node === normalized;
		}
		for (const key of [
			"allOf",
			"anyOf",
			"oneOf"
		]) {
			const variants = node[key];
			for (const variant of Array.isArray(variants) ? variants : []) if (isRecord(variant)) normalizeNode(variant, accountMap, withinRootScope);
		}
		if (accountMap) {
			if (node.additionalProperties === true) {
				node.additionalProperties = {};
				changed = true;
			}
			const entries = [
				node.additionalProperties,
				...Object.values(isRecord(node.properties) ? node.properties : {}),
				...Object.values(isRecord(node.patternProperties) ? node.patternProperties : {})
			];
			for (const entry of entries) if (isRecord(entry)) normalizeNode(entry, false, withinRootScope);
			return;
		}
		const properties = isRecord(node.properties) ? node.properties : {};
		if (JSON.stringify(properties.heartbeatVisibility) !== JSON.stringify(CHANNEL_HEARTBEAT_VISIBILITY_JSON_SCHEMA)) {
			node.properties = {
				...properties,
				heartbeatVisibility: CHANNEL_HEARTBEAT_VISIBILITY_JSON_SCHEMA
			};
			changed = true;
		}
		const accounts = properties.accounts;
		if (isRecord(accounts)) normalizeNode(accounts, true, withinRootScope);
	};
	normalizeNode(normalized);
	return changed ? normalized : schema;
}
/** Collects plugin config UI metadata with deterministic origin precedence and output ordering. */
function collectPluginSchemaMetadataCore(registry) {
	const deduped = /* @__PURE__ */ new Map();
	for (const record of registry.plugins) {
		const current = deduped.get(record.id);
		const nextRank = PLUGIN_ORIGIN_RANK[record.origin] ?? Number.MAX_SAFE_INTEGER;
		if (current && current.originRank <= nextRank) continue;
		deduped.set(record.id, {
			id: record.id,
			name: record.name,
			description: record.description,
			configUiHints: record.configUiHints,
			configSchema: record.configSchema,
			originRank: nextRank
		});
	}
	return [...deduped.values()].toSorted((left, right) => left.id.localeCompare(right.id)).map(({ originRank: _originRank, ...record }) => record);
}
function prepareChannelConfigSchema(origin, channelId, schema) {
	if (origin === "bundled") return widenOfficialExternalChannelSecretSchema({
		channelId,
		schema
	});
	try {
		return widenOfficialExternalChannelSecretSchema({
			channelId,
			schema: schema === void 0 ? schema : normalizeCoreOwnedChannelSchema(schema)
		});
	} catch {
		return schema;
	}
}
/** Collects per-channel config metadata with the plugin that supplied the selected schema. */
function collectChannelSchemaMetadataWithOwnership(registry, selectedPluginIds) {
	const byChannelId = /* @__PURE__ */ new Map();
	const selectedOwners = /* @__PURE__ */ new Map();
	for (const record of registry.plugins.toSorted((left, right) => PLUGIN_ORIGIN_RANK[left.origin] - PLUGIN_ORIGIN_RANK[right.origin])) {
		if (!selectedPluginIds?.has(record.id)) continue;
		for (const channelId of record.channels) if (!selectedOwners.has(channelId)) selectedOwners.set(channelId, record.id);
	}
	for (const record of registry.plugins) {
		const originRank = PLUGIN_ORIGIN_RANK[record.origin] ?? Number.MAX_SAFE_INTEGER;
		const rootLabel = record.channelCatalogMeta?.label;
		const rootDescription = record.channelCatalogMeta?.blurb;
		for (const channelId of record.channels) {
			if (selectedOwners.has(channelId) && selectedOwners.get(channelId) !== record.id) continue;
			const current = byChannelId.get(channelId);
			if (!current || originRank <= current.originRank) byChannelId.set(channelId, {
				id: channelId,
				label: rootLabel ?? current?.label,
				description: rootDescription ?? current?.description,
				configSchema: current?.configSchema,
				configUiHints: current?.configUiHints,
				schemaPluginId: current?.schemaPluginId,
				schemaPluginOrigin: current?.schemaPluginOrigin ?? record.origin,
				originRank
			});
		}
		for (const [channelId, channelConfig] of Object.entries(record.channelConfigs ?? {})) {
			if (selectedOwners.has(channelId) && selectedOwners.get(channelId) !== record.id) continue;
			const current = byChannelId.get(channelId);
			if (current && current.originRank < originRank && (current.configSchema !== void 0 || current.configUiHints !== void 0)) continue;
			const configSchema = prepareChannelConfigSchema(record.origin, channelId, channelConfig.schema);
			byChannelId.set(channelId, {
				id: channelId,
				label: channelConfig.label ?? rootLabel ?? current?.label,
				description: channelConfig.description ?? rootDescription ?? current?.description,
				configSchema,
				configUiHints: channelConfig.uiHints,
				schemaPluginId: configSchema === void 0 ? void 0 : record.id,
				schemaPluginOrigin: record.origin,
				originRank
			});
		}
	}
	return [...byChannelId.values()].toSorted((left, right) => left.id.localeCompare(right.id)).map(({ originRank: _originRank, ...entry }) => entry);
}
/** Collects public per-channel config UI metadata without internal schema ownership. */
function collectChannelSchemaMetadataCore(registry, selectedPluginIds) {
	return collectChannelSchemaMetadataWithOwnership(registry, selectedPluginIds).map(({ schemaPluginId: _schemaPluginId, schemaPluginOrigin: _schemaPluginOrigin, ...entry }) => entry);
}
/** Collects channel DM policy metadata without importing doctor/runtime command modules. */
function collectChannelDmPolicyMetadata(registry) {
	const byChannelId = /* @__PURE__ */ new Map();
	const put = (channelId, originRank, dmAllowFromMode) => {
		const id = channelId?.trim();
		if (!id) return;
		const current = byChannelId.get(id);
		if (current && current.originRank < originRank) return;
		byChannelId.set(id, {
			id,
			...dmAllowFromMode ? { dmAllowFromMode } : {},
			originRank
		});
	};
	for (const record of registry.plugins) {
		const originRank = PLUGIN_ORIGIN_RANK[record.origin] ?? Number.MAX_SAFE_INTEGER;
		const packageChannelId = record.packageChannel?.id?.trim();
		const dmAllowFromMode = record.packageChannel?.doctorCapabilities?.dmAllowFromMode;
		for (const channelId of record.channels) put(channelId, originRank, channelId === packageChannelId ? dmAllowFromMode : void 0);
		put(packageChannelId, originRank, dmAllowFromMode);
		for (const channelId of Object.keys(record.channelConfigs ?? {})) put(channelId, originRank, channelId === packageChannelId ? dmAllowFromMode : void 0);
	}
	return [...byChannelId.values()].toSorted((left, right) => left.id.localeCompare(right.id)).map(({ originRank: _originRank, ...entry }) => entry);
}
//#endregion
//#region src/config/channel-schema-selection.ts
/** Select metadata owners through the same preference and eligibility policy as channel startup. */
function resolveChannelSchemaSelection(registry, config, env = process.env) {
	const activationSourceConfig = resolvePluginActivationSourceConfig({ config });
	const effectiveConfig = config === getRuntimeConfigSnapshot() ? config : materializePluginAutoEnableCandidatesInternal({
		config: activationSourceConfig,
		candidates: resolveConfiguredChannelAutoEnableCandidates({
			config: activationSourceConfig,
			env,
			registry
		}),
		env,
		manifestRegistry: registry
	}).config;
	const pluginsConfig = normalizePluginsConfig(effectiveConfig.plugins);
	const activationSource = createPluginActivationSource({ config: activationSourceConfig });
	return new Set(registry.plugins.filter((plugin) => plugin.channels.length > 0 && canStartConfiguredChannelPlugin({
		id: plugin.id,
		origin: plugin.origin,
		channelIds: plugin.channels,
		config: effectiveConfig,
		pluginsConfig,
		activationSource
	})).map((plugin) => plugin.id));
}
//#endregion
//#region src/config/agent-limits.ts
const MIN_AGENT_MAX_CONCURRENT = 8;
const MAX_AGENT_MAX_CONCURRENT = 16;
let defaultAgentMaxConcurrent;
function resolveDefaultAgentMaxConcurrent() {
	if (defaultAgentMaxConcurrent === void 0) {
		const availableParallelism = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
		defaultAgentMaxConcurrent = Math.min(MAX_AGENT_MAX_CONCURRENT, Math.max(MIN_AGENT_MAX_CONCURRENT, availableParallelism));
	}
	return defaultAgentMaxConcurrent;
}
/** Resolves top-level agent concurrency, flooring finite values and clamping to at least one. */
function resolveAgentMaxConcurrent(cfg) {
	const raw = cfg?.agents?.defaults?.maxConcurrent;
	if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
	return resolveDefaultAgentMaxConcurrent();
}
/** Resolves subagent concurrency, flooring finite values and clamping to at least one. */
function resolveSubagentMaxConcurrent(cfg) {
	const raw = cfg?.agents?.defaults?.subagents?.maxConcurrent;
	if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
	return 8;
}
//#endregion
//#region src/config/provider-policy.ts
/** Applies bundled provider-owned normalization to one provider config during config defaults. */
function normalizeProviderConfigForConfigDefaults(params) {
	const normalized = resolveBundledProviderPolicySurface(params.provider, { manifestRegistry: params.manifestRegistry })?.normalizeConfig?.({
		provider: params.provider,
		providerConfig: params.providerConfig
	});
	return normalized && normalized !== params.providerConfig ? normalized : params.providerConfig;
}
/** Applies bundled provider-owned defaults to the full config when that provider has policy. */
function applyProviderConfigDefaultsForConfig(params) {
	return resolveBundledProviderPolicySurface(params.provider, {
		manifestRegistry: params.manifestRegistry,
		loadManifestRegistry: params.loadManifestRegistry
	})?.applyConfigDefaults?.({
		provider: params.provider,
		config: params.config,
		env: params.env
	}) ?? params.config;
}
//#endregion
//#region src/config/defaults.ts
const defaultWarnState = { warned: false };
const DEFAULT_MODEL_ALIASES = {
	opus: "anthropic/claude-opus-5",
	sonnet: "anthropic/claude-sonnet-5",
	gpt: "openai/gpt-5.4",
	"gpt-mini": "openai/gpt-5.4-mini",
	"gpt-nano": "openai/gpt-5.4-nano",
	gemini: "google/gemini-3.1-pro-preview",
	"gemini-flash": "google/gemini-3-flash-preview",
	"gemini-flash-lite": "google/gemini-3.1-flash-lite"
};
const DEFAULT_MODEL_COST = {
	input: 0,
	output: 0,
	cacheRead: 0,
	cacheWrite: 0
};
const DEFAULT_MODEL_INPUT = ["text"];
const DEFAULT_MODEL_MAX_TOKENS = 8192;
const MISTRAL_SAFE_MAX_TOKENS_BY_MODEL = {
	"devstral-medium-latest": 32768,
	"magistral-small": 4e4,
	"mistral-large-latest": 16384,
	"mistral-medium-2508": 8192,
	"mistral-small-latest": 16384,
	"pixtral-large-latest": 32768
};
function resolveModelCost(raw) {
	return {
		input: typeof raw?.input === "number" ? raw.input : DEFAULT_MODEL_COST.input,
		output: typeof raw?.output === "number" ? raw.output : DEFAULT_MODEL_COST.output,
		cacheRead: typeof raw?.cacheRead === "number" ? raw.cacheRead : DEFAULT_MODEL_COST.cacheRead,
		cacheWrite: typeof raw?.cacheWrite === "number" ? raw.cacheWrite : DEFAULT_MODEL_COST.cacheWrite,
		...raw?.tieredPricing ? { tieredPricing: raw.tieredPricing } : {}
	};
}
function resolveNormalizedProviderModelMaxTokens(params) {
	const clamped = Math.min(params.rawMaxTokens, params.contextWindow);
	if (normalizeProviderId(params.providerId) !== "mistral") return clamped;
	const safeMaxTokens = Object.hasOwn(MISTRAL_SAFE_MAX_TOKENS_BY_MODEL, params.modelId) ? MISTRAL_SAFE_MAX_TOKENS_BY_MODEL[params.modelId] : void 0;
	if (safeMaxTokens !== void 0) return Math.min(clamped, safeMaxTokens);
	return clamped < params.contextWindow ? clamped : Math.min(DEFAULT_MODEL_MAX_TOKENS, params.contextWindow);
}
function applyMessageDefaults(cfg) {
	const messages = cfg.messages;
	if (messages?.ackReactionScope !== void 0) return cfg;
	const nextMessages = messages ? { ...messages } : {};
	nextMessages.ackReactionScope = "group-mentions";
	return {
		...cfg,
		messages: nextMessages
	};
}
function applySessionDefaults(cfg, options = {}) {
	const session = cfg.session;
	if (!session || session.mainKey === void 0) return cfg;
	const trimmed = session.mainKey.trim();
	const warn = options.warn ?? console.warn;
	const warnState = options.warnState ?? defaultWarnState;
	const next = {
		...cfg,
		session: {
			...session,
			mainKey: "main"
		}
	};
	if (trimmed && trimmed !== "main" && !warnState.warned) {
		warnState.warned = true;
		warn("session.mainKey is ignored; main session is always \"main\".");
	}
	return next;
}
function applyTalkConfigNormalization(config) {
	return normalizeTalkConfig(config);
}
/**
* Indexes plugin manifest catalog rows so configured model entries can inherit
* metadata the operator omitted. Without this, materialization would turn an
* override entry that pins only sizing fields into a text-only, non-reasoning,
* zero-cost model — silently dropping vision-gated tools downstream.
*/
function buildManifestCatalogModelLookup(manifestRegistry, policies) {
	const plugins = manifestRegistry?.plugins;
	if (!plugins || plugins.length === 0) return () => void 0;
	let index;
	const keyFor = (providerId, modelId) => normalizeProviderId(providerId) + " " + normalizeConfiguredProviderCatalogModelId(providerId, modelId, policies).toLowerCase();
	return (providerId, modelId) => {
		if (!index) {
			index = /* @__PURE__ */ new Map();
			for (const plugin of plugins) for (const [catalogProviderId, provider] of Object.entries(plugin.modelCatalog?.providers ?? {})) for (const model of provider.models) {
				const key = keyFor(catalogProviderId, model.id);
				if (!index.has(key)) index.set(key, model);
			}
		}
		return structuredClone(index.get(keyFor(providerId, modelId)));
	};
}
function applyModelDefaults(cfg, options = {}) {
	let mutated = false;
	let nextCfg = cfg;
	const providerConfig = nextCfg.models?.providers;
	if (providerConfig) {
		const manifestRegistry = options.manifestRegistry ?? options.loadManifestRegistry?.();
		const modelIdNormalizationPolicies = manifestRegistry ? collectManifestModelIdNormalizationPolicies(manifestRegistry.plugins) : void 0;
		const resolveCatalogModel = buildManifestCatalogModelLookup(manifestRegistry, modelIdNormalizationPolicies);
		const nextProviders = { ...providerConfig };
		for (const [providerId, provider] of Object.entries(providerConfig)) {
			const normalizedProvider = normalizeProviderConfigForConfigDefaults({
				provider: providerId,
				providerConfig: provider,
				manifestRegistry
			});
			const models = normalizedProvider.models;
			if (!Array.isArray(models) || models.length === 0) {
				if (normalizedProvider !== provider) {
					nextProviders[providerId] = normalizedProvider;
					mutated = true;
				}
				continue;
			}
			const providerApi = normalizedProvider.api;
			const providerMaxTokens = asPositiveFiniteNumber(normalizedProvider.maxTokens);
			const nextProvider = normalizedProvider;
			if (nextProvider !== provider) mutated = true;
			let providerMutated = false;
			const nextModels = models.map((model) => {
				const raw = model;
				const id = normalizeConfiguredProviderCatalogModelId(providerId, raw.id, modelIdNormalizationPolicies);
				const catalogModel = resolveCatalogModel(providerId, id);
				const reasoning = typeof raw.reasoning === "boolean" ? raw.reasoning : catalogModel?.reasoning ?? false;
				const input = raw.input ?? catalogModel?.input ?? [...DEFAULT_MODEL_INPUT];
				const cost = resolveModelCost(mergeModelCost(catalogModel?.cost, raw.cost));
				const costMutated = !raw.cost || raw.cost.input !== cost.input || raw.cost.output !== cost.output || raw.cost.cacheRead !== cost.cacheRead || raw.cost.cacheWrite !== cost.cacheWrite || raw.cost.tieredPricing !== cost.tieredPricing;
				const contextWindow = asPositiveFiniteNumber(raw.contextWindow) ?? asPositiveFiniteNumber(catalogModel?.contextWindow);
				const contextTokens = asPositiveFiniteNumber(raw.contextTokens) ?? asPositiveFiniteNumber(catalogModel?.contextTokens);
				const maxTokenContextWindow = contextWindow ?? 2e5;
				const defaultMaxTokens = Math.min(providerMaxTokens ?? DEFAULT_MODEL_MAX_TOKENS, maxTokenContextWindow);
				const rawMaxTokens = asPositiveFiniteNumber(raw.maxTokens) ?? asPositiveFiniteNumber(catalogModel?.maxTokens) ?? defaultMaxTokens;
				const maxTokens = resolveNormalizedProviderModelMaxTokens({
					providerId,
					modelId: id,
					contextWindow: maxTokenContextWindow,
					rawMaxTokens
				});
				const api = raw.api ?? providerApi;
				const thinkingLevelMap = raw.thinkingLevelMap === void 0 && catalogModel?.thinkingLevelMap !== void 0 ? catalogModel.thinkingLevelMap : void 0;
				const compat = raw.compat === void 0 && catalogModel?.compat !== void 0 ? catalogModel.compat : void 0;
				if (!(id !== raw.id || raw.reasoning !== reasoning || raw.input === void 0 || costMutated || raw.contextWindow !== contextWindow || raw.contextTokens !== contextTokens || raw.maxTokens !== maxTokens || raw.api !== api || thinkingLevelMap !== void 0 || compat !== void 0)) return model;
				providerMutated = true;
				return Object.assign({}, raw, {
					id,
					reasoning,
					input,
					cost,
					contextWindow,
					contextTokens,
					maxTokens,
					api
				}, thinkingLevelMap !== void 0 ? { thinkingLevelMap } : {}, compat !== void 0 ? { compat } : {});
			});
			if (!providerMutated) {
				if (nextProvider !== provider) nextProviders[providerId] = nextProvider;
				continue;
			}
			nextProviders[providerId] = {
				...nextProvider,
				models: nextModels
			};
			mutated = true;
		}
		if (mutated) nextCfg = {
			...nextCfg,
			models: {
				...nextCfg.models,
				providers: nextProviders
			}
		};
	}
	let nextAgents = nextCfg.agents;
	const rawAgentList = nextAgents?.list;
	if (Array.isArray(rawAgentList)) {
		let listMutated = false;
		const agentList = rawAgentList.map((agent) => {
			if (!isRecord(agent)) return agent;
			let nextAgent = agent;
			if (Object.hasOwn(agent, "model")) {
				const normalizedModel = normalizeAgentModelSelectionForConfig(agent.model);
				if (normalizedModel !== agent.model) {
					nextAgent = {
						...nextAgent,
						model: normalizedModel
					};
					listMutated = true;
				}
			}
			if (isRecord(agent.models)) {
				const normalizedModels = normalizeAgentModelMapForConfig(agent.models);
				if (normalizedModels !== agent.models) {
					nextAgent = {
						...nextAgent,
						models: normalizedModels
					};
					listMutated = true;
				}
			}
			return nextAgent;
		});
		if (listMutated) {
			nextAgents = {
				...nextAgents,
				list: agentList
			};
			mutated = true;
		}
	}
	const existingAgent = nextAgents?.defaults;
	if (!existingAgent) {
		if (!mutated) return cfg;
		return nextAgents === nextCfg.agents ? nextCfg : {
			...nextCfg,
			agents: nextAgents
		};
	}
	let nextAgent = existingAgent;
	const normalizedModel = normalizeAgentModelSelectionForConfig(existingAgent.model);
	if (normalizedModel !== existingAgent.model) {
		nextAgent = {
			...nextAgent,
			model: normalizedModel
		};
		mutated = true;
	}
	const rawExistingModels = existingAgent.models ?? {};
	const existingModels = normalizeAgentModelMapForConfig(rawExistingModels);
	if (existingModels !== rawExistingModels) mutated = true;
	if (Object.keys(existingModels).length === 0) return mutated ? {
		...nextCfg,
		agents: {
			...nextAgents,
			defaults: nextAgent
		}
	} : cfg;
	const nextModels = { ...existingModels };
	for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
		const entry = nextModels[target];
		if (!entry) continue;
		if (entry.alias !== void 0) continue;
		const normalizedAlias = normalizeLowercaseStringOrEmpty(alias);
		if (Object.entries(nextModels).some(([modelRef, candidate]) => modelRef !== target && normalizeLowercaseStringOrEmpty(candidate.alias) === normalizedAlias)) continue;
		nextModels[target] = {
			...entry,
			alias
		};
		mutated = true;
	}
	if (!mutated) return cfg;
	return {
		...nextCfg,
		agents: {
			...nextAgents,
			defaults: {
				...nextAgent,
				models: nextModels
			}
		}
	};
}
function applyAgentDefaults(cfg) {
	const agents = cfg.agents;
	const defaults = agents?.defaults;
	const hasMax = typeof defaults?.maxConcurrent === "number" && Number.isFinite(defaults.maxConcurrent);
	const hasSubMax = typeof defaults?.subagents?.maxConcurrent === "number" && Number.isFinite(defaults.subagents.maxConcurrent);
	const hasSubArchive = typeof defaults?.subagents?.archiveAfterMinutes === "number" && Number.isFinite(defaults.subagents.archiveAfterMinutes);
	if (hasMax && hasSubMax && hasSubArchive) return cfg;
	let mutated = false;
	const nextDefaults = defaults ? { ...defaults } : {};
	if (!hasMax) {
		nextDefaults.maxConcurrent = resolveAgentMaxConcurrent();
		mutated = true;
	}
	const nextSubagents = defaults?.subagents ? { ...defaults.subagents } : {};
	if (!hasSubMax) {
		nextSubagents.maxConcurrent = 8;
		mutated = true;
	}
	if (!hasSubArchive) {
		nextSubagents.archiveAfterMinutes = 60;
		mutated = true;
	}
	if (!mutated) return cfg;
	return {
		...cfg,
		agents: {
			...agents,
			defaults: {
				...nextDefaults,
				subagents: nextSubagents
			}
		}
	};
}
function applyCronDefaults(cfg) {
	return cfg;
}
function applyLoggingDefaults(cfg) {
	return cfg;
}
function hasAnthropicDefaultSignal(cfg, env) {
	if (env.ANTHROPIC_API_KEY?.trim() || env.ANTHROPIC_OAUTH_TOKEN?.trim()) return true;
	const profiles = cfg.auth?.profiles;
	if (profiles) for (const profile of Object.values(profiles)) {
		const provider = normalizeProviderId(profile?.provider);
		if (provider === "anthropic" || provider === "claude-cli") return true;
	}
	const order = cfg.auth?.order;
	if (!order) return false;
	return Object.keys(order).some((provider) => {
		const normalizedProvider = normalizeProviderId(provider);
		if (normalizedProvider !== "anthropic" && normalizedProvider !== "claude-cli") return false;
		return order[provider] !== void 0;
	});
}
function applyContextPruningDefaults(cfg, options = {}) {
	if (!cfg.agents?.defaults) return cfg;
	if (!hasAnthropicDefaultSignal(cfg, process.env)) return cfg;
	return applyProviderConfigDefaultsForConfig({
		provider: "anthropic",
		config: cfg,
		env: process.env,
		manifestRegistry: options.manifestRegistry,
		loadManifestRegistry: options.loadManifestRegistry
	}) ?? cfg;
}
function applyCompactionDefaults(cfg) {
	const defaults = cfg.agents?.defaults;
	if (!defaults) return cfg;
	const compaction = defaults?.compaction;
	if (compaction?.mode) return cfg;
	return {
		...cfg,
		agents: {
			...cfg.agents,
			defaults: {
				...defaults,
				compaction: {
					...compaction,
					mode: "safeguard"
				}
			}
		}
	};
}
//#endregion
//#region src/config/normalize-exec-safe-bin.ts
/**
* Config normalization for exec safe-bin policy before materialized config is consumed.
* Keep this limited to persisted global/per-agent config shape; runtime trust decisions live in infra.
*/
/** Normalize exec safe-bin profiles and trusted dirs in global and per-agent config scopes. */
function normalizeExecSafeBinProfilesInConfig(cfg) {
	const normalizeExec = (exec) => {
		if (!exec || typeof exec !== "object" || Array.isArray(exec)) return;
		const typedExec = exec;
		const normalizedProfiles = normalizeSafeBinProfileFixtures(typedExec.safeBinProfiles);
		typedExec.safeBinProfiles = Object.keys(normalizedProfiles).length > 0 ? normalizedProfiles : void 0;
		const normalizedTrustedDirs = normalizeTrustedSafeBinDirs(typedExec.safeBinTrustedDirs);
		typedExec.safeBinTrustedDirs = normalizedTrustedDirs.length > 0 ? normalizedTrustedDirs : void 0;
	};
	normalizeExec(cfg.tools?.exec);
	for (const agent of listAgentEntries(cfg)) normalizeExec(agent?.tools?.exec);
}
//#endregion
//#region src/config/normalize-paths.ts
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
const PATH_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
const PATH_LIST_KEYS = /* @__PURE__ */ new Set(["paths", "pathPrepend"]);
/** Normalize tilde paths in path-like config fields using the config reader's home. */
function normalizeConfigPaths(cfg, opts) {
	function normalizeAny(key, value) {
		if (typeof value === "string") return key && PATH_VALUE_RE.test(value.trim()) && (PATH_KEY_RE.test(key) || PATH_LIST_KEYS.has(key)) ? resolveUserPath(value, opts?.env, opts?.homedir) : value;
		if (Array.isArray(value)) {
			const normalizeChildren = Boolean(key && PATH_LIST_KEYS.has(key));
			return value.map((entry) => normalizeAny(typeof entry === "string" && normalizeChildren ? key : void 0, entry));
		}
		if (isPlainObject(value)) for (const [childKey, childValue] of Object.entries(value)) {
			const next = normalizeAny(childKey, childValue);
			if (next !== childValue) value[childKey] = next;
		}
		return value;
	}
	normalizeAny(void 0, cfg);
	return cfg;
}
//#endregion
//#region src/config/materialize.ts
function asResolvedSourceConfig(config) {
	return config;
}
function asRuntimeConfig(config) {
	return config;
}
function materializeRuntimeConfig(config, options = {}) {
	let next = applyMessageDefaults(config);
	next = applyLoggingDefaults(next);
	next = applySessionDefaults(next);
	next = applyAgentDefaults(next);
	next = applyCronDefaults(next);
	next = applyContextPruningDefaults(next, options);
	next = applyCompactionDefaults(next);
	next = applyModelDefaults(next, {
		manifestRegistry: options.manifestRegistry,
		loadManifestRegistry: options.loadManifestRegistry
	});
	next = applyTalkConfigNormalization(next);
	normalizeConfigPaths(next, options);
	normalizeExecSafeBinProfilesInConfig(next);
	return asRuntimeConfig(inheritLegacyDefaultAgentId(config, next));
}
//#endregion
//#region src/config/validation-channel-rules.ts
const bundledChannelSchemaById = new Map(GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.filter((entry) => entry.configurable !== false).map((entry) => [entry.channelId, entry.schema]));
const bundledChannelIds = Object.freeze(GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.filter((entry) => entry.configurable !== false).map((entry) => normalizeLowercaseStringOrEmpty(entry.channelId)).filter((channelId) => channelId.length > 0));
const bundledChannelIdSet = new Set(bundledChannelIds);
const bundledChannelAliases = new Map(GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.filter((entry) => entry.configurable !== false).flatMap((entry) => {
	const channelId = normalizeLowercaseStringOrEmpty(entry.channelId);
	if (!channelId) return [];
	return (entry.aliases ?? []).map((alias) => [normalizeLowercaseStringOrEmpty(alias), channelId]).filter(([alias]) => alias.length > 0);
}));
function normalizeBundledChannelId(raw) {
	const normalized = normalizeLowercaseStringOrEmpty(raw);
	if (!normalized) return null;
	const resolved = bundledChannelAliases.get(normalized) ?? normalized;
	return bundledChannelIdSet.has(resolved) ? resolved : null;
}
function formatRawChannelConfigIssueMessage(message) {
	return `invalid config: ${message}`;
}
function buildDmPolicyDependencyWarning(params) {
	const channelBase = `channels.${params.channelId}`;
	const scope = params.accountId ? `${channelBase}.accounts.${params.accountId}` : channelBase;
	const allowFromPath = `${scope}.allowFrom`;
	const inherited = params.accountId && params.allowFromSource === "inherited";
	const allowFromSubject = inherited ? `${allowFromPath} is unset and ${channelBase}.allowFrom` : allowFromPath;
	const accountInheritedTarget = inherited ? ` or ${channelBase}.allowFrom` : "";
	const accountOverrideFix = params.accountId && !inherited ? `, remove ${allowFromPath} to inherit ${channelBase}.allowFrom,` : "";
	return {
		path: allowFromPath,
		message: params.violation === "open_requires_wildcard" ? `${scope}.dmPolicy="open" but ${allowFromSubject} does not include "*"; all DMs will be dropped. Add "*" to ${allowFromPath}${accountInheritedTarget}${accountOverrideFix} or set ${scope}.dmPolicy to "pairing"/"allowlist".` : `${scope}.dmPolicy="allowlist" but ${allowFromSubject} is empty; all DMs will be dropped. Add at least one sender ID to ${allowFromPath}${accountInheritedTarget}${accountOverrideFix} or change ${scope}.dmPolicy.`
	};
}
const DM_POLICY_PSEUDO_CHANNEL_KEYS = /* @__PURE__ */ new Set([
	"defaults",
	"modelByChannel",
	"tools"
]);
function hasDefinedConfigValue(record, key) {
	return Object.hasOwn(record, key) && record[key] !== void 0;
}
function hasConfiguredDmAllowFrom(record, mode) {
	const dm = isRecord(record.dm) ? record.dm : null;
	if (mode === "nestedOnly") return dm !== null && hasDefinedConfigValue(dm, "allowFrom") || hasDefinedConfigValue(record, "allowFrom");
	return hasDefinedConfigValue(record, "allowFrom") || dm !== null && hasDefinedConfigValue(dm, "allowFrom");
}
function isConfigRecordEnabled(record) {
	return record.enabled !== false;
}
function hasChannelDmPolicyDependencyWarningCandidates(config) {
	if (!config.channels || !isRecord(config.channels)) return false;
	return Object.entries(config.channels).some(([channelId, channelValue]) => !DM_POLICY_PSEUDO_CHANNEL_KEYS.has(channelId) && isRecord(channelValue) && isConfigRecordEnabled(channelValue));
}
/**
* Surface dmPolicy/allowFrom dependency problems generically for every channel that
* exposes DM policy via the canonical top-level `dmPolicy`/`allowFrom` fields. These
* configs parse fine but drop every DM at runtime, so we warn (rather than reject) to
* stay consistent with `security audit`/`doctor` and avoid breaking existing-but-usable
* configs on upgrade.
*
* Resolution goes through the shared DM-access helpers so the warning matches the
* effective policy/allowFrom the runtime sees, including the legacy `dm.*` aliases and
* account->channel inheritance. `nestedOnly` channels (canonical fields under `dm.*`)
* are skipped because their config shape does not match this warning's top-level paths.
*/
function collectChannelDmPolicyDependencyWarnings(config, options = {}) {
	if (!config.channels || !isRecord(config.channels)) return [];
	const warnings = [];
	for (const [channelId, channelValue] of Object.entries(config.channels)) {
		if (DM_POLICY_PSEUDO_CHANNEL_KEYS.has(channelId) || !isRecord(channelValue) || !isConfigRecordEnabled(channelValue)) continue;
		const mode = options.dmAllowFromModes?.get(channelId) ?? "topOnly";
		if (mode === "nestedOnly") continue;
		const channelViolation = evaluateDmPolicyAllowFromDependency({
			policy: resolveChannelDmPolicy({
				account: channelValue,
				mode
			}),
			allowFrom: resolveChannelDmAllowFrom({
				account: channelValue,
				mode
			})
		});
		if (channelViolation) warnings.push(buildDmPolicyDependencyWarning({
			channelId,
			violation: channelViolation
		}));
		if (!isRecord(channelValue.accounts)) continue;
		for (const [accountId, accountValue] of Object.entries(channelValue.accounts)) {
			if (!isRecord(accountValue) || !isConfigRecordEnabled(accountValue)) continue;
			const allowFromSource = hasConfiguredDmAllowFrom(accountValue, mode) ? "explicit" : "inherited";
			const accountViolation = evaluateDmPolicyAllowFromDependency({
				policy: resolveChannelDmPolicy({
					account: accountValue,
					parent: channelValue,
					mode
				}),
				allowFrom: resolveChannelDmAllowFrom({
					account: accountValue,
					parent: channelValue,
					mode
				})
			});
			if (accountViolation) warnings.push(buildDmPolicyDependencyWarning({
				channelId,
				accountId,
				allowFromSource,
				violation: accountViolation
			}));
		}
	}
	return warnings;
}
function collectRawBundledChannelConfigIssues(config) {
	if (!config.channels || !isRecord(config.channels)) return [];
	const issues = [];
	for (const [channelId, schema] of bundledChannelSchemaById) {
		if (!Object.hasOwn(config.channels, channelId)) continue;
		const result = validateJsonSchemaValue({
			schema,
			cacheKey: `raw-channel:${channelId}`,
			value: config.channels[channelId],
			applyDefaults: false
		});
		if (result.ok) continue;
		for (const error of result.errors) {
			const message = error.additionalProperty ? `${error.message}: "${error.additionalProperty}"` : error.message;
			const path = error.path === "<root>" ? `channels.${channelId}` : `channels.${channelId}.${error.path}`;
			issues.push({
				path,
				message: formatRawChannelConfigIssueMessage(message),
				allowedValues: error.allowedValues,
				allowedValuesHiddenCount: error.allowedValuesHiddenCount
			});
		}
	}
	return issues;
}
//#endregion
//#region src/shared/gateway-tailscale-auth-policy.ts
/** True when Tailscale exposure is configured without gateway authentication. */
function isUnsafeGatewayTailscaleNoAuth(params) {
	return params.authMode === "none" && (params.tailscaleMode === "serve" || params.tailscaleMode === "funnel");
}
/** Formats the shared validation message for unsafe Tailscale no-auth exposure. */
function formatUnsafeGatewayTailscaleNoAuthMessage(tailscaleMode) {
	if (tailscaleMode === "funnel") return "gateway.tailscale.mode=funnel requires gateway.auth.mode=password; auth.mode=none cannot be used when exposing the gateway through Tailscale Funnel";
	return `gateway.auth.mode=none cannot be used with gateway.tailscale.mode=${tailscaleMode}; configure token, password, or trusted-proxy auth before exposing the gateway through Tailscale`;
}
//#endregion
//#region src/secrets/unsupported-surface-policy.ts
/** Defines unsupported secret-ref surfaces and operator-facing policy messages. */
const CORE_UNSUPPORTED_SECRETREF_SURFACE_PATTERNS = [
	"hooks.token",
	"hooks.gmail.pushToken",
	"hooks.mappings[].sessionKey",
	"auth-profiles.oauth.*"
];
const CORE_UNSUPPORTED_SECRETREF_CONFIG_CANDIDATE_PATTERNS = [
	"hooks.token",
	"hooks.gmail.pushToken",
	"hooks.mappings[].sessionKey"
];
const bundledChannelUnsupportedSecretRefSurfacePatterns = [...new Set(GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.flatMap((entry) => "unsupportedSecretRefSurfacePatterns" in entry ? entry.unsupportedSecretRefSurfacePatterns ?? [] : []))];
const unsupportedSecretRefSurfacePatterns = [...CORE_UNSUPPORTED_SECRETREF_SURFACE_PATTERNS, ...bundledChannelUnsupportedSecretRefSurfacePatterns];
const unsupportedSecretRefConfigCandidatePatterns = [...CORE_UNSUPPORTED_SECRETREF_CONFIG_CANDIDATE_PATTERNS, ...bundledChannelUnsupportedSecretRefSurfacePatterns];
const parsedPatternCache = /* @__PURE__ */ new Map();
function parseUnsupportedSecretRefSurfacePattern(pattern) {
	const cached = parsedPatternCache.get(pattern);
	if (cached) return cached;
	const parsed = pattern.split(".").filter((segment) => segment.length > 0).map((segment) => {
		if (segment === "*") return { kind: "wildcard" };
		if (segment.endsWith("[]")) return {
			kind: "array",
			key: segment.slice(0, -2)
		};
		return {
			kind: "key",
			key: segment
		};
	});
	parsedPatternCache.set(pattern, parsed);
	return parsed;
}
function collectPatternCandidates(params) {
	if (params.tokenIndex >= params.tokens.length) {
		params.candidates.push({
			path: params.pathSegments.join("."),
			value: params.current
		});
		return;
	}
	const token = params.tokens[params.tokenIndex];
	if (!token) return;
	if (token.kind === "wildcard") {
		if (Array.isArray(params.current)) {
			for (const [index, value] of params.current.entries()) collectPatternCandidates({
				...params,
				current: value,
				tokenIndex: params.tokenIndex + 1,
				pathSegments: [...params.pathSegments, String(index)]
			});
			return;
		}
		if (!isRecord(params.current)) return;
		for (const [key, value] of Object.entries(params.current)) collectPatternCandidates({
			...params,
			current: value,
			tokenIndex: params.tokenIndex + 1,
			pathSegments: [...params.pathSegments, key]
		});
		return;
	}
	if (!isRecord(params.current)) return;
	if (token.kind === "array") {
		if (!Object.hasOwn(params.current, token.key)) return;
		const value = params.current[token.key];
		if (!Array.isArray(value)) return;
		for (const [index, entry] of value.entries()) collectPatternCandidates({
			...params,
			current: entry,
			tokenIndex: params.tokenIndex + 1,
			pathSegments: [
				...params.pathSegments,
				token.key,
				String(index)
			]
		});
		return;
	}
	if (!Object.hasOwn(params.current, token.key)) return;
	collectPatternCandidates({
		...params,
		current: params.current[token.key],
		tokenIndex: params.tokenIndex + 1,
		pathSegments: [...params.pathSegments, token.key]
	});
}
/**
* Returns canonical config/auth-profile path patterns that do not support SecretRef values.
*/
function listUnsupportedSecretRefSurfacePatterns() {
	return [...unsupportedSecretRefSurfacePatterns];
}
/**
* Finds configured openclaw.json values whose surfaces currently reject SecretRef objects.
*/
function collectUnsupportedSecretRefConfigCandidates(raw) {
	if (!isRecord(raw)) return [];
	const candidates = [];
	for (const pattern of unsupportedSecretRefConfigCandidatePatterns) collectPatternCandidates({
		current: raw,
		tokens: parseUnsupportedSecretRefSurfacePattern(pattern),
		tokenIndex: 0,
		pathSegments: [],
		candidates
	});
	return candidates;
}
const unsupportedSecretRefSurfacePolicy = {
	listPatterns: listUnsupportedSecretRefSurfacePatterns,
	collectConfigCandidates: collectUnsupportedSecretRefConfigCandidates
};
//#endregion
//#region src/config/validation-issues.ts
const CUSTOM_EXPECTED_ONE_OF_RE = /expected one of ((?:"[^"]+"(?:\|"?[^"]+"?)*)+)/i;
const SECRETREF_POLICY_DOC_URL = "https://docs.openclaw.ai/reference/secretref-credential-surface";
function toConfigPathSegments(path) {
	if (!Array.isArray(path)) return [];
	return path.filter((segment) => {
		const segmentType = typeof segment;
		return segmentType === "string" || segmentType === "number";
	});
}
function formatConfigPath(segments) {
	return segments.join(".");
}
function withConfigIssuePath(issue, pathSegments) {
	Object.defineProperty(issue, "pathSegments", {
		value: [...pathSegments],
		enumerable: false
	});
	return issue;
}
function lookupJsonSchemaNode(schema, pathSegments) {
	let current = asNullableObjectRecord(schema);
	for (const segment of pathSegments) {
		if (!current) return null;
		if (typeof segment === "number") {
			const items = current.items;
			if (Array.isArray(items)) {
				current = asNullableObjectRecord(items[segment] ?? items[0]);
				continue;
			}
			current = asNullableObjectRecord(items);
			continue;
		}
		const properties = asNullableObjectRecord(current.properties);
		current = properties && asNullableObjectRecord(properties[segment]) || asNullableObjectRecord(current.additionalProperties);
	}
	return current;
}
function collectAllowedValuesFromJsonSchemaNode(schema) {
	const node = asNullableObjectRecord(schema);
	if (!node) return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	if (Object.hasOwn(node, "const")) return {
		values: [node.const],
		incomplete: false,
		hasValues: true
	};
	if (Array.isArray(node.enum)) return {
		values: node.enum,
		incomplete: false,
		hasValues: node.enum.length > 0
	};
	const type = node.type;
	if (type === "boolean" || Array.isArray(type) && type.includes("boolean")) return {
		values: [true, false],
		incomplete: false,
		hasValues: true
	};
	const unionBranches = Array.isArray(node.anyOf) ? node.anyOf : Array.isArray(node.oneOf) ? node.oneOf : null;
	if (!unionBranches) return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	const collected = [];
	for (const branch of unionBranches) {
		const branchCollected = collectAllowedValuesFromJsonSchemaNode(branch);
		if (branchCollected.incomplete || !branchCollected.hasValues) return {
			values: [],
			incomplete: true,
			hasValues: false
		};
		collected.push(...branchCollected.values);
	}
	return {
		values: collected,
		incomplete: false,
		hasValues: collected.length > 0
	};
}
function collectAllowedValuesFromBundledChannelSchemaPath(pathSegments) {
	if (pathSegments[0] !== "channels" || typeof pathSegments[1] !== "string") return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	const channelSchema = bundledChannelSchemaById.get(pathSegments[1]);
	if (!channelSchema) return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	const targetNode = lookupJsonSchemaNode(channelSchema, pathSegments.slice(2));
	return targetNode ? collectAllowedValuesFromJsonSchemaNode(targetNode) : {
		values: [],
		incomplete: false,
		hasValues: false
	};
}
function collectAllowedValuesFromCustomIssue(record) {
	const expectedMatch = (typeof record.message === "string" ? record.message : "").match(CUSTOM_EXPECTED_ONE_OF_RE);
	if (expectedMatch?.[1]) {
		const values = [...expectedMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
		return {
			values,
			incomplete: false,
			hasValues: values.length > 0
		};
	}
	return collectAllowedValuesFromBundledChannelSchemaPath(toConfigPathSegments(record.path));
}
function appendNumericBoundHint(message, record) {
	if ((typeof record.origin === "string" ? record.origin : "") !== "number") return message;
	const inclusive = record.inclusive === true;
	if (record.code === "too_big") {
		const maximum = typeof record.maximum === "number" ? record.maximum : void 0;
		if (maximum !== void 0) return inclusive ? `${message} (maximum: ${maximum})` : `${message} (must be less than ${maximum})`;
	}
	if (record.code === "too_small") {
		const minimum = typeof record.minimum === "number" ? record.minimum : void 0;
		if (minimum !== void 0) return inclusive ? `${message} (minimum: ${minimum})` : `${message} (must be greater than ${minimum})`;
	}
	return message;
}
function collectAllowedValuesFromIssue(issue) {
	const record = asNullableObjectRecord(issue);
	if (!record) return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	const code = typeof record.code === "string" ? record.code : "";
	if (code === "invalid_value") {
		const values = record.values;
		return Array.isArray(values) ? {
			values,
			incomplete: false,
			hasValues: values.length > 0
		} : {
			values: [],
			incomplete: true,
			hasValues: false
		};
	}
	if (code === "invalid_type") return record.expected === "boolean" ? {
		values: [true, false],
		incomplete: false,
		hasValues: true
	} : {
		values: [],
		incomplete: true,
		hasValues: false
	};
	if (code === "custom") return collectAllowedValuesFromCustomIssue(record);
	if (code !== "invalid_union") return {
		values: [],
		incomplete: false,
		hasValues: false
	};
	const nested = record.errors;
	if (!Array.isArray(nested) || nested.length === 0) return {
		values: [],
		incomplete: true,
		hasValues: false
	};
	const collected = [];
	for (const branch of nested) {
		if (!Array.isArray(branch) || branch.length === 0) return {
			values: [],
			incomplete: true,
			hasValues: false
		};
		const branchCollected = collectAllowedValuesFromIssueList(branch);
		if (branchCollected.incomplete || !branchCollected.hasValues) return {
			values: [],
			incomplete: true,
			hasValues: false
		};
		collected.push(...branchCollected.values);
	}
	return {
		values: collected,
		incomplete: false,
		hasValues: collected.length > 0
	};
}
function collectAllowedValuesFromIssueList(issues) {
	const collected = [];
	let hasValues = false;
	for (const issue of issues) {
		const branch = collectAllowedValuesFromIssue(issue);
		if (branch.incomplete) return {
			values: [],
			incomplete: true,
			hasValues: false
		};
		if (branch.hasValues) {
			hasValues = true;
			collected.push(...branch.values);
		}
	}
	return {
		values: collected,
		incomplete: false,
		hasValues
	};
}
function collectAllowedValuesFromUnknownIssue(issue) {
	const collection = collectAllowedValuesFromIssue(issue);
	return collection.incomplete || !collection.hasValues ? [] : collection.values;
}
function isBindingsIssuePath(pathSegments) {
	return pathSegments[0] === "bindings" && typeof pathSegments[1] === "number";
}
function isRouteTypeMismatchIssue(issue) {
	const issuePath = toConfigPathSegments(issue.path);
	return issuePath.length === 1 && issuePath[0] === "type" && issue.code === "invalid_value" && Array.isArray(issue.values) && issue.values.includes("route");
}
function extractBindingsSpecificUnionIssue(record, parentPathSegments) {
	if (!isBindingsIssuePath(toConfigPathSegments(record.path)) || !Array.isArray(record.errors)) return null;
	let matchingBranchIssue = null;
	let matchingBranchIsUnrecognized = false;
	let matchingBranchPathLen = -1;
	let sawRouteTypeMismatch = false;
	for (const errGroup of record.errors) {
		if (!Array.isArray(errGroup)) continue;
		const branch = errGroup.map(asNullableObjectRecord).filter(Boolean);
		if (branch.length === 0) continue;
		if (branch.some(isRouteTypeMismatchIssue)) {
			sawRouteTypeMismatch = true;
			continue;
		}
		let branchBestIssue = null;
		let branchBestIsUnrecognized = false;
		let branchBestPathLen = -1;
		for (const issue of branch) {
			const issuePathLen = toConfigPathSegments(issue.path).length;
			const issueIsUnrecognized = issue.code === "unrecognized_keys";
			if (issuePathLen > branchBestPathLen || issuePathLen === branchBestPathLen && issueIsUnrecognized && !branchBestIsUnrecognized) {
				branchBestIssue = issue;
				branchBestIsUnrecognized = issueIsUnrecognized;
				branchBestPathLen = issuePathLen;
			}
		}
		if (!branchBestIssue) continue;
		if (matchingBranchIssue) return null;
		matchingBranchIssue = branchBestIssue;
		matchingBranchIsUnrecognized = branchBestIsUnrecognized;
		matchingBranchPathLen = branchBestPathLen;
	}
	if (!sawRouteTypeMismatch || !matchingBranchIssue || matchingBranchPathLen === 0 && !matchingBranchIsUnrecognized) return null;
	const fullPathSegments = [...parentPathSegments, ...toConfigPathSegments(matchingBranchIssue.path)];
	const message = typeof matchingBranchIssue.message === "string" ? matchingBranchIssue.message : "Invalid input";
	return withConfigIssuePath({
		path: formatConfigPath(fullPathSegments),
		message
	}, fullPathSegments);
}
function mapZodIssueToConfigIssue(issue) {
	const record = asNullableObjectRecord(issue);
	const pathSegments = toConfigPathSegments(record?.path);
	const path = formatConfigPath(pathSegments);
	const message = typeof record?.message === "string" ? record.message : "Invalid input";
	const enrichedMessage = record ? appendNumericBoundHint(message, record) : message;
	const allowedValuesSummary = summarizeAllowedValues(collectAllowedValuesFromUnknownIssue(issue));
	if (record?.code === "invalid_union" && !allowedValuesSummary) {
		const betterIssue = extractBindingsSpecificUnionIssue(record, pathSegments);
		if (betterIssue) return betterIssue;
	}
	if (!allowedValuesSummary) return withConfigIssuePath({
		path,
		message: enrichedMessage
	}, pathSegments);
	return withConfigIssuePath({
		path,
		message: appendAllowedValuesHint(enrichedMessage, allowedValuesSummary),
		allowedValues: allowedValuesSummary.values,
		allowedValuesHiddenCount: allowedValuesSummary.hiddenCount
	}, pathSegments);
}
function isObjectSecretRefCandidate(value) {
	return isRecord(value) && Boolean(coerceSecretRef(value));
}
function formatUnsupportedMutableSecretRefMessage(path) {
	return [
		`SecretRef objects are not supported at ${path}.`,
		"This credential is runtime-mutable or runtime-managed and must stay a plain string value.",
		"Use a plain string (env template strings like \"${MY_VAR}\" are allowed).",
		`See ${SECRETREF_POLICY_DOC_URL}.`
	].join(" ");
}
function collectUnsupportedMutableSecretRefIssues(raw) {
	const issues = [];
	for (const candidate of unsupportedSecretRefSurfacePolicy.collectConfigCandidates(raw)) if (isObjectSecretRefCandidate(candidate.value)) issues.push({
		path: candidate.path,
		message: formatUnsupportedMutableSecretRefMessage(candidate.path)
	});
	return issues;
}
function formatFilteredUnrecognizedKeyMessage(message, keys) {
	const quotedKeys = keys.map((key) => `"${key}"`).join(", ");
	if (/must not have additional properties/i.test(message)) return `must not have additional properties: ${quotedKeys}`;
	return keys.length === 1 ? `Unrecognized key: ${quotedKeys}` : `Unrecognized keys: ${quotedKeys}`;
}
function filterUnsupportedMutableSecretRefSchemaIssue(params) {
	const { issue, policyIssue } = params;
	if (issue.path === policyIssue.path) return /expected string, received object/i.test(issue.message) ? null : issue;
	if (!issue.path || !policyIssue.path || !policyIssue.path.startsWith(`${issue.path}.`)) return issue;
	const childKey = policyIssue.path.slice(issue.path.length + 1).split(".")[0];
	if (!childKey || !/Unrecognized key|must not have additional properties/i.test(issue.message)) return issue;
	const unrecognizedKeys = [...issue.message.matchAll(/"([^"]+)"/g)].map((match) => match[1]);
	if (!unrecognizedKeys.includes(childKey)) return issue;
	const remainingKeys = unrecognizedKeys.filter((key) => key !== void 0 && key !== childKey);
	return remainingKeys.length === 0 ? null : {
		...issue,
		message: formatFilteredUnrecognizedKeyMessage(issue.message, remainingKeys)
	};
}
function mergeUnsupportedMutableSecretRefIssues(policyIssues, schemaIssues) {
	if (policyIssues.length === 0) return schemaIssues;
	const filteredSchemaIssues = schemaIssues.flatMap((issue) => {
		let filteredIssue = issue;
		for (const policyIssue of policyIssues) {
			if (!filteredIssue) return [];
			filteredIssue = filterUnsupportedMutableSecretRefSchemaIssue({
				issue: filteredIssue,
				policyIssue
			});
		}
		return filteredIssue ? [filteredIssue] : [];
	});
	return [...policyIssues, ...filteredSchemaIssues];
}
function collectUnsupportedSecretRefPolicyIssues(raw) {
	return collectUnsupportedMutableSecretRefIssues(raw);
}
//#endregion
//#region src/config/validation-core.ts
function collectHeartbeatOwnerWarnings(config) {
	const agentEntries = listAgentEntries(config);
	return listAgentIds(config).length > 1 && !agentEntries.some((entry) => Boolean(entry.heartbeat)) && !config.agents?.defaults?.heartbeat && tryResolveAmbientOwnerAgentId(config) === void 0 ? [{
		path: "agents.defaults.heartbeat.agentId",
		message: "Multi-agent config has no ambient heartbeat owner; heartbeats stay disabled until agents.defaults.heartbeat.agentId or agents.defaults.systemAgent.agentId is set."
	}] : [];
}
function materializeBundledModelProviderOverlays(config) {
	const providers = config.models?.providers;
	if (!providers) return config;
	let nextProviders;
	for (const [providerId, providerConfig] of Object.entries(providers)) {
		if (!isBuiltInModelProviderOverlayId(providerId) || providerConfig.baseUrl && Array.isArray(providerConfig.models)) continue;
		nextProviders ??= { ...providers };
		nextProviders[providerId] = {
			...providerConfig,
			baseUrl: providerConfig.baseUrl ?? "",
			models: providerConfig.models ?? []
		};
	}
	return nextProviders ? {
		...config,
		models: {
			...config.models,
			providers: nextProviders
		}
	} : config;
}
function stripPreservedLegacyRootKeysForValidation(raw, keys) {
	if (!keys || keys.length === 0 || !isRecord(raw)) return raw;
	const next = { ...raw };
	for (const key of keys) delete next[key];
	return next;
}
function collectMcpServerNameIssues(raw) {
	if (!isRecord(raw)) return [];
	const mcp = isRecord(raw.mcp) ? raw.mcp : void 0;
	const nodeHost = isRecord(raw.nodeHost) ? raw.nodeHost : void 0;
	const nodeHostMcp = isRecord(nodeHost?.mcp) ? nodeHost.mcp : void 0;
	const locations = [{
		path: ["mcp", "servers"],
		servers: isRecord(mcp?.servers) ? mcp.servers : void 0,
		schema: McpServerNameSchema
	}, {
		path: [
			"nodeHost",
			"mcp",
			"servers"
		],
		servers: isRecord(nodeHostMcp?.servers) ? nodeHostMcp.servers : void 0,
		schema: NodeHostMcpServerNameSchema
	}];
	const issues = [];
	for (const location of locations) for (const serverName of Object.keys(location.servers ?? {})) {
		const result = location.schema.safeParse(serverName);
		if (result.success) continue;
		const pathSegments = [...location.path, serverName];
		for (const issue of result.error.issues) issues.push(withConfigIssuePath({
			path: pathSegments.join("."),
			message: issue.message
		}, pathSegments));
	}
	return issues;
}
function isWorkspaceAvatarPath(value, workspaceDir) {
	const workspaceRoot = path.resolve(workspaceDir);
	const resolved = path.resolve(workspaceRoot, value);
	return isPathWithinRoot(workspaceRoot, resolved);
}
function createIdentityAvatarIssue(source, message) {
	const pathSegments = source.kind === "entries" ? [
		"agents",
		"entries",
		source.key,
		"identity",
		"avatar"
	] : [
		"agents",
		"list",
		source.index,
		"identity",
		"avatar"
	];
	return withConfigIssuePath({
		path: pathSegments.join("."),
		message
	}, pathSegments);
}
function validateIdentityAvatar(config, env) {
	const agents = listAgentEntriesWithSource(config);
	if (agents.length === 0) return [];
	const issues = [];
	for (const { entry, source } of agents) {
		const avatarRaw = entry.identity?.avatar;
		if (typeof avatarRaw !== "string") continue;
		const avatar = avatarRaw.trim();
		if (!avatar || isAvatarDataUrl(avatar) || isAvatarHttpUrl(avatar)) continue;
		if (avatar.startsWith("~")) {
			issues.push(createIdentityAvatarIssue(source, "identity.avatar must be a workspace-relative path, http(s) URL, or data URI."));
			continue;
		}
		if (hasAvatarUriScheme(avatar) && !isWindowsAbsolutePath(avatar)) {
			issues.push(createIdentityAvatarIssue(source, "identity.avatar must be a workspace-relative path, http(s) URL, or data URI."));
			continue;
		}
		if (!isWorkspaceAvatarPath(avatar, resolveAgentWorkspaceDir(config, entry.id ?? resolveAmbientOwnerAgentId(config), env))) issues.push(createIdentityAvatarIssue(source, "identity.avatar must stay within the agent workspace."));
	}
	return issues;
}
function validateGatewayTailscaleBind(config) {
	const tailscaleMode = config.gateway?.tailscale?.mode ?? "off";
	if (tailscaleMode !== "serve" && tailscaleMode !== "funnel") return [];
	const bindMode = config.gateway?.bind ?? "loopback";
	if (bindMode === "loopback") return [];
	const customBindHost = config.gateway?.customBindHost;
	if (bindMode === "custom" && isCanonicalDottedDecimalIPv4(customBindHost) && isLoopbackIpAddress(customBindHost)) return [];
	return [{
		path: "gateway.bind",
		message: `gateway.bind must resolve to loopback when gateway.tailscale.mode=${tailscaleMode} (use gateway.bind="loopback" or gateway.bind="custom" with gateway.customBindHost="127.0.0.1")`
	}];
}
function validateGatewayTailscaleAuth(config) {
	const tailscaleMode = config.gateway?.tailscale?.mode ?? "off";
	if (!isUnsafeGatewayTailscaleNoAuth({
		authMode: config.gateway?.auth?.mode,
		tailscaleMode
	})) return [];
	return [{
		path: "gateway.auth.mode",
		message: formatUnsafeGatewayTailscaleNoAuthMessage(tailscaleMode)
	}];
}
function collectModelPolicyAllowIssues(config) {
	const issues = [];
	const defaultModels = config.agents?.defaults?.models;
	const validateRefs = (refs, configPath, isValidRef) => {
		for (const [index, raw] of (refs ?? []).entries()) {
			if (isValidRef(raw)) continue;
			issues.push({
				path: `${configPath}.${index}`,
				message: `invalid model policy ref: ${sanitizeForLog(JSON.stringify(raw))}. Use a configured alias, an exact "provider/model" ref, or a trailing prefix wildcard such as "provider/*" or "provider/namespace/*".`
			});
		}
	};
	validateRefs(config.agents?.defaults?.modelPolicy?.allow, "agents.defaults.modelPolicy.allow", createModelPolicyRefValidator(defaultModels));
	for (const { entry: agent, source } of listAgentEntriesWithSource(config)) {
		const pathPrefix = source.kind === "entries" ? `agents.entries.${source.key}` : `agents.list.${source.index}`;
		validateRefs(agent.modelPolicy?.allow, `${pathPrefix}.modelPolicy.allow`, createModelPolicyRefValidator(defaultModels, agent.models));
	}
	return issues;
}
function collectSandboxContainerEnvIssues(config, sourceRaw) {
	const agents = listAgentEntriesWithSource(config);
	if (!config.agents?.defaults?.sandbox?.docker?.env && !agents.some(({ entry }) => entry.sandbox?.docker?.env)) return [];
	const issues = [];
	const seen = /* @__PURE__ */ new Set();
	const authoredAgents = isRecord(sourceRaw) ? listAgentEntriesWithSource(sourceRaw) : agents;
	const authoredById = new Map(authoredAgents.map((agent) => [agent.entry.id, agent]));
	const defaultSandbox = config.agents?.defaults?.sandbox;
	const effectiveAgents = agents.length > 0 ? agents : [void 0];
	for (const agent of effectiveAgents) {
		const agentSandbox = agent?.entry.sandbox;
		const scope = resolveSandboxScope({ scope: agentSandbox?.scope ?? defaultSandbox?.scope });
		const backend = agentSandbox?.backend?.trim() || defaultSandbox?.backend?.trim() || "docker";
		if (backend !== "docker" && backend !== "podman") continue;
		const env = resolveSandboxDockerEnv({
			scope,
			globalEnv: defaultSandbox?.docker?.env,
			agentEnv: agentSandbox?.docker?.env
		});
		const authoredAgent = agent ? authoredById.get(agent.entry.id) ?? agent : void 0;
		for (const [key, value] of Object.entries(env)) {
			const reason = getContainerEnvFileEntryIssue(key, value);
			if (!reason) continue;
			const pathSegments = scope !== "shared" && authoredAgent !== void 0 && Object.hasOwn(authoredAgent.entry.sandbox?.docker?.env ?? {}, key) && authoredAgent ? authoredAgent.source.kind === "entries" ? [
				"agents",
				"entries",
				authoredAgent.source.key,
				"sandbox",
				"docker",
				"env",
				key
			] : [
				"agents",
				"list",
				authoredAgent.source.index,
				"sandbox",
				"docker",
				"env",
				key
			] : [
				"agents",
				"defaults",
				"sandbox",
				"docker",
				"env",
				key
			];
			const issuePath = pathSegments.join(".");
			const issueIdentity = JSON.stringify([issuePath, reason]);
			if (seen.has(issueIdentity)) continue;
			seen.add(issueIdentity);
			const backendName = backend === "podman" ? "Podman" : "Docker";
			const remediation = reason === "invalid-name" ? `Rename key ${JSON.stringify(key)} to use letters, digits, and underscores without a leading digit.` : `Use a single-line, non-NUL value for key ${JSON.stringify(key)}, or deliver multiline material through a mounted file or custom image.`;
			issues.push(withConfigIssuePath({
				path: issuePath,
				message: `${backendName} sandbox backend requires portable environment names and single-line, non-NUL values because the secure env-file transport is line-delimited. ${remediation} SSH/OpenShell backends may keep multiline values. Run openclaw doctor to report the invalid path; manual remediation is required.`
			}, pathSegments));
		}
	}
	return issues;
}
/**
* Validates config without applying runtime defaults.
* Use this when you need the raw validated config (e.g., for writing back to file).
*/
function validateConfigObjectRaw(raw, opts) {
	const legacyDefaultAgentId = isRecord(raw) ? tryGetLegacyDefaultAgentId(raw) : void 0;
	let normalizedRaw = stripPreservedLegacyRootKeysForValidation(raw, opts?.preservedLegacyRootKeys);
	let syntheticLegacyOwnership = false;
	if (legacyDefaultAgentId && isRecord(normalizedRaw) && isRecord(normalizedRaw.agents)) {
		const entries = normalizedRaw.agents.entries;
		if (isRecord(entries) && Object.keys(entries).length > 1 && normalizedRaw.agents.ownership === void 0) {
			normalizedRaw = {
				...normalizedRaw,
				agents: {
					...normalizedRaw.agents,
					ownership: "explicit"
				}
			};
			syntheticLegacyOwnership = true;
		}
	}
	const normalizedMcpServerNameIssueKeys = new Set(collectMcpServerNameIssues(normalizedRaw).map((issue) => JSON.stringify([issue.path, issue.message])));
	const mcpServerNameIssues = collectMcpServerNameIssues(opts?.sourceRaw).filter((issue) => !normalizedMcpServerNameIssueKeys.has(JSON.stringify([issue.path, issue.message])));
	const policyIssues = collectUnsupportedSecretRefPolicyIssues(normalizedRaw);
	const validated = OpenClawSchema.safeParse(normalizedRaw);
	if (!validated.success || mcpServerNameIssues.length > 0) return {
		ok: false,
		issues: mergeUnsupportedMutableSecretRefIssues(policyIssues, validated.success ? mcpServerNameIssues : [...mcpServerNameIssues, ...validated.error.issues.map(mapZodIssueToConfigIssue)])
	};
	let parsedConfig = validated.data;
	if (syntheticLegacyOwnership && parsedConfig.agents) {
		const agents = { ...parsedConfig.agents };
		delete agents.ownership;
		parsedConfig = {
			...parsedConfig,
			agents
		};
	}
	const validatedConfig = inheritLegacyDefaultAgentId(raw, attachAgentListProjection(materializeBundledModelProviderOverlays(parsedConfig)));
	const channelIssues = policyIssues.length > 0 || opts?.validateBundledChannels ? collectRawBundledChannelConfigIssues(validatedConfig) : [];
	if (channelIssues.length > 0) return {
		ok: false,
		issues: mergeUnsupportedMutableSecretRefIssues(policyIssues, channelIssues)
	};
	if (policyIssues.length > 0) return {
		ok: false,
		issues: policyIssues
	};
	const sandboxContainerEnvIssues = collectSandboxContainerEnvIssues(validatedConfig, opts?.sourceRaw);
	if (sandboxContainerEnvIssues.length > 0) return {
		ok: false,
		issues: sandboxContainerEnvIssues
	};
	const duplicates = findDuplicateAgentDirs(validatedConfig, opts);
	if (duplicates.length > 0) return {
		ok: false,
		issues: [{
			path: "agents.entries",
			message: formatDuplicateAgentDirError(duplicates)
		}]
	};
	const avatarIssues = validateIdentityAvatar(validatedConfig, opts?.env);
	if (avatarIssues.length > 0) return {
		ok: false,
		issues: avatarIssues
	};
	const gatewayTailscaleBindIssues = validateGatewayTailscaleBind(validatedConfig);
	if (gatewayTailscaleBindIssues.length > 0) return {
		ok: false,
		issues: gatewayTailscaleBindIssues
	};
	const gatewayTailscaleAuthIssues = validateGatewayTailscaleAuth(validatedConfig);
	if (gatewayTailscaleAuthIssues.length > 0) return {
		ok: false,
		issues: gatewayTailscaleAuthIssues
	};
	const modelPolicyAllowIssues = collectModelPolicyAllowIssues(validatedConfig);
	if (modelPolicyAllowIssues.length > 0) return {
		ok: false,
		issues: modelPolicyAllowIssues
	};
	return {
		ok: true,
		config: validatedConfig
	};
}
function validateConfigObject(raw, opts) {
	const result = validateConfigObjectRaw(migratePersistedImplicitMainRoster(raw).config, opts);
	if (!result.ok) return result;
	return {
		ok: true,
		config: attachAgentListProjection(materializeRuntimeConfig(result.config, { manifestRegistry: opts?.manifestRegistry }))
	};
}
//#endregion
//#region src/config/validation-plugin-config.ts
const BLOCKED_PLUGIN_CANDIDATE_PREFIX = "blocked plugin candidate:";
function collectExplicitPluginReferences(raw) {
	const references = {
		entries: /* @__PURE__ */ new Set(),
		allow: /* @__PURE__ */ new Set(),
		deny: /* @__PURE__ */ new Set(),
		slots: /* @__PURE__ */ new Map()
	};
	if (!isRecord(raw) || !isRecord(raw.plugins)) return references;
	const { plugins } = raw;
	if (isRecord(plugins.entries)) for (const pluginId of Object.keys(plugins.entries)) {
		const normalized = normalizePluginId(pluginId);
		if (normalized) references.entries.add(normalized);
	}
	for (const [key, target] of [["allow", references.allow], ["deny", references.deny]]) {
		const value = plugins[key];
		if (!Array.isArray(value)) continue;
		for (const entry of value) if (typeof entry === "string") {
			const normalized = normalizePluginId(entry);
			if (normalized) target.add(normalized);
		}
	}
	if (isRecord(plugins.slots)) for (const [slotId, pluginId] of Object.entries(plugins.slots)) {
		if (typeof pluginId !== "string") continue;
		const normalized = normalizePluginId(pluginId);
		if (normalized && normalized !== "none") references.slots.set(normalized, slotId);
	}
	return references;
}
function resolveExplicitPluginReferencePath(references, pluginId) {
	const normalized = normalizePluginId(pluginId);
	if (!normalized) return;
	if (references.entries.has(normalized)) return `plugins.entries.${normalized}`;
	if (references.allow.has(normalized)) return "plugins.allow";
	if (references.deny.has(normalized)) return "plugins.deny";
	const slotId = references.slots.get(normalized);
	return slotId ? `plugins.slots.${slotId}` : void 0;
}
function formatRemovedPluginConfigWarning(pluginId) {
	if (pluginId === "skill-workshop") return "plugin removed: skill-workshop (stale plugin config ignored; Skill Workshop is built into OpenClaw skills now. Use skills.workshop settings and openclaw skills workshop commands, then remove this plugins config entry)";
	return `plugin removed: ${pluginId} (stale config entry ignored; remove it from plugins config)`;
}
function formatMissingOfficialExternalPluginWarning(pluginId, opts) {
	const catalogEntry = getOfficialExternalPluginCatalogEntry(pluginId);
	if (!catalogEntry) return null;
	const installSpec = resolveOfficialExternalPluginInstallSources(catalogEntry)[0]?.spec;
	if (!installSpec) return null;
	if (pluginId === "memory-lancedb" && opts?.selectedMissingMemorySlot) return `plugin not installed: ${pluginId} — gateway will run without persistent memory until installed; install the official external plugin with: openclaw plugins install ${installSpec}`;
	return `plugin not installed: ${pluginId} — install the official external plugin with: openclaw plugins install ${installSpec}`;
}
function validateExplicitPluginConfig(params) {
	const { raw, config, env, applyDefaults, registry, knownIds, normalizedPlugins, ensureCompatPluginIds, ensureOverriddenPluginIds, issues, warnings } = params;
	const blockedPluginDiagnostics = /* @__PURE__ */ new Map();
	const blockedPluginDiagnosticsWithSource = [];
	const normalizeBlockedDiagnosticPath = (value) => {
		const trimmed = value?.trim();
		if (!trimmed) return "";
		try {
			return path.resolve(resolveUserPath(trimmed, env ?? process.env));
		} catch {
			return path.resolve(trimmed);
		}
	};
	for (const diag of registry.diagnostics) {
		if (!diag.message.startsWith(BLOCKED_PLUGIN_CANDIDATE_PREFIX)) continue;
		if (!diag.pluginId && diag.source) blockedPluginDiagnosticsWithSource.push({
			message: diag.message,
			source: diag.source
		});
		if (diag.pluginId) {
			const normalizedPluginId = normalizePluginId(diag.pluginId);
			for (const key of [diag.pluginId, normalizedPluginId]) if (key && !blockedPluginDiagnostics.has(key)) blockedPluginDiagnostics.set(key, {
				message: diag.message,
				...diag.source ? { source: diag.source } : {}
			});
		}
	}
	const blockedDiagnosticSourceMatchesPluginId = (diagnostic, pluginId) => {
		const normalizedPluginId = normalizePluginId(pluginId);
		if (!normalizedPluginId) return false;
		const sourcePath = normalizeBlockedDiagnosticPath(diagnostic.source);
		if (!sourcePath) return false;
		if (normalizePluginId(path.basename(sourcePath)) === normalizedPluginId || normalizePluginId(path.basename(path.dirname(sourcePath))) === normalizedPluginId) return true;
		const loadPaths = config.plugins?.load?.paths;
		if (!Array.isArray(loadPaths)) return false;
		for (const loadPath of loadPaths) {
			if (typeof loadPath !== "string") continue;
			const resolvedLoadPath = normalizeBlockedDiagnosticPath(loadPath);
			if (resolvedLoadPath && normalizePluginId(path.basename(resolvedLoadPath)) === normalizedPluginId && (sourcePath === resolvedLoadPath || isPathInside(resolvedLoadPath, sourcePath) || isPathInside(sourcePath, resolvedLoadPath))) return true;
		}
		return false;
	};
	const findBlockedPluginDiagnostic = (pluginId) => blockedPluginDiagnostics.get(pluginId) ?? blockedPluginDiagnostics.get(normalizePluginId(pluginId)) ?? blockedPluginDiagnosticsWithSource.find((diagnostic) => blockedDiagnosticSourceMatchesPluginId(diagnostic, pluginId));
	const missingOfficialPluginWarningIds = /* @__PURE__ */ new Set();
	const pushMissingPluginIssue = (issuePath, pluginId, options) => {
		if (isRetiredPluginId(pluginId)) {
			warnings.push({
				path: issuePath,
				message: formatRemovedPluginConfigWarning(pluginId)
			});
			return;
		}
		const blockedDiagnostic = findBlockedPluginDiagnostic(pluginId);
		if (blockedDiagnostic) {
			const message = `plugin present but blocked: ${pluginId} (see preceding plugin warning${blockedDiagnostic.source ? `; source: ${blockedDiagnostic.source}` : ""}; fix the blocked plugin path instead of removing config)`;
			(options?.warnOnly ? warnings : issues).push({
				path: issuePath,
				message
			});
			return;
		}
		if (normalizePluginId(pluginId) === "codex" && issuePath === "plugins.entries.codex" && shouldSuppressMissingCodexPluginDiagnostics(config, env ?? process.env, isRecord(raw) ? raw : void 0)) return;
		if (options?.warnOnly && options.officialInstallHint !== false) {
			const externalInstallWarning = options.missingMessage ?? formatMissingOfficialExternalPluginWarning(pluginId);
			if (externalInstallWarning) {
				const normalizedPluginId = normalizePluginId(pluginId);
				if (!options.missingMessage && normalizedPluginId) {
					if (missingOfficialPluginWarningIds.has(normalizedPluginId)) return;
					missingOfficialPluginWarningIds.add(normalizedPluginId);
				}
				warnings.push({
					path: issuePath,
					message: externalInstallWarning
				});
				return;
			}
		}
		const message = options?.warnOnly ? `plugin not found: ${pluginId} (stale config entry ignored; remove it from plugins config)` : `plugin not found: ${pluginId}`;
		(options?.warnOnly ? warnings : issues).push({
			path: issuePath,
			message
		});
	};
	const pluginsConfig = config.plugins;
	const entries = pluginsConfig?.entries;
	const hasIntentionalDisableMarker = (pluginId) => isExplicitPluginDisableMarker(entries?.[pluginId]) && !isRetiredPluginId(pluginId);
	if (entries && isRecord(entries)) {
		for (const pluginId of Object.keys(entries)) if (!knownIds.has(pluginId) && !hasIntentionalDisableMarker(pluginId)) pushMissingPluginIssue(`plugins.entries.${pluginId}`, pluginId, { warnOnly: true });
	}
	for (const pluginId of pluginsConfig?.allow ?? []) {
		if (typeof pluginId !== "string" || !pluginId.trim() || knownIds.has(pluginId)) continue;
		const commandAlias = resolveManifestCommandAliasOwnerInRegistry({
			command: pluginId,
			registry
		});
		if (commandAlias?.pluginId && knownIds.has(commandAlias.pluginId)) warnings.push({
			path: "plugins.allow",
			message: `"${pluginId}" is not a plugin — it is a command provided by the "${commandAlias.pluginId}" plugin. Use "${commandAlias.pluginId}" in plugins.allow instead.`
		});
		else if (!hasIntentionalDisableMarker(pluginId)) pushMissingPluginIssue("plugins.allow", pluginId, { warnOnly: true });
	}
	for (const pluginId of pluginsConfig?.deny ?? []) if (typeof pluginId === "string" && pluginId.trim() && !knownIds.has(pluginId)) pushMissingPluginIssue("plugins.deny", pluginId, {
		warnOnly: true,
		officialInstallHint: false
	});
	const pluginSlots = pluginsConfig?.slots;
	const hasExplicitMemorySlot = pluginSlots !== void 0 && Object.hasOwn(pluginSlots, "memory");
	const memorySlot = normalizedPlugins.slots.memory;
	if (hasExplicitMemorySlot && typeof memorySlot === "string" && memorySlot.trim() && !knownIds.has(memorySlot)) {
		const missingMessage = formatMissingOfficialExternalPluginWarning(memorySlot, { selectedMissingMemorySlot: true });
		pushMissingPluginIssue("plugins.slots.memory", memorySlot, {
			warnOnly: memorySlot === "memory-lancedb" && Boolean(missingMessage) && !findBlockedPluginDiagnostic(memorySlot),
			missingMessage
		});
	}
	let selectedMemoryPluginId = null;
	const seenPlugins = /* @__PURE__ */ new Set();
	for (const record of registry.plugins) {
		const pluginId = record.id;
		if (seenPlugins.has(pluginId)) continue;
		seenPlugins.add(pluginId);
		const entry = normalizedPlugins.entries[pluginId];
		const entryHasConfig = Boolean(entry?.config);
		const activationState = resolveEffectivePluginActivationState({
			id: pluginId,
			origin: record.origin,
			channelIds: record.channels,
			config: normalizedPlugins,
			rootConfig: config,
			enabledByDefault: isPluginEnabledByDefaultForPlatform(record)
		});
		let enabled = activationState.activated;
		let reason = activationState.reason;
		if (enabled) {
			const memoryDecision = resolveMemorySlotDecision({
				id: pluginId,
				kind: record.kind,
				slot: memorySlot,
				selectedId: selectedMemoryPluginId
			});
			if (!memoryDecision.enabled) {
				enabled = false;
				reason = memoryDecision.reason;
			}
			if (memoryDecision.selected && hasKind(record.kind, "memory")) selectedMemoryPluginId = pluginId;
		}
		const shouldReplacePluginConfig = entryHasConfig || applyDefaults && enabled;
		if (enabled || entryHasConfig) {
			if (record.configSchema) {
				const result = validatePluginSchemaValue({
					origin: record.origin,
					schema: record.configSchema,
					cacheKey: record.schemaCacheKey ?? record.manifestPath ?? pluginId,
					value: entry?.config ?? {},
					applyDefaults: true
				});
				if (!result.ok) for (const error of result.errors) {
					const base = `plugins.entries.${pluginId}.config`;
					issues.push({
						path: !error.path || error.path === "<root>" ? base : `${base}.${error.path}`,
						message: `invalid config: ${error.message}`,
						allowedValues: error.allowedValues,
						allowedValuesHiddenCount: error.allowedValuesHiddenCount
					});
				}
				else if (shouldReplacePluginConfig) params.replacePluginEntryConfig(pluginId, result.value);
			} else if (record.format === "bundle") {} else issues.push({
				path: `plugins.entries.${pluginId}`,
				message: `plugin schema missing for ${pluginId}`
			});
		}
		const suppressDisabledConfigWarning = ensureCompatPluginIds().has(pluginId) && !ensureOverriddenPluginIds().has(pluginId);
		if (!enabled && entryHasConfig && !suppressDisabledConfigWarning) warnings.push({
			path: `plugins.entries.${pluginId}`,
			message: `plugin disabled (${reason ?? "disabled"}) but config is present`
		});
	}
}
//#endregion
//#region src/config/validation.ts
function collectSecretRefProviderSourceIssues(params) {
	const issues = [];
	for (const target of discoverConfigSecretTargets(params.config, {
		env: params.env,
		manifestRegistry: params.manifestRegistry
	})) {
		const { ref } = resolveSecretInputRef({
			value: target.value,
			refValue: target.refValue,
			defaults: params.config.secrets?.defaults
		});
		if (!ref) continue;
		const configuredSource = resolveSecretRefProviderSourceMismatch(params.config, ref);
		if (!configuredSource) continue;
		const path = target.refPath ?? target.path;
		const pathSegments = target.refPathSegments ?? target.pathSegments;
		issues.push(withConfigIssuePath({
			path,
			message: `Secret provider "${ref.provider}" has source "${configuredSource}" but ref requests "${ref.source}".`
		}, pathSegments));
	}
	return issues;
}
function validateConfigObjectWithPlugins(raw, params) {
	return validateConfigObjectWithPluginMode(raw, params, true);
}
function validateConfigObjectRawWithPlugins(raw, params) {
	return validateConfigObjectWithPluginMode(raw, params, false);
}
function validateConfigObjectWithPluginMode(raw, params, applyDefaults) {
	const contextBudgetConfig = migrateLegacyContextBudgetConfig(raw).config;
	const migrated = migratePersistedImplicitMainRoster(contextBudgetConfig, {
		env: params?.env,
		homedir: params?.homedir
	}).config;
	let manifestRegistry = params?.pluginMetadataSnapshot?.manifestRegistry;
	const result = validateConfigObjectWithPluginsBase(migrated, {
		...params,
		applyDefaults,
		pluginValidation: params?.pluginValidation ?? "full",
		semanticValidation: params?.semanticValidation ?? "runtime",
		onManifestRegistryResolved: (registry) => {
			manifestRegistry = registry;
		}
	});
	const legacyDefaultAgentId = tryGetLegacyDefaultAgentId(migrated);
	if (!result.ok || !legacyDefaultAgentId || params?.pluginValidation === "core-only") return result;
	const config = materializeLegacyAgentOwnershipForActiveChannelsResult(inheritLegacyDefaultAgentId(migrated, result.config), legacyDefaultAgentId, params?.env, manifestRegistry?.plugins).config;
	return {
		...result,
		config
	};
}
function materializeLegacyAgentOwnershipForActiveChannelsResult(config, legacyDefaultAgentId, env, manifestRecords, options) {
	const ambientChannelIds = listChannelIdsForOwnershipMigration({
		config,
		env,
		...manifestRecords ? { manifestRecords } : {}
	});
	const materialized = materializeLegacyDefaultAgentRoles(config, legacyDefaultAgentId, {
		ambientChannelIds,
		env,
		homedir: options?.homedir,
		materializeSessionStore: options?.materializeSessionStore,
		materializeWorkspace: options?.materializeWorkspace
	});
	const next = inheritLegacyDefaultAgentId(config, materialized.config);
	return {
		...materialized,
		config: next
	};
}
function validateConfigObjectWithPluginsBase(raw, opts) {
	const base = validateConfigObjectRaw(raw, {
		sourceRaw: opts.sourceRaw,
		preservedLegacyRootKeys: opts.preservedLegacyRootKeys,
		env: opts.env,
		homedir: opts.homedir
	});
	if (!base.ok) return {
		ok: false,
		issues: base.issues,
		warnings: []
	};
	const parsedConfig = inheritLegacyDefaultAgentId(raw, base.config);
	const rememberRegistry = (registry) => {
		opts.onManifestRegistryResolved?.(registry);
		return { registry };
	};
	let registryInfo = opts.pluginMetadataSnapshot ? rememberRegistry(opts.pluginMetadataSnapshot.manifestRegistry) : null;
	if (opts.applyDefaults && !registryInfo && opts.pluginValidation !== "core-only") {
		const pluginMetadataSnapshot = opts.loadPluginMetadataSnapshot?.(parsedConfig);
		if (pluginMetadataSnapshot) registryInfo = rememberRegistry(pluginMetadataSnapshot.manifestRegistry);
	}
	const config = opts.applyDefaults ? materializeRuntimeConfig(parsedConfig, {
		env: opts.env,
		homedir: opts.homedir,
		manifestRegistry: registryInfo?.registry ?? (opts.pluginValidation === "core-only" ? { plugins: [] } : void 0)
	}) : parsedConfig;
	if (opts.pluginValidation === "skip" || opts.pluginValidation === "core-only") return {
		ok: true,
		config,
		warnings: []
	};
	const issues = [];
	const warnings = [];
	warnings.push(...collectHeartbeatOwnerWarnings(config));
	const hasExplicitPluginsConfig = isRecord(raw) && Object.hasOwn(raw, "plugins");
	const explicitPluginReferences = collectExplicitPluginReferences(raw);
	const formatChannelConfigIssueMessage = (message, pluginId) => {
		const safePluginId = pluginId ? sanitizeForLog(pluginId).trim() : "";
		return safePluginId ? `invalid config for plugin ${safePluginId}: ${message}` : formatRawChannelConfigIssueMessage(message);
	};
	let compatPluginIds = null;
	let registryDiagnosticsPushed = false;
	const pushRegistryDiagnostics = (registry) => {
		if (registryDiagnosticsPushed) return;
		registryDiagnosticsPushed = true;
		for (const diag of registry.diagnostics) {
			const explicitPath = diag.pluginId ? resolveExplicitPluginReferencePath(explicitPluginReferences, diag.pluginId) : void 0;
			let issuePath = explicitPath ?? "plugins";
			if (!diag.pluginId && diag.message.includes("plugin path not found")) issuePath = "plugins.load.paths";
			const pluginLabel = diag.pluginId ? `plugin ${diag.pluginId}` : "plugin";
			const issue = {
				path: issuePath,
				message: `${pluginLabel}: ${diag.message}`
			};
			if (diag.level === "error" && (explicitPath || !diag.pluginId)) issues.push(issue);
			else warnings.push(issue);
		}
	};
	const loadValidationRegistry = () => {
		const pluginMetadataSnapshot = opts.loadPluginMetadataSnapshot?.(config);
		if (pluginMetadataSnapshot) {
			registryInfo = rememberRegistry(pluginMetadataSnapshot.manifestRegistry);
			return registryInfo;
		}
		const registry = resolveConfigWidePluginManifestRegistry({
			config,
			env: opts.env ?? process.env
		});
		registryInfo = rememberRegistry(registry);
		return registryInfo;
	};
	const ensureLoadedRegistryInfo = () => registryInfo ?? loadValidationRegistry();
	const ensureCompatPluginIds = () => {
		if (compatPluginIds) return compatPluginIds;
		const allow = config.plugins?.allow;
		if (!Array.isArray(allow) || allow.length === 0) {
			compatPluginIds = /* @__PURE__ */ new Set();
			return compatPluginIds;
		}
		const { registry } = registryInfo ?? loadValidationRegistry();
		const overriddenBundledPluginIds = ensureOverriddenPluginIds();
		compatPluginIds = new Set(registry.plugins.filter((plugin) => plugin.origin === "bundled" && (plugin.contracts?.webSearchProviders?.length ?? 0) > 0 && !overriddenBundledPluginIds.has(plugin.id)).map((plugin) => plugin.id));
		return compatPluginIds;
	};
	const ensureRegistry = () => {
		const info = ensureLoadedRegistryInfo();
		pushRegistryDiagnostics(info.registry);
		return info;
	};
	const ensureKnownIds = () => {
		const info = ensureRegistry();
		info.knownIds ??= new Set(info.registry.plugins.map((record) => record.id));
		return info.knownIds;
	};
	const ensureOverriddenPluginIds = () => {
		const info = ensureRegistry();
		info.overriddenPluginIds ??= new Set(info.registry.diagnostics.filter((diag) => diag.message.includes("duplicate plugin id detected")).map((diag) => diag.pluginId).filter((pluginId) => typeof pluginId === "string" && pluginId !== ""));
		return info.overriddenPluginIds;
	};
	const ensureNormalizedPlugins = () => {
		const info = ensureRegistry();
		info.normalizedPlugins ??= normalizePluginsConfig(config.plugins);
		return info.normalizedPlugins;
	};
	const ensureChannelSchemas = () => {
		const info = ensureRegistry();
		if (!info.channelSchemas) {
			info.channelSchemas = new Map(GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.map((entry) => [entry.channelId, {
				schema: entry.schema,
				origin: "bundled"
			}]));
			const selection = resolveChannelSchemaSelection(info.registry, parsedConfig, opts.env);
			for (const entry of collectChannelSchemaMetadataWithOwnership(info.registry, selection)) {
				const current = info.channelSchemas.get(entry.id);
				if (entry.configSchema) info.channelSchemas.set(entry.id, {
					schema: entry.configSchema,
					pluginId: entry.schemaPluginOrigin === "bundled" ? void 0 : entry.schemaPluginId,
					origin: entry.schemaPluginOrigin
				});
				else if (!current) info.channelSchemas.set(entry.id, { origin: entry.schemaPluginOrigin });
			}
		}
		return info.channelSchemas;
	};
	const ensureChannelDmAllowFromModes = () => {
		const info = ensureLoadedRegistryInfo();
		info.channelDmAllowFromModes ??= new Map(collectChannelDmPolicyMetadata(info.registry).flatMap((entry) => entry.dmAllowFromMode ? [[entry.id, entry.dmAllowFromMode]] : []));
		return info.channelDmAllowFromModes;
	};
	warnings.push(...hasChannelDmPolicyDependencyWarningCandidates(parsedConfig) ? collectChannelDmPolicyDependencyWarnings(parsedConfig, { dmAllowFromModes: ensureChannelDmAllowFromModes() }) : collectChannelDmPolicyDependencyWarnings(parsedConfig));
	let mutatedConfig = config;
	let channelsCloned = false;
	let pluginsCloned = false;
	let pluginEntriesCloned = false;
	let installedPluginRecordIds;
	const ensureInstalledPluginRecordIds = () => {
		if (installedPluginRecordIds) return installedPluginRecordIds;
		try {
			installedPluginRecordIds = new Set(Object.keys(loadInstalledPluginIndexInstallRecordsSync({ env: opts.env })).map(normalizePluginId));
		} catch {
			installedPluginRecordIds = /* @__PURE__ */ new Set();
		}
		return installedPluginRecordIds;
	};
	const hasStalePluginEvidenceForUnknownChannel = (channelId) => {
		const normalizedChannelId = normalizePluginId(channelId);
		if (!normalizedChannelId || ensureKnownIds().has(normalizedChannelId)) return false;
		const pluginConfig = config.plugins;
		const matches = (pluginId) => normalizePluginId(pluginId) === normalizedChannelId;
		return Array.isArray(pluginConfig?.allow) && pluginConfig.allow.some(matches) || isRecord(pluginConfig?.entries) && Object.keys(pluginConfig.entries).some(matches) || isRecord(pluginConfig?.installs) && Object.keys(pluginConfig.installs).some(matches) || ensureInstalledPluginRecordIds().has(normalizedChannelId);
	};
	const collectActiveWebSearchProviderIds = () => {
		const { registry } = ensureRegistry();
		return [...new Set(registry.plugins.flatMap((record) => record.contracts?.webSearchProviders ?? []).map((providerId) => providerId.trim()).filter((providerId) => providerId.length > 0))].toSorted((left, right) => left.localeCompare(right));
	};
	const collectKnownWebSearchProviderIds = () => {
		return [.../* @__PURE__ */ new Set([...collectActiveWebSearchProviderIds(), ...resolveWebSearchInstallCatalogEntries().map((entry) => entry.provider.id.trim()).filter((providerId) => providerId.length > 0)])].toSorted((left, right) => left.localeCompare(right));
	};
	const hasPluginEvidenceForWebSearchProvider = (...pluginOrProviderIds) => {
		const candidateIds = new Set(pluginOrProviderIds.map(normalizePluginId).filter((id) => id.length > 0));
		if (candidateIds.size === 0) return false;
		const matches = (pluginId) => candidateIds.has(normalizePluginId(pluginId));
		const pluginConfig = config.plugins;
		if (Array.isArray(pluginConfig?.allow) && pluginConfig.allow.some(matches) || isRecord(pluginConfig?.entries) && Object.keys(pluginConfig.entries).some(matches) || isRecord(pluginConfig?.installs) && Object.keys(pluginConfig.installs).some(matches)) return true;
		return [...candidateIds].some((pluginId) => ensureInstalledPluginRecordIds().has(pluginId));
	};
	const validateWebSearchProvider = () => {
		const provider = config.tools?.web?.search?.provider;
		if (typeof provider !== "string") return;
		const trimmed = provider.trim();
		const issuePath = "tools.web.search.provider";
		if (!trimmed) {
			issues.push({
				path: issuePath,
				message: "web_search provider must not be empty"
			});
			return;
		}
		if (collectActiveWebSearchProviderIds().includes(trimmed)) return;
		const installCatalogEntry = resolveWebSearchInstallCatalogEntries().find((entry) => entry.provider.id === trimmed);
		if (installCatalogEntry) {
			const issue = {
				path: issuePath,
				message: `web_search provider is not available: ${trimmed} (install or enable plugin "${installCatalogEntry.pluginId}", then run openclaw doctor --fix)`,
				allowedValues: collectKnownWebSearchProviderIds()
			};
			if (hasPluginEvidenceForWebSearchProvider(trimmed, installCatalogEntry.pluginId)) warnings.push({
				...issue,
				message: `web_search provider is not available: ${trimmed} (configured plugin "${installCatalogEntry.pluginId}" is unavailable; Gateway will ignore this optional provider until the plugin is installed/enabled or openclaw doctor --fix repairs the config)`
			});
			else issues.push(issue);
			return;
		}
		const allowedValues = collectKnownWebSearchProviderIds();
		if (allowedValues.length === 0) return;
		const issue = {
			path: issuePath,
			message: `unknown web_search provider: ${trimmed}`,
			allowedValues
		};
		const normalizedProviderId = normalizePluginId(trimmed);
		if (Boolean(normalizedProviderId && !ensureKnownIds().has(normalizedProviderId) && hasPluginEvidenceForWebSearchProvider(trimmed))) warnings.push({
			...issue,
			message: `${issue.message} (stale web search plugin config ignored; run openclaw doctor --fix to remove stale config, or install the plugin)`
		});
		else issues.push(issue);
	};
	const validateConfiguredModelRefs = () => {
		const configuredRefs = collectConfiguredModelRefs(config);
		if (configuredRefs.length === 0) return;
		const { registry } = ensureRegistry();
		const suppressedModels = /* @__PURE__ */ new Map();
		for (const suppression of planManifestModelCatalogSuppressions({ registry }).suppressions) {
			const key = `${suppression.provider}/${suppression.model}`;
			if (!suppression.when && !suppressedModels.has(key)) suppressedModels.set(key, {
				provider: suppression.provider,
				model: suppression.model,
				...suppression.reason ? { reason: suppression.reason } : {}
			});
		}
		const seen = /* @__PURE__ */ new Set();
		for (const ref of configuredRefs) {
			const slashIndex = ref.value.indexOf("/");
			if (slashIndex <= 0 || slashIndex >= ref.value.length - 1) continue;
			const provider = normalizeLowercaseStringOrEmpty(ref.value.slice(0, slashIndex));
			const model = normalizeLowercaseStringOrEmpty(ref.value.slice(slashIndex + 1));
			if (!provider || !model) continue;
			const suppression = suppressedModels.get(`${provider}/${model}`);
			const issueKey = `${ref.path}\0${provider}/${model}`;
			if (!suppression || seen.has(issueKey)) continue;
			seen.add(issueKey);
			const modelRef = `${suppression.provider}/${suppression.model}`;
			issues.push({
				path: ref.path,
				message: suppression.reason ? `Unknown model: ${modelRef}. ${suppression.reason}` : `Unknown model: ${modelRef}.`
			});
		}
	};
	const replaceChannelConfig = (channelId, nextValue) => {
		if (!channelsCloned) {
			mutatedConfig = {
				...mutatedConfig,
				channels: { ...mutatedConfig.channels }
			};
			channelsCloned = true;
		}
		mutatedConfig.channels[channelId] = nextValue;
	};
	const replacePluginEntryConfig = (pluginId, nextValue) => {
		if (!pluginsCloned) {
			mutatedConfig = {
				...mutatedConfig,
				plugins: { ...mutatedConfig.plugins }
			};
			pluginsCloned = true;
		}
		if (!pluginEntriesCloned) {
			mutatedConfig.plugins = {
				...mutatedConfig.plugins,
				entries: { ...mutatedConfig.plugins?.entries }
			};
			pluginEntriesCloned = true;
		}
		const currentEntry = mutatedConfig.plugins?.entries?.[pluginId];
		mutatedConfig.plugins.entries[pluginId] = {
			...currentEntry,
			config: nextValue
		};
	};
	const allowedChannels = /* @__PURE__ */ new Set([
		"defaults",
		"modelByChannel",
		...bundledChannelIds
	]);
	if (config.channels && isRecord(config.channels)) for (const key of Object.keys(config.channels)) {
		const trimmed = key.trim();
		if (!trimmed) continue;
		if (!allowedChannels.has(trimmed)) for (const record of ensureRegistry().registry.plugins) for (const channelId of record.channels) allowedChannels.add(channelId);
		if (!allowedChannels.has(trimmed)) {
			const issue = {
				path: `channels.${trimmed}`,
				message: `unknown channel id: ${trimmed}`
			};
			if (hasStalePluginEvidenceForUnknownChannel(trimmed)) warnings.push({
				...issue,
				message: `${issue.message} (stale channel plugin config ignored; run openclaw doctor --fix to remove stale config, or install the plugin)`
			});
			else issues.push(issue);
			continue;
		}
		const channelSchema = ensureChannelSchemas().get(trimmed);
		if (!channelSchema?.schema) continue;
		const result = validatePluginSchemaValue({
			origin: channelSchema.origin,
			schema: channelSchema.schema,
			cacheKey: `channel:${trimmed}`,
			value: config.channels[trimmed],
			applyDefaults: true
		});
		if (!result.ok) for (const error of result.errors) issues.push({
			path: error.path === "<root>" ? `channels.${trimmed}` : `channels.${trimmed}.${error.path}`,
			message: formatChannelConfigIssueMessage(error.message, channelSchema.pluginId),
			allowedValues: error.allowedValues,
			allowedValuesHiddenCount: error.allowedValuesHiddenCount
		});
		else replaceChannelConfig(trimmed, result.value);
	}
	const heartbeatChannelIds = new Set(bundledChannelIds.map((channelId) => normalizeLowercaseStringOrEmpty(channelId)));
	const validateHeartbeatTarget = (target, issuePath) => {
		if (typeof target !== "string") return;
		const trimmed = target.trim();
		if (!trimmed) {
			issues.push({
				path: issuePath,
				message: "heartbeat target must not be empty"
			});
			return;
		}
		const normalized = normalizeLowercaseStringOrEmpty(trimmed);
		if (normalized === "owner" || normalized === "last" || normalized === "none" || normalizeBundledChannelId(trimmed)) return;
		if (!heartbeatChannelIds.has(normalized)) for (const record of ensureRegistry().registry.plugins) for (const channelId of record.channels) {
			const pluginChannel = channelId.trim();
			if (pluginChannel) heartbeatChannelIds.add(normalizeLowercaseStringOrEmpty(pluginChannel));
		}
		if (!heartbeatChannelIds.has(normalized)) issues.push({
			path: issuePath,
			message: `unknown heartbeat target: ${target}`
		});
	};
	validateHeartbeatTarget(config.agents?.defaults?.heartbeat?.target, "agents.defaults.heartbeat.target");
	for (const { entry, source } of listAgentEntriesWithSource(config)) {
		const pathPrefix = source.kind === "entries" ? `agents.entries.${source.key}` : `agents.list.${source.index}`;
		validateHeartbeatTarget(entry?.heartbeat?.target, `${pathPrefix}.heartbeat.target`);
	}
	validateWebSearchProvider();
	validateConfiguredModelRefs();
	if (hasExplicitPluginsConfig) {
		const { registry } = ensureRegistry();
		validateExplicitPluginConfig({
			raw,
			config,
			env: opts.env,
			applyDefaults: opts.applyDefaults,
			registry,
			knownIds: ensureKnownIds(),
			normalizedPlugins: ensureNormalizedPlugins(),
			ensureCompatPluginIds,
			ensureOverriddenPluginIds,
			replacePluginEntryConfig,
			issues,
			warnings
		});
	}
	if (opts.semanticValidation === "strict" && Object.keys(mutatedConfig.secrets?.providers ?? {}).length > 0) issues.push(...collectSecretRefProviderSourceIssues({
		config: mutatedConfig,
		env: opts.env,
		manifestRegistry: ensureLoadedRegistryInfo().registry
	}));
	return issues.length > 0 ? {
		ok: false,
		issues,
		warnings
	} : {
		ok: true,
		config: mutatedConfig,
		warnings
	};
}
//#endregion
//#region src/config/json5-comments.ts
function hasJSON5Comments(raw) {
	let quote;
	for (let index = 0; index < raw.length; index += 1) {
		const char = raw[index];
		if (quote) {
			if (char === "\\") index += 1;
			else if (char === quote) quote = void 0;
			continue;
		}
		if (char === "\"" || char === "'") {
			quote = char;
			continue;
		}
		if (char === "/" && (raw[index + 1] === "/" || raw[index + 1] === "*")) return true;
	}
	return false;
}
function warnIfJSON5CommentsWillBeStripped(params) {
	if (params.skipOutputLogs || typeof params.raw !== "string" || !hasJSON5Comments(params.raw)) return;
	(params.warn ?? console.warn)(`Config write will strip JSON5 comments from ${params.filePath}.`);
}
//#endregion
//#region src/config/io.warnings.ts
function warnOnConfigMiskeys(raw, logger) {
	if (!raw || typeof raw !== "object") return;
	const gateway = raw.gateway;
	if (!gateway || typeof gateway !== "object") return;
	if ("token" in gateway) logger.warn("Config uses \"gateway.token\". This key is ignored; use \"gateway.auth.token\" instead.");
}
function logConfigWarningsOnce(params) {
	if (params.warnings.length === 0) {
		loggedConfigWarningFingerprints.delete(params.configPath);
		return;
	}
	const details = params.warnings.map((warning) => `${sanitizeTerminalText(warning.path || "<root>")}: ${sanitizeTerminalText(warning.message)}`).join("; ");
	const fingerprint = hashConfigRaw(details);
	if (loggedConfigWarningFingerprints.get(params.configPath) === fingerprint) {
		setBoundedConfigIoWarningEntry(loggedConfigWarningFingerprints, params.configPath, fingerprint);
		return;
	}
	setBoundedConfigIoWarningEntry(loggedConfigWarningFingerprints, params.configPath, fingerprint);
	params.logger.warn(`Config warnings: ${details}`);
}
function warnIfConfigFromFuture(cfg, logger) {
	const touched = cfg.meta?.lastTouchedVersion;
	if (!touched || !shouldWarnOnTouchedVersion(VERSION, touched)) return;
	if (warnedFutureTouchedVersions.check(touched)) return;
	logger.warn([
		`Your OpenClaw config was written by version ${touched}, but this command is running ${VERSION}.`,
		"Check: `openclaw --version`, `which openclaw`, and `openclaw gateway status --deep`.",
		"If unexpected, update PATH so `openclaw` points to the version you want, or reinstall the Gateway service from that same OpenClaw install."
	].join("\n"));
}
//#endregion
//#region src/config/config-path-mutation.ts
const MANAGED_CONFIG_UNSET_PATHS = [["plugins", "installs"]];
const WRITE_PRUNED_OBJECT = Symbol("write-pruned-object");
function unsetPathForWriteAt(value, pathSegments, depth) {
	if (depth >= pathSegments.length) return {
		changed: false,
		value
	};
	const segment = expectDefined(pathSegments[depth], "path segments entry at depth");
	const isLeaf = depth === pathSegments.length - 1;
	if (Array.isArray(value)) {
		const index = parseConfigPathArrayIndex(segment);
		if (index === void 0 || index >= value.length) return {
			changed: false,
			value
		};
		if (isLeaf) {
			const next = value.slice();
			next.splice(index, 1);
			return {
				changed: true,
				value: next
			};
		}
		const child = unsetPathForWriteAt(value[index], pathSegments, depth + 1);
		if (!child.changed) return {
			changed: false,
			value
		};
		const next = value.slice();
		if (child.value === WRITE_PRUNED_OBJECT) next.splice(index, 1);
		else next[index] = child.value;
		return {
			changed: true,
			value: next
		};
	}
	if (isBlockedObjectKey(segment) || !isRecord(value) || !Object.hasOwn(value, segment)) return {
		changed: false,
		value
	};
	if (isLeaf) {
		const next = { ...value };
		delete next[segment];
		return {
			changed: true,
			value: Object.keys(next).length === 0 ? WRITE_PRUNED_OBJECT : next
		};
	}
	const child = unsetPathForWriteAt(value[segment], pathSegments, depth + 1);
	if (!child.changed) return {
		changed: false,
		value
	};
	const next = { ...value };
	if (child.value === WRITE_PRUNED_OBJECT) delete next[segment];
	else next[segment] = child.value;
	return {
		changed: true,
		value: Object.keys(next).length === 0 ? WRITE_PRUNED_OBJECT : next
	};
}
function unsetPathForWrite(root, pathSegments) {
	if (pathSegments.length === 0) return {
		changed: false,
		next: root
	};
	const result = unsetPathForWriteAt(root, pathSegments, 0);
	if (!result.changed) return {
		changed: false,
		next: root
	};
	if (result.value === WRITE_PRUNED_OBJECT) return {
		changed: true,
		next: {}
	};
	if (isRecord(result.value)) return {
		changed: true,
		next: result.value
	};
	return {
		changed: false,
		next: root
	};
}
function applyUnsetPathsForWrite(root, unsetPaths) {
	let next = root;
	for (const unsetPath of unsetPaths ?? []) {
		if (!Array.isArray(unsetPath) || unsetPath.length === 0) continue;
		const unsetResult = unsetPathForWrite(next, unsetPath);
		if (unsetResult.changed) next = unsetResult.next;
	}
	return next;
}
function resolveManagedUnsetPathsForWrite(unsetPaths) {
	const next = [];
	for (const managedPath of MANAGED_CONFIG_UNSET_PATHS) next.push(Array.from(managedPath));
	for (const unsetPath of unsetPaths ?? []) {
		if (!Array.isArray(unsetPath) || unsetPath.length === 0) continue;
		if (next.some((existing) => isDeepStrictEqual(existing, unsetPath))) continue;
		next.push([...unsetPath]);
	}
	return next;
}
//#endregion
//#region src/config/env-preserve.ts
/**
* Preserves `${VAR}` environment variable references during config write-back.
*
* When config is read, `${VAR}` references are resolved to their values.
* When writing back, callers pass the resolved config. This module detects
* values that match what a `${VAR}` reference would resolve to and restores
* the original reference, so env var references survive config round-trips.
*
* A value is restored only if:
* 1. The pre-substitution value contained a `${VAR}` pattern
* 2. Resolving that pattern with current env vars produces the incoming value
*
* If a caller intentionally set a new value (different from what the env var
* resolves to), the new value is kept as-is.
*/
const ENV_VAR_PATTERN = /\$\{[A-Z_][A-Z0-9_]*\}/;
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
var EnvRefArrayMutationError = class extends Error {
	constructor() {
		super("Config write would reorder or modify an array containing environment references.");
		this.name = "EnvRefArrayMutationError";
	}
};
/**
* Check if a string contains any `${VAR}` env var references.
*/
function hasEnvVarRef(value) {
	return ENV_VAR_PATTERN.test(value);
}
function collectAuthoredEnvRefs(value) {
	const refs = [];
	for (let index = 0; index < value.length; index += 1) {
		if (value[index] !== "$") continue;
		const isEscaped = value[index + 1] === "$" && value[index + 2] === "{";
		const nameStart = index + (isEscaped ? 3 : 2);
		if (!isEscaped && value[index + 1] !== "{") continue;
		const nameEnd = value.indexOf("}", nameStart);
		if (nameEnd === -1 || !ENV_VAR_NAME_PATTERN.test(value.slice(nameStart, nameEnd))) continue;
		refs.push({
			kind: isEscaped ? "escaped" : "unescaped",
			name: value.slice(nameStart, nameEnd)
		});
		index = nameEnd;
	}
	return refs;
}
function hasUnescapedEnvVarRef(value) {
	return collectAuthoredEnvRefs(value).some((ref) => ref.kind === "unescaped");
}
function hasEscapedEnvVarRef(value) {
	return collectAuthoredEnvRefs(value).some((ref) => ref.kind === "escaped");
}
function containsAuthoredUnescapedEnvTemplate(value) {
	if (typeof value === "string") return hasUnescapedEnvVarRef(value);
	if (Array.isArray(value)) return value.some((item) => containsAuthoredUnescapedEnvTemplate(item));
	if (isPlainObject(value)) return Object.values(value).some((item) => containsAuthoredUnescapedEnvTemplate(item));
	return false;
}
function containsAuthoredEscapedEnvTemplate(value) {
	if (typeof value === "string") return hasEscapedEnvVarRef(value);
	if (Array.isArray(value)) return value.some((item) => containsAuthoredEscapedEnvTemplate(item));
	if (isPlainObject(value)) return Object.values(value).some((item) => containsAuthoredEscapedEnvTemplate(item));
	return false;
}
function countAuthoredEnvRefsByPath(value, kind) {
	const countsByName = /* @__PURE__ */ new Map();
	const visit = (item, path) => {
		if (typeof item === "string") {
			for (const ref of collectAuthoredEnvRefs(item)) if (ref.kind === kind) {
				const pathCounts = countsByName.get(ref.name) ?? /* @__PURE__ */ new Map();
				const pathKey = JSON.stringify(path);
				pathCounts.set(pathKey, (pathCounts.get(pathKey) ?? 0) + 1);
				countsByName.set(ref.name, pathCounts);
			}
			return;
		}
		if (Array.isArray(item)) {
			item.forEach((child, index) => visit(child, [...path, String(index)]));
			return;
		}
		if (isPlainObject(item)) Object.entries(item).forEach(([key, child]) => visit(child, [...path, key]));
	};
	visit(value, []);
	return countsByName;
}
function countResolvedActiveEnvRefsByPath(incoming, parsed, env) {
	const countsByName = /* @__PURE__ */ new Map();
	const visit = (incomingItem, parsedItem, path) => {
		if (typeof incomingItem === "string" && typeof parsedItem === "string") {
			if (!isDeepStrictEqual(incomingItem, tryResolveString(parsedItem, env))) return;
			for (const ref of collectAuthoredEnvRefs(parsedItem)) if (ref.kind === "unescaped") {
				const pathCounts = countsByName.get(ref.name) ?? /* @__PURE__ */ new Map();
				const pathKey = JSON.stringify(path);
				pathCounts.set(pathKey, (pathCounts.get(pathKey) ?? 0) + 1);
				countsByName.set(ref.name, pathCounts);
			}
			return;
		}
		if (Array.isArray(incomingItem) && Array.isArray(parsedItem)) {
			parsedItem.forEach((child, index) => visit(incomingItem[index], child, [...path, String(index)]));
			return;
		}
		if (isPlainObject(incomingItem) && isPlainObject(parsedItem)) Object.entries(parsedItem).forEach(([key, child]) => visit(incomingItem[key], child, [...path, key]));
	};
	visit(incoming, parsed, []);
	return countsByName;
}
function containsUnaccountedActiveEscapedEnvRef(incoming, escapedParsed, matchedIncoming, matchedParsed, env) {
	const escapedCounts = countAuthoredEnvRefsByPath(escapedParsed, "escaped");
	const incomingActiveCounts = countAuthoredEnvRefsByPath(incoming, "unescaped");
	const incomingEscapedCounts = countAuthoredEnvRefsByPath(incoming, "escaped");
	const matchedActiveCounts = countResolvedActiveEnvRefsByPath(matchedIncoming, matchedParsed, env);
	const matchedEscapedCounts = countAuthoredEnvRefsByPath(matchedParsed, "escaped");
	return [...escapedCounts].some(([name, escapedPathCounts]) => [...incomingActiveCounts.get(name) ?? /* @__PURE__ */ new Map()].some(([path, count]) => count > (matchedActiveCounts.get(name)?.get(path) ?? 0)) || [...escapedPathCounts.keys()].some((path) => {
		return (incomingActiveCounts.get(name)?.get(path) ?? 0) > 0 && (incomingEscapedCounts.get(name)?.get(path) ?? 0) < (matchedEscapedCounts.get(name)?.get(path) ?? 0);
	}));
}
function preservesAuthoredEscapedEnvRefs(incoming, parsed) {
	const parsedEscapedCounts = countAuthoredEnvRefsByPath(parsed, "escaped");
	const incomingEscapedCounts = countAuthoredEnvRefsByPath(incoming, "escaped");
	return [...parsedEscapedCounts].every(([name, parsedPathCounts]) => [...parsedPathCounts].every(([path, count]) => (incomingEscapedCounts.get(name)?.get(path) ?? 0) >= count));
}
function getArrayIdentityPathValue(value, path) {
	let current = value;
	for (const segment of path) {
		if (!isPlainObject(current)) return;
		current = current[segment];
	}
	return current;
}
function collectStableArrayIdentityPaths(value) {
	if (!isPlainObject(value)) return [];
	for (const key of ["id", "agentId"]) {
		const child = value[key];
		if (typeof child === "string" && !hasEnvVarRef(child)) return [[key]];
	}
	return [];
}
function resolveStableArrayIdentityMatch(params) {
	const parsedItem = params.parsed[params.parsedIndex];
	const identityPaths = collectStableArrayIdentityPaths(parsedItem);
	if (identityPaths.length === 0) return { kind: "none" };
	let incomingIndex;
	let hasUniqueAuthoredIdentity = false;
	for (const identityPath of identityPaths) {
		const identityValue = getArrayIdentityPathValue(parsedItem, identityPath);
		if (params.parsed.filter((item) => isDeepStrictEqual(getArrayIdentityPathValue(item, identityPath), identityValue)).length !== 1) continue;
		hasUniqueAuthoredIdentity = true;
		const incomingMatches = params.incoming.flatMap((item, index) => isDeepStrictEqual(getArrayIdentityPathValue(item, identityPath), identityValue) ? [index] : []);
		if (incomingMatches.length !== 1 || incomingIndex !== void 0 && incomingIndex !== incomingMatches[0]) return { kind: "invalid" };
		incomingIndex = incomingMatches[0];
	}
	if (incomingIndex !== void 0) return {
		kind: "match",
		incomingIndex
	};
	return hasUniqueAuthoredIdentity ? { kind: "invalid" } : { kind: "none" };
}
function collectLiteralArrayIdentityPaths(value, path = []) {
	if (typeof value === "string") return hasEnvVarRef(value) ? [] : [path];
	if (!isPlainObject(value)) return [];
	return Object.entries(value).flatMap(([key, child]) => collectLiteralArrayIdentityPaths(child, [...path, key]));
}
function hasStableSameIndexLiteralShape(params) {
	if (params.incoming.length !== params.parsed.length) return false;
	const parsedItem = params.parsed[params.parsedIndex];
	const literalPaths = collectLiteralArrayIdentityPaths(parsedItem);
	if (literalPaths.length === 0 || literalPaths.some((identityPath) => {
		const identityValue = getArrayIdentityPathValue(parsedItem, identityPath);
		return !isDeepStrictEqual(getArrayIdentityPathValue(params.incoming[params.parsedIndex], identityPath), identityValue);
	})) return false;
	return literalPaths.some((identityPath) => {
		const identityValue = getArrayIdentityPathValue(parsedItem, identityPath);
		const authoredCount = params.parsed.filter((item) => isDeepStrictEqual(getArrayIdentityPathValue(item, identityPath), identityValue)).length;
		const incomingCount = params.incoming.filter((item) => isDeepStrictEqual(getArrayIdentityPathValue(item, identityPath), identityValue)).length;
		return authoredCount === 1 && incomingCount === 1;
	});
}
function matchesArrayElementAtSameIndex(incoming, parsed, env) {
	return isDeepStrictEqual(incoming, parsed) || isDeepStrictEqual(incoming, resolveEnvVarRefsForComparison(parsed, env));
}
function matchesRetainedArrayItem(params) {
	if (matchesArrayElementAtSameIndex(params.incoming[params.incomingIndex], params.parsed[params.parsedIndex], params.env)) return true;
	const stableIdentity = resolveStableArrayIdentityMatch({
		incoming: params.incoming,
		parsed: params.parsed,
		parsedIndex: params.parsedIndex
	});
	return stableIdentity.kind === "match" && stableIdentity.incomingIndex === params.incomingIndex;
}
function hasStableSameIndexNeighbors(params) {
	return params.incoming.length === params.parsed.length && params.parsed.every((item, index) => index === params.parsedIndex || matchesArrayElementAtSameIndex(params.incoming[index], item, params.env));
}
function matchUniqueRetainedArrayItems(params) {
	if (params.incoming.length >= params.parsed.length) return;
	const earliestParsedIndexes = [];
	let nextParsedIndex = 0;
	for (let incomingIndex = 0; incomingIndex < params.incoming.length; incomingIndex += 1) {
		const parsedIndex = params.parsed.findIndex((_parsedItem, index) => index >= nextParsedIndex && matchesRetainedArrayItem({
			...params,
			incomingIndex,
			parsedIndex: index
		}));
		if (parsedIndex < 0) return;
		earliestParsedIndexes.push(parsedIndex);
		nextParsedIndex = parsedIndex + 1;
	}
	const latestParsedIndexes = Array.from({ length: params.incoming.length }, () => 0);
	nextParsedIndex = params.parsed.length - 1;
	for (let incomingIndex = params.incoming.length - 1; incomingIndex >= 0; incomingIndex -= 1) {
		let parsedIndex = nextParsedIndex;
		while (parsedIndex >= 0 && !matchesRetainedArrayItem({
			...params,
			incomingIndex,
			parsedIndex
		})) parsedIndex -= 1;
		if (parsedIndex < 0) return;
		latestParsedIndexes[incomingIndex] = parsedIndex;
		nextParsedIndex = parsedIndex - 1;
	}
	if (!isDeepStrictEqual(earliestParsedIndexes, latestParsedIndexes)) return;
	return new Map(earliestParsedIndexes.map((parsedIndex, incomingIndex) => [parsedIndex, incomingIndex]));
}
function matchAuthoredTemplateArrayItems(params) {
	const templateIndexes = params.parsed.flatMap((item, index) => containsAuthoredUnescapedEnvTemplate(item) ? [index] : []);
	if (params.incoming.length === params.parsed.length && params.incoming.every((item, index) => matchesArrayElementAtSameIndex(item, params.parsed[index], params.env))) return new Map(templateIndexes.map((index) => [index, index]));
	const retainedDeletionMatches = matchUniqueRetainedArrayItems(params);
	if (retainedDeletionMatches) return new Map(templateIndexes.flatMap((parsedIndex) => {
		const incomingIndex = retainedDeletionMatches.get(parsedIndex);
		return incomingIndex === void 0 ? [] : [[parsedIndex, incomingIndex]];
	}));
	const matches = /* @__PURE__ */ new Map();
	const usedIncomingIndexes = /* @__PURE__ */ new Set();
	const addMatch = (parsedIndex, incomingIndex) => {
		if (usedIncomingIndexes.has(incomingIndex)) throw new EnvRefArrayMutationError();
		matches.set(parsedIndex, incomingIndex);
		usedIncomingIndexes.add(incomingIndex);
	};
	for (const parsedIndex of templateIndexes) {
		const parsedItem = params.parsed[parsedIndex];
		const stableIdentity = resolveStableArrayIdentityMatch({
			incoming: params.incoming,
			parsed: params.parsed,
			parsedIndex
		});
		if (stableIdentity.kind !== "none") {
			if (stableIdentity.kind === "invalid") throw new EnvRefArrayMutationError();
			addMatch(parsedIndex, stableIdentity.incomingIndex);
			continue;
		}
		if (parsedIndex < params.incoming.length && matchesArrayElementAtSameIndex(params.incoming[parsedIndex], parsedItem, params.env)) {
			const precedingItemsRemainAligned = params.parsed.slice(0, parsedIndex).every((item, index) => matchesArrayElementAtSameIndex(params.incoming[index], item, params.env));
			const duplicateAuthoredMatch = params.parsed.some((item, index) => index !== parsedIndex && matchesArrayElementAtSameIndex(params.incoming[parsedIndex], item, params.env));
			const duplicateIncomingMatch = params.incoming.some((item, index) => index !== parsedIndex && matchesArrayElementAtSameIndex(item, parsedItem, params.env));
			if (!(params.incoming.length === params.parsed.length || precedingItemsRemainAligned) || duplicateAuthoredMatch || duplicateIncomingMatch) throw new EnvRefArrayMutationError();
			addMatch(parsedIndex, parsedIndex);
			continue;
		}
		if (isPlainObject(parsedItem) || Array.isArray(parsedItem)) {
			const isSinglePositionEdit = params.incoming.length === 1 && params.parsed.length === 1;
			const hasSameIndexLiteralIdentity = hasStableSameIndexLiteralShape({
				incoming: params.incoming,
				parsed: params.parsed,
				parsedIndex
			});
			const hasSameIndexNeighbors = hasStableSameIndexNeighbors({
				incoming: params.incoming,
				parsed: params.parsed,
				parsedIndex,
				env: params.env
			});
			if (!isSinglePositionEdit && !hasSameIndexLiteralIdentity && !hasSameIndexNeighbors) throw new EnvRefArrayMutationError();
			addMatch(parsedIndex, parsedIndex);
			continue;
		}
		if (params.incoming.some((item, incomingIndex) => incomingIndex !== parsedIndex && matchesArrayElementAtSameIndex(item, parsedItem, params.env))) throw new EnvRefArrayMutationError();
		if (parsedIndex < params.incoming.length) addMatch(parsedIndex, parsedIndex);
	}
	return matches;
}
function matchAuthoredEscapedTemplateArrayItems(params) {
	const escapedTemplateIndexes = params.parsed.flatMap((item, index) => containsAuthoredEscapedEnvTemplate(item) && !containsAuthoredUnescapedEnvTemplate(item) ? [index] : []);
	if (params.incoming.length === params.parsed.length && params.incoming.every((item, index) => matchesArrayElementAtSameIndex(item, params.parsed[index], params.env))) return new Map(escapedTemplateIndexes.map((index) => [index, index]));
	const retainedDeletionMatches = matchUniqueRetainedArrayItems(params);
	if (retainedDeletionMatches) return new Map(escapedTemplateIndexes.flatMap((parsedIndex) => {
		const incomingIndex = retainedDeletionMatches.get(parsedIndex);
		if (incomingIndex === void 0) return [];
		if (params.usedIncomingIndexes.has(incomingIndex)) throw new EnvRefArrayMutationError();
		return [[parsedIndex, incomingIndex]];
	}));
	const matches = /* @__PURE__ */ new Map();
	const usedIncomingIndexes = new Set(params.usedIncomingIndexes);
	const addMatch = (parsedIndex, incomingIndex) => {
		if (usedIncomingIndexes.has(incomingIndex)) throw new EnvRefArrayMutationError();
		matches.set(parsedIndex, incomingIndex);
		usedIncomingIndexes.add(incomingIndex);
	};
	for (const parsedIndex of escapedTemplateIndexes) {
		const parsedItem = params.parsed[parsedIndex];
		const stableIdentity = resolveStableArrayIdentityMatch({
			incoming: params.incoming,
			parsed: params.parsed,
			parsedIndex
		});
		if (stableIdentity.kind !== "none") {
			if (stableIdentity.kind === "match") {
				addMatch(parsedIndex, stableIdentity.incomingIndex);
				continue;
			}
		}
		const resolvedItem = resolveEnvVarRefsForComparison(parsedItem, params.env);
		const incomingMatches = params.incoming.flatMap((item, incomingIndex) => !usedIncomingIndexes.has(incomingIndex) && isDeepStrictEqual(item, resolvedItem) ? [incomingIndex] : []);
		const authoredMatches = escapedTemplateIndexes.filter((index) => isDeepStrictEqual(resolveEnvVarRefsForComparison(params.parsed[index], params.env), resolvedItem));
		const authoredRepresentationsAreIdentical = authoredMatches.every((index) => isDeepStrictEqual(params.parsed[index], parsedItem));
		if (incomingMatches.length > 0 && incomingMatches.length <= authoredMatches.length && authoredRepresentationsAreIdentical) {
			const sameIndexMatch = incomingMatches.includes(parsedIndex) ? parsedIndex : incomingMatches[0];
			addMatch(parsedIndex, expectDefined(sameIndexMatch, "env preserve same index match"));
			continue;
		}
		if (incomingMatches.length > 0) throw new EnvRefArrayMutationError();
		if (isPlainObject(parsedItem) || Array.isArray(parsedItem)) {
			const isSinglePositionEdit = params.incoming.length === 1 && params.parsed.length === 1;
			const hasSameIndexLiteralIdentity = hasStableSameIndexLiteralShape({
				incoming: params.incoming,
				parsed: params.parsed,
				parsedIndex
			});
			const hasSameIndexNeighbors = hasStableSameIndexNeighbors({
				incoming: params.incoming,
				parsed: params.parsed,
				parsedIndex,
				env: params.env
			});
			if (stableIdentity.kind === "none" && parsedIndex < params.incoming.length && !usedIncomingIndexes.has(parsedIndex) && (isSinglePositionEdit || hasSameIndexLiteralIdentity || hasSameIndexNeighbors)) {
				addMatch(parsedIndex, parsedIndex);
				continue;
			}
		}
	}
	return matches;
}
/**
* Resolve `${VAR}` references in a single string using the given env.
* Preserves missing references so matching remains aligned with config reads.
*
* Mirrors the substitution semantics of `substituteString` in env-substitution.ts:
* - `${VAR}` → env value (returns null if missing)
* - `$${VAR}` → literal `${VAR}` (escape sequence)
*/
function tryResolveString(template, env) {
	const chunks = [];
	for (let i = 0; i < template.length; i++) {
		if (template[i] === "$") {
			if (template[i + 1] === "$" && template[i + 2] === "{") {
				const start = i + 3;
				const end = template.indexOf("}", start);
				if (end !== -1) {
					const name = template.slice(start, end);
					if (ENV_VAR_NAME_PATTERN.test(name)) {
						chunks.push(`\${${name}}`);
						i = end;
						continue;
					}
				}
			}
			if (template[i + 1] === "{") {
				const start = i + 2;
				const end = template.indexOf("}", start);
				if (end !== -1) {
					const name = template.slice(start, end);
					if (ENV_VAR_NAME_PATTERN.test(name)) {
						const val = env[name];
						if (val === void 0 || val === "") {
							chunks.push(`\${${name}}`);
							i = end;
							continue;
						}
						chunks.push(val);
						i = end;
						continue;
					}
				}
			}
		}
		chunks.push(template.charAt(i));
	}
	return chunks.join("");
}
function resolveEnvVarRefsForComparison(value, env) {
	if (typeof value === "string") return hasEnvVarRef(value) ? tryResolveString(value, env) : value;
	if (Array.isArray(value)) return value.map((item) => resolveEnvVarRefsForComparison(item, env));
	if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, resolveEnvVarRefsForComparison(item, env)]));
	return value;
}
/**
* Deep-walk the incoming config and restore `${VAR}` references from the
* pre-substitution parsed config wherever the resolved value matches.
*
* @param incoming - The resolved config about to be written
* @param parsed - The pre-substitution parsed config (from the current file on disk)
* @param env - Environment variables for verification
* @returns A new config object with env var references restored where appropriate
*/
function restoreEnvVarRefs(incoming, parsed, env = process.env) {
	if (parsed === null || parsed === void 0) return incoming;
	if (typeof incoming === "string" && typeof parsed === "string") {
		if (hasEnvVarRef(parsed)) {
			if (tryResolveString(parsed, env) === incoming) return parsed;
		}
		return incoming;
	}
	if (Array.isArray(incoming) && Array.isArray(parsed)) {
		if (!containsAuthoredUnescapedEnvTemplate(parsed) && !containsAuthoredEscapedEnvTemplate(parsed)) return incoming.map((item, index) => index < parsed.length ? restoreEnvVarRefs(item, parsed[index], env) : item);
		const unescapedMatches = matchAuthoredTemplateArrayItems({
			incoming,
			parsed,
			env
		});
		const escapedMatches = matchAuthoredEscapedTemplateArrayItems({
			incoming,
			parsed,
			env,
			usedIncomingIndexes: new Set(unescapedMatches.values())
		});
		const matches = new Map([...unescapedMatches, ...escapedMatches]);
		const next = [...incoming];
		const matchedIncomingIndexes = new Set(matches.values());
		for (const [parsedIndex, incomingIndex] of matches) next[incomingIndex] = restoreEnvVarRefs(incoming[incomingIndex], parsed[parsedIndex], env);
		for (let index = 0; index < incoming.length && index < parsed.length; index += 1) if (!matchedIncomingIndexes.has(index) && !containsAuthoredUnescapedEnvTemplate(parsed[index]) && !containsAuthoredEscapedEnvTemplate(parsed[index])) next[index] = restoreEnvVarRefs(incoming[index], parsed[index], env);
		const matchedParsedIndexByIncoming = new Map([...matches].map(([parsedIndex, incomingIndex]) => [incomingIndex, parsedIndex]));
		for (const [escapedParsedIndex, escapedParsedItem] of parsed.entries()) {
			if (!containsAuthoredEscapedEnvTemplate(escapedParsedItem)) continue;
			const matchedIncomingIndex = matches.get(escapedParsedIndex);
			if (matchedIncomingIndex !== void 0 && preservesAuthoredEscapedEnvRefs(next[matchedIncomingIndex], escapedParsedItem)) continue;
			if (next.some((item, incomingIndex) => {
				const matchedParsedIndex = matchedParsedIndexByIncoming.get(incomingIndex);
				return containsUnaccountedActiveEscapedEnvRef(item, escapedParsedItem, incoming[incomingIndex], matchedParsedIndex === void 0 ? void 0 : parsed[matchedParsedIndex], env);
			})) throw new EnvRefArrayMutationError();
		}
		return next;
	}
	if (isPlainObject(incoming) && isPlainObject(parsed)) {
		const result = {};
		for (const [key, value] of Object.entries(incoming)) if (Object.hasOwn(parsed, key)) result[key] = restoreEnvVarRefs(value, parsed[key], env);
		else result[key] = value;
		return result;
	}
	return incoming;
}
function parentPath(value) {
	if (!value) return "";
	if (value.endsWith("]")) {
		const index = value.lastIndexOf("[");
		return index > 0 ? value.slice(0, index) : "";
	}
	const index = value.lastIndexOf(".");
	return index >= 0 ? value.slice(0, index) : "";
}
function isPathChanged(path, changedPaths) {
	if (changedPaths.has(path)) return true;
	let current = parentPath(path);
	while (current) {
		if (changedPaths.has(current)) return true;
		current = parentPath(current);
	}
	return changedPaths.has("");
}
function restoreEnvRefsFromMap(value, path, envRefMap, changedPaths, identityRestoredPaths = /* @__PURE__ */ new Set()) {
	if (typeof value === "string") {
		if (identityRestoredPaths.has(path)) return value;
		if (!isPathChanged(path, changedPaths)) {
			const original = envRefMap.get(path);
			if (original !== void 0) return original;
		}
		return value;
	}
	if (Array.isArray(value)) {
		let changed = false;
		const next = value.map((item, index) => {
			const updated = restoreEnvRefsFromMap(item, `${path}[${index}]`, envRefMap, changedPaths, identityRestoredPaths);
			if (updated !== item) changed = true;
			return updated;
		});
		return changed ? next : value;
	}
	if (isRecord(value)) {
		let changed = false;
		const next = {};
		for (const [key, child] of Object.entries(value)) {
			const updated = restoreEnvRefsFromMap(child, path ? `${path}.${key}` : key, envRefMap, changedPaths, identityRestoredPaths);
			if (updated !== child) changed = true;
			next[key] = updated;
		}
		return changed ? next : value;
	}
	return value;
}
function resolveWriteEnvSnapshotForPath(params) {
	if (params.expectedConfigPath === void 0 || params.expectedConfigPath === params.actualConfigPath) return params.envSnapshotForRestore;
}
//#endregion
//#region src/config/io.types.ts
const configWritePostCommitRollback = Symbol("configWritePostCommitRollback");
var ConfigRuntimeRefreshError = class extends Error {
	constructor(message, options) {
		super(message, options);
		this.name = "ConfigRuntimeRefreshError";
	}
};
//#endregion
export { resolveChannelSchemaSelection as A, resolveConfigWriteSuspiciousReasons as B, materializeRuntimeConfig as C, normalizeProviderConfigForConfigDefaults as D, applyProviderConfigDefaultsForConfig as E, assertBaseSnapshotStillCurrent as F, recordConfigWriteMetadata as G, stampConfigVersion as H, formatConfigArtifactTimestamp as I, loggedConfigWarningFingerprints as J, stampConfigWriteMetadata as K, resolveConfigSizeBaselineBytes as L, collectPluginSchemaMetadataCore as M, attachAgentListProjection as N, resolveAgentMaxConcurrent as O, migrateLegacyContextBudgetConfig as P, findDuplicateAgentDirs as Q, resolveConfigStatMetadata as R, asRuntimeConfig as S, resolveNormalizedProviderModelMaxTokens as T, tightenStateDirPermissionsIfNeeded as U, rollbackConfigFileWriteIfUnchanged as V, AUTO_MANAGED_CONFIG_META_PATHS as W, setBoundedConfigIoWarningEntry as X, loggedInvalidConfigs as Y, DuplicateAgentDirError as Z, validateConfigObjectRaw as _, restoreEnvRefsFromMap as a, isUnsafeGatewayTailscaleNoAuth as b, resolveManagedUnsetPathsForWrite as c, warnOnConfigMiskeys as d, warnIfJSON5CommentsWillBeStripped as f, validateConfigObject as g, validateConfigObjectWithPlugins as h, resolveWriteEnvSnapshotForPath as i, collectChannelSchemaMetadataCore as j, resolveSubagentMaxConcurrent as k, logConfigWarningsOnce as l, validateConfigObjectRawWithPlugins as m, configWritePostCommitRollback as n, restoreEnvVarRefs as o, materializeLegacyAgentOwnershipForActiveChannelsResult as p, autoOwnerDisplaySecretByPath as q, EnvRefArrayMutationError as r, applyUnsetPathsForWrite as s, ConfigRuntimeRefreshError as t, warnIfConfigFromFuture as u, collectUnsupportedSecretRefPolicyIssues as v, DEFAULT_MODEL_ALIASES as w, asResolvedSourceConfig as x, formatUnsafeGatewayTailscaleNoAuthMessage as y, resolveConfigWriteBlockingReasons as z };