import { i as __exportAll } from "./tool-display-DMBmPBI-.js";
import { E as resolveRequiredHomeDir, _ as resolveOAuthPath, a as resolveAgentConfigPath, b as DEFAULT_GATEWAY_PORT, c as resolveConfigPath, d as resolveGatewayLockDir, f as resolveGatewayPort, g as resolveOAuthDir, h as resolveNewStateDir, l as resolveConfigPathCandidate, m as resolveLegacyStateDirs, n as STATE_DIR, o as resolveAgentConfigPathWithFallback, p as resolveIsNixMode, r as isNixMode, s as resolveCanonicalConfigPath, t as CONFIG_PATH, u as resolveDefaultConfigCandidates, v as resolveStateDir } from "./paths-WcYpcMAY.js";
import { Q as isPlainObject$2, it as resolveConfigDir, st as resolveUserPath } from "./entry.js";
import { Bt as shouldEnableShellEnvFallback, Lt as loadShellEnvFallback, Rt as resolveShellEnvFallbackTimeoutMs, zt as shouldDeferShellEnvFallback } from "./auth-profiles-CaNK0cUT.js";
import { C as DuplicateAgentDirError, E as AgentToolsSchema, T as GuardrailConfigSchema, _ as applyMessageDefaults, b as applyTalkApiKey, d as applyLegacyMigrations, f as findLegacyConfigIssues, g as applyLoggingDefaults, h as applyContextPruningDefaults, m as applyCompactionDefaults, n as validateConfigObjectWithPlugins, p as applyAgentDefaults, t as validateConfigObjectRawWithPlugins, v as applyModelDefaults, w as findDuplicateAgentDirs, y as applySessionDefaults } from "./validation-B00oOkJ3.js";
import { n as isPathInside } from "./scan-paths-Dx-7XBMF.js";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import JSON5 from "json5";
import crypto from "node:crypto";
import { z } from "zod";
import dotenv from "dotenv";
import { parse, stringify } from "comment-json";

//#region src/agents/identity/agent-manifest-schema.ts
/**
* Zod schema for per-agent manifest files (agent.json).
*
* Validates the canonical per-agent configuration that lives at
* ~/.minion/agents/{id}/agent.json. The manifest is source of truth
* for agent identity, type, scope overrides, guardrails, and capabilities.
*/
const AgentTypeSchema = z.enum([
	"admin",
	"standard",
	"restricted"
]);
const AgentScopesOverrideSchema = z.object({
	addScopes: z.array(z.string()).optional(),
	removeScopes: z.array(z.string()).optional()
}).strict().optional();
const AgentManifestSchema = z.object({
	id: z.string().optional(),
	name: z.string().optional(),
	agentType: AgentTypeSchema.optional(),
	scopes: AgentScopesOverrideSchema,
	guardrails: GuardrailConfigSchema,
	owners: z.array(z.string()).optional(),
	skills: z.array(z.string()).optional(),
	sessionMode: z.enum(["isolated", "shared"]).optional(),
	capabilities: z.object({
		role: z.enum(["orchestrator", "specialist"]).optional(),
		description: z.string().optional(),
		keywords: z.array(z.string()).optional(),
		taskTypes: z.array(z.string()).optional(),
		estimatedTime: z.string().optional()
	}).strict().optional(),
	tools: AgentToolsSchema
}).strict();

//#endregion
//#region src/agents/identity/agent-manifest.ts
/**
* Agent manifest discovery, loading, validation, auto-migration, and merge.
*
* Agents are discovered from ~/.minion/agents/XX/agent.json on startup,
* validated with Zod (AgentManifestSchema), and merged into the config.
* Legacy agents.list[] entries are auto-migrated to agent.json files.
*/
/**
* Discover agent.json manifests from agent subdirectories.
*
* Scans agentsBaseDir for subdirectories containing agent.json (or minion.json
* as fallback). Each manifest is parsed with JSON5 and validated with
* AgentManifestSchema. Malformed manifests are skipped with a warning.
*/
function discoverAgentManifests(params) {
	const { agentsBaseDir, logger } = params;
	if (!fs.existsSync(agentsBaseDir)) return [];
	const entries = fs.readdirSync(agentsBaseDir, { withFileTypes: true });
	const manifests = [];
	for (const entry of entries) {
		if (!entry.isDirectory()) continue;
		const agentId = entry.name;
		const agentJsonPath = path.join(agentsBaseDir, agentId, "agent.json");
		const minionJsonPath = path.join(agentsBaseDir, agentId, "minion.json");
		let configPath;
		if (fs.existsSync(agentJsonPath)) configPath = agentJsonPath;
		else if (fs.existsSync(minionJsonPath)) configPath = minionJsonPath;
		if (!configPath) continue;
		try {
			const raw = fs.readFileSync(configPath, "utf-8");
			const parsed = JSON5.parse(raw);
			const result = AgentManifestSchema.safeParse(parsed);
			if (!result.success) {
				const errors = result.error.issues.map((iss) => `${iss.path.join(".")}: ${iss.message}`).join(", ");
				logger.warn(`Skipping agent '${agentId}': invalid manifest at ${configPath} (${errors})`);
				continue;
			}
			const manifest = result.data;
			if (!manifest.id) manifest.id = agentId;
			manifests.push(manifest);
		} catch (err) {
			logger.warn(`Skipping agent '${agentId}': failed to parse ${configPath} (${String(err)})`);
		}
	}
	return manifests;
}
/**
* Fields that belong exclusively in gateway.json as deployment overrides.
* These are excluded from auto-migration to agent.json.
*/
const DEPLOYMENT_FIELDS = new Set([
	"id",
	"default",
	"model",
	"sandbox",
	"workspace",
	"agentDir",
	"heartbeat",
	"humanDelay",
	"memorySearch",
	"memory",
	"messages",
	"subagents",
	"groupChat",
	"identity",
	"systemPromptUserAwareness"
]);
/**
* Auto-migrate agents.list[] entries to agent.json manifest files.
*
* For each agent in agentsList that does NOT have an existing agent.json,
* writes a new agent.json with manifest-appropriate fields (excluding
* deployment fields like model, sandbox, workspace).
*/
function migrateAgentsListToManifests(params) {
	const { agentsList, agentsBaseDir, logger } = params;
	for (const agent of agentsList) {
		if (!agent.id) continue;
		const agentDir = path.join(agentsBaseDir, agent.id);
		const agentJsonPath = path.join(agentDir, "agent.json");
		if (fs.existsSync(agentJsonPath)) continue;
		const manifest = {};
		for (const [key, value] of Object.entries(agent)) if (!DEPLOYMENT_FIELDS.has(key) && value !== void 0) manifest[key] = value;
		if (Object.keys(manifest).length === 0) continue;
		try {
			fs.mkdirSync(agentDir, { recursive: true });
			const content = JSON.stringify(manifest, null, 2);
			fs.writeFileSync(agentJsonPath, content, {
				encoding: "utf-8",
				mode: 384
			});
			logger.info?.(`Migrated agents.list[] entry '${agent.id}' to ${agentJsonPath}`);
		} catch (err) {
			logger.warn(`Failed to migrate agent '${agent.id}' to agent.json: ${String(err)}`);
		}
	}
}
/**
* Merge a manifest with an agents.list[] entry. List entry fields
* override manifest fields where defined (gateway.json wins).
*/
function mergeManifestWithListEntry(manifest, listEntry) {
	const base = { ...manifest };
	for (const [key, value] of Object.entries(listEntry)) if (value !== void 0) base[key] = value;
	if (!base.id && manifest.id) base.id = manifest.id;
	return base;
}
/**
* Inject discovered agent manifests into the agents list.
*
* For each discovered manifest:
* - If a matching agents.list[] entry exists: merge (list wins)
* - If no match: create new entry from manifest
*
* Preserves ordering: existing list entries first, then newly discovered.
*/
const warnedManifestFieldAgents = /* @__PURE__ */ new Set();
function injectDiscoveredAgents(params) {
	const { manifests, existingList, logger } = params;
	const existingById = /* @__PURE__ */ new Map();
	for (let i = 0; i < existingList.length; i++) {
		const agent = existingList[i];
		if (agent?.id) existingById.set(agent.id, i);
	}
	const result = [...existingList];
	const mergedIds = /* @__PURE__ */ new Set();
	for (const manifest of manifests) {
		const id = manifest.id;
		if (!id) continue;
		const existingIndex = existingById.get(id);
		if (existingIndex !== void 0) {
			const listEntry = existingList[existingIndex];
			result[existingIndex] = mergeManifestWithListEntry(manifest, listEntry);
			mergedIds.add(id);
			if (Object.keys(listEntry).some((key) => !DEPLOYMENT_FIELDS.has(key) && listEntry[key] !== void 0) && !warnedManifestFieldAgents.has(id)) {
				warnedManifestFieldAgents.add(id);
				logger.warn?.(`agents.list[] entry for '${id}' has manifest fields that belong in agent.json (gateway.json deployment overrides still applied)`);
			}
		} else {
			const agentConfig = {
				...manifest,
				id
			};
			result.push(agentConfig);
		}
	}
	return result;
}

//#endregion
//#region src/agents/identity/agent-types.ts
/** All valid scope values, for validation purposes. */
const ALL_SCOPES = [
	"fs:read",
	"fs:write",
	"exec",
	"sessions",
	"memory",
	"web",
	"messaging",
	"gateway",
	"browser",
	"media",
	"gog",
	"nodes"
];
/** Default agent type when omitted from config. */
const DEFAULT_AGENT_TYPE = "standard";
/**
* Scope sets for each agent type.
* - admin: all scopes
* - standard: common development/messaging scopes (no gateway, browser, gog, nodes)
* - restricted: read-only filesystem, memory, web
*/
const AGENT_TYPE_SCOPES = {
	admin: [
		"fs:read",
		"fs:write",
		"exec",
		"sessions",
		"memory",
		"web",
		"messaging",
		"gateway",
		"browser",
		"media",
		"gog",
		"nodes"
	],
	standard: [
		"fs:read",
		"fs:write",
		"exec",
		"sessions",
		"memory",
		"web",
		"messaging",
		"media"
	],
	restricted: [
		"fs:read",
		"memory",
		"web"
	]
};
/**
* Default guardrail profile for each agent type.
* - admin: permissive (no guardrails by default)
* - standard: moderate
* - restricted: strict
*/
const AGENT_TYPE_GUARDRAIL_PROFILES = {
	admin: "permissive",
	standard: "moderate",
	restricted: "strict"
};
/**
* Named scope presets that unify legacy ToolProfileId (minimal/coding/messaging/full)
* and PolicyPresetName (analysis/developer/verification) into the scope model.
*
* These serve as convenient aliases for common scope sets.
*/
const SCOPE_PRESETS = {
	minimal: ["sessions"],
	coding: [
		"fs:read",
		"fs:write",
		"exec",
		"sessions",
		"memory"
	],
	messaging: ["messaging", "sessions"],
	full: [
		"fs:read",
		"fs:write",
		"exec",
		"sessions",
		"memory",
		"web",
		"messaging",
		"gateway",
		"browser",
		"media",
		"gog",
		"nodes"
	],
	analysis: [
		"fs:read",
		"memory",
		"sessions"
	],
	developer: [
		"fs:read",
		"fs:write",
		"exec",
		"memory"
	],
	verification: [
		"fs:read",
		"exec",
		"memory"
	],
	github_access: ["web"]
};

//#endregion
//#region src/infra/dotenv.ts
function loadDotEnv(opts) {
	const quiet = opts?.quiet ?? true;
	dotenv.config({ quiet });
	const globalEnvPath = path.join(resolveConfigDir(process.env), ".env");
	if (!fs.existsSync(globalEnvPath)) return;
	dotenv.config({
		quiet,
		path: globalEnvPath,
		override: false
	});
}

//#endregion
//#region src/version.ts
function firstNonEmpty(...values) {
	for (const value of values) {
		const trimmed = value?.trim();
		if (trimmed) return trimmed;
	}
}
function resolveRuntimeServiceVersion(env = process.env, fallback = "dev") {
	return firstNonEmpty(env["MINION_VERSION"], env["OPENCLAW_VERSION"], env["MINION_SERVICE_VERSION"], env["OPENCLAW_SERVICE_VERSION"], env["npm_package_version"]) ?? fallback;
}
const VERSION = "2026.5.14-dev";

//#endregion
//#region src/config/backup-rotation.ts
const CONFIG_BACKUP_COUNT = 5;
async function rotateConfigBackups(configPath, ioFs) {
	if (CONFIG_BACKUP_COUNT <= 1) return;
	const backupBase = `${configPath}.bak`;
	const maxIndex = CONFIG_BACKUP_COUNT - 1;
	await ioFs.unlink(`${backupBase}.${maxIndex}`).catch(() => {});
	for (let index = maxIndex - 1; index >= 1; index -= 1) await ioFs.rename(`${backupBase}.${index}`, `${backupBase}.${index + 1}`).catch(() => {});
	await ioFs.rename(backupBase, `${backupBase}.1`).catch(() => {});
}

//#endregion
//#region src/config/comment-preserve.ts
/**
* Parse JSON/JSON5 string preserving comments as Symbol metadata.
* Drop-in replacement for JSON5.parse that retains comment positions.
*/
function parseWithComments(raw) {
	return parse(raw);
}
/**
* Serialize a value to JSON with comments restored in their original positions.
* Drop-in replacement for JSON.stringify that outputs comments.
*/
function stringifyWithComments(value, space) {
	return stringify(value, null, space);
}
/**
* Copy comment Symbol metadata from a source object to a target object.
* Only copies Symbol properties (where comment-json stores comments),
* not data properties — so the target's values remain unchanged.
*
* Recurses into matching object keys so nested comments are preserved.
*/
function transferComments(source, target) {
	if (!source || typeof source !== "object" || !target || typeof target !== "object") return target;
	for (const sym of Object.getOwnPropertySymbols(source)) target[sym] = source[sym];
	const src = source;
	const tgt = target;
	for (const key of Object.keys(tgt)) if (key in src && src[key] && typeof src[key] === "object" && tgt[key] && typeof tgt[key] === "object") transferComments(src[key], tgt[key]);
	return target;
}

//#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_]*\}/;
function isPlainObject$1(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
}
/**
* Check if a string contains any `${VAR}` env var references.
*/
function hasEnvVarRef(value) {
	return ENV_VAR_PATTERN.test(value);
}
/**
* Resolve `${VAR}` references in a single string using the given env.
* Returns null if any referenced var is missing (instead of throwing).
*
* 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 ENV_VAR_NAME = /^[A-Z_][A-Z0-9_]*$/;
	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.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.test(name)) {
						const val = env[name];
						if (val === void 0 || val === "") return null;
						chunks.push(val);
						i = end;
						continue;
					}
				}
			}
		}
		chunks.push(template[i]);
	}
	return chunks.join("");
}
/**
* 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)) return incoming.map((item, i) => i < parsed.length ? restoreEnvVarRefs(item, parsed[i], env) : item);
	if (isPlainObject$1(incoming) && isPlainObject$1(parsed)) {
		const result = {};
		for (const [key, value] of Object.entries(incoming)) if (key in parsed) result[key] = restoreEnvVarRefs(value, parsed[key], env);
		else result[key] = value;
		return result;
	}
	return incoming;
}

//#endregion
//#region src/config/env-substitution.ts
/**
* Environment variable substitution for config values.
*
* Supports `${VAR_NAME}` syntax in string values, substituted at config load time.
* - Only uppercase env vars are matched: `[A-Z_][A-Z0-9_]*`
* - Escape with `$${}` to output literal `${}`
* - Missing env vars throw `MissingEnvVarError` with context
*
* @example
* ```json5
* {
*   models: {
*     providers: {
*       "vercel-gateway": {
*         apiKey: "${VERCEL_GATEWAY_API_KEY}"
*       }
*     }
*   }
* }
* ```
*/
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
var MissingEnvVarError = class extends Error {
	constructor(varName, configPath) {
		super(`Missing env var "${varName}" referenced at config path: ${configPath}`);
		this.varName = varName;
		this.configPath = configPath;
		this.name = "MissingEnvVarError";
	}
};
function parseEnvTokenAt(value, index) {
	if (value[index] !== "$") return null;
	const next = value[index + 1];
	const afterNext = value[index + 2];
	if (next === "$" && afterNext === "{") {
		const start = index + 3;
		const end = value.indexOf("}", start);
		if (end !== -1) {
			const name = value.slice(start, end);
			if (ENV_VAR_NAME_PATTERN.test(name)) return {
				kind: "escaped",
				name,
				end
			};
		}
	}
	if (next === "{") {
		const start = index + 2;
		const end = value.indexOf("}", start);
		if (end !== -1) {
			const name = value.slice(start, end);
			if (ENV_VAR_NAME_PATTERN.test(name)) return {
				kind: "substitution",
				name,
				end
			};
		}
	}
	return null;
}
function substituteString(value, env, configPath) {
	if (!value.includes("$")) return value;
	const chunks = [];
	for (let i = 0; i < value.length; i += 1) {
		const char = value[i];
		if (char !== "$") {
			chunks.push(char);
			continue;
		}
		const token = parseEnvTokenAt(value, i);
		if (token?.kind === "escaped") {
			chunks.push(`\${${token.name}}`);
			i = token.end;
			continue;
		}
		if (token?.kind === "substitution") {
			const envValue = env[token.name];
			if (envValue === void 0 || envValue === "") throw new MissingEnvVarError(token.name, configPath);
			chunks.push(envValue);
			i = token.end;
			continue;
		}
		chunks.push(char);
	}
	return chunks.join("");
}
function containsEnvVarReference(value) {
	if (!value.includes("$")) return false;
	for (let i = 0; i < value.length; i += 1) {
		if (value[i] !== "$") continue;
		const token = parseEnvTokenAt(value, i);
		if (token?.kind === "escaped") {
			i = token.end;
			continue;
		}
		if (token?.kind === "substitution") return true;
	}
	return false;
}
function substituteAny(value, env, path) {
	if (typeof value === "string") return substituteString(value, env, path);
	if (Array.isArray(value)) return value.map((item, index) => substituteAny(item, env, `${path}[${index}]`));
	if (isPlainObject$2(value)) {
		const result = {};
		for (const [key, val] of Object.entries(value)) result[key] = substituteAny(val, env, path ? `${path}.${key}` : key);
		return result;
	}
	return value;
}
/**
* Resolves `${VAR_NAME}` environment variable references in config values.
*
* @param obj - The parsed config object (after JSON5 parse and $include resolution)
* @param env - Environment variables to use for substitution (defaults to process.env)
* @returns The config object with env vars substituted
* @throws {MissingEnvVarError} If a referenced env var is not set or empty
*/
function resolveConfigEnvVars(obj, env = process.env) {
	return substituteAny(obj, env, "");
}

//#endregion
//#region src/config/env-vars.ts
function collectConfigEnvVars(cfg) {
	const envConfig = cfg?.env;
	if (!envConfig) return {};
	const entries = {};
	if (envConfig.vars) for (const [key, value] of Object.entries(envConfig.vars)) {
		if (!value) continue;
		entries[key] = value;
	}
	for (const [key, value] of Object.entries(envConfig)) {
		if (key === "shellEnv" || key === "vars") continue;
		if (typeof value !== "string" || !value.trim()) continue;
		entries[key] = value;
	}
	return entries;
}
function applyConfigEnvVars(cfg, env = process.env) {
	const entries = collectConfigEnvVars(cfg);
	for (const [key, value] of Object.entries(entries)) {
		if (env[key]?.trim()) continue;
		env[key] = value;
	}
}

//#endregion
//#region src/config/guardrail-merge.ts
/**
* Merge global and per-agent guardrail configs with additive array semantics.
*
* @param params.global - Global guardrail config from gateway.json
* @param params.perAgent - Per-agent guardrail config from agent.json
* @param params.agentTypeProfile - Default guardrail profile from agent type mapping
* @returns Merged config, or undefined if both inputs are empty
*/
function mergeGuardrailConfig(params) {
	const { global, perAgent, agentTypeProfile } = params;
	if (!global && !perAgent && !agentTypeProfile) return;
	if (!global && !perAgent && agentTypeProfile) return { profile: agentTypeProfile };
	if (!perAgent) {
		const result = { ...global };
		if (!result.profile && agentTypeProfile) result.profile = agentTypeProfile;
		return result;
	}
	if (!global) {
		const result = { ...perAgent };
		if (!result.profile && agentTypeProfile) result.profile = agentTypeProfile;
		return result;
	}
	const merged = {};
	merged.profile = perAgent.profile ?? global.profile ?? agentTypeProfile;
	if (perAgent.actionMode !== void 0) merged.actionMode = perAgent.actionMode;
	else if (global.actionMode !== void 0) merged.actionMode = global.actionMode;
	if (perAgent.dryRun !== void 0) merged.dryRun = perAgent.dryRun;
	else if (global.dryRun !== void 0) merged.dryRun = global.dryRun;
	merged.customPatterns = mergePatternArrays(global.customPatterns, perAgent.customPatterns);
	if (global.dataLeakage || perAgent.dataLeakage) {
		merged.dataLeakage = {};
		const gDL = global.dataLeakage;
		const pDL = perAgent.dataLeakage;
		if (pDL?.enabled !== void 0) merged.dataLeakage.enabled = pDL.enabled;
		else if (gDL?.enabled !== void 0) merged.dataLeakage.enabled = gDL.enabled;
		if (pDL?.builtinPatterns !== void 0) merged.dataLeakage.builtinPatterns = pDL.builtinPatterns;
		else if (gDL?.builtinPatterns !== void 0) merged.dataLeakage.builtinPatterns = gDL.builtinPatterns;
		merged.dataLeakage.patterns = mergePatternArrays(gDL?.patterns, pDL?.patterns);
	}
	if (global.topicBoundaries || perAgent.topicBoundaries) {
		merged.topicBoundaries = {};
		const gTB = global.topicBoundaries;
		const pTB = perAgent.topicBoundaries;
		if (pTB?.enabled !== void 0) merged.topicBoundaries.enabled = pTB.enabled;
		else if (gTB?.enabled !== void 0) merged.topicBoundaries.enabled = gTB.enabled;
		merged.topicBoundaries.blocklist = mergeStringArrays(gTB?.blocklist, pTB?.blocklist);
	}
	return cleanUndefinedFields(merged);
}
/**
* Merge two GuardrailPattern arrays with deduplication by name.
* Per-agent patterns with the same name as global patterns replace the global version.
*/
function mergePatternArrays(base, overlay) {
	if (!base?.length && !overlay?.length) return;
	if (!base?.length) return overlay;
	if (!overlay?.length) return base;
	const byName = /* @__PURE__ */ new Map();
	for (const p of base) byName.set(p.name, p);
	for (const p of overlay) byName.set(p.name, p);
	return [...byName.values()];
}
/**
* Merge two string arrays with deduplication (preserves order).
*/
function mergeStringArrays(base, overlay) {
	if (!base?.length && !overlay?.length) return;
	if (!base?.length) return overlay;
	if (!overlay?.length) return base;
	const seen = /* @__PURE__ */ new Set();
	const result = [];
	for (const s of base) if (!seen.has(s)) {
		seen.add(s);
		result.push(s);
	}
	for (const s of overlay) if (!seen.has(s)) {
		seen.add(s);
		result.push(s);
	}
	return result;
}
/**
* Remove undefined fields from an object (shallow).
*/
function cleanUndefinedFields(obj) {
	const result = {};
	for (const [key, value] of Object.entries(obj)) if (value !== void 0) result[key] = value;
	return result;
}

//#endregion
//#region src/config/includes.ts
/**
* Config includes: $include directive for modular configs
*
* @example
* ```json5
* {
*   "$include": "./base.json5",           // single file
*   "$include": ["./a.json5", "./b.json5"] // merge multiple
* }
* ```
*/
const INCLUDE_KEY = "$include";
const MAX_INCLUDE_DEPTH = 10;
var ConfigIncludeError = class extends Error {
	constructor(message, includePath, cause) {
		super(message);
		this.includePath = includePath;
		this.cause = cause;
		this.name = "ConfigIncludeError";
	}
};
var CircularIncludeError = class extends ConfigIncludeError {
	constructor(chain) {
		super(`Circular include detected: ${chain.join(" -> ")}`, chain[chain.length - 1]);
		this.chain = chain;
		this.name = "CircularIncludeError";
	}
};
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
function deepMerge(target, source) {
	if (Array.isArray(target) && Array.isArray(source)) return [...target, ...source];
	if (isPlainObject$2(target) && isPlainObject$2(source)) {
		const result = { ...target };
		for (const key of Object.keys(source)) result[key] = key in result ? deepMerge(result[key], source[key]) : source[key];
		return result;
	}
	return source;
}
var IncludeProcessor = class IncludeProcessor {
	constructor(basePath, resolver, rootDir) {
		this.basePath = basePath;
		this.resolver = resolver;
		this.visited = /* @__PURE__ */ new Set();
		this.depth = 0;
		this.visited.add(path.normalize(basePath));
		this.rootDir = path.normalize(rootDir ?? path.dirname(basePath));
		this.rootRealDir = path.normalize(safeRealpath(this.rootDir));
	}
	process(obj) {
		if (Array.isArray(obj)) return obj.map((item) => this.process(item));
		if (!isPlainObject$2(obj)) return obj;
		if (!(INCLUDE_KEY in obj)) return this.processObject(obj);
		return this.processInclude(obj);
	}
	processObject(obj) {
		const result = {};
		for (const [key, value] of Object.entries(obj)) result[key] = this.process(value);
		return result;
	}
	processInclude(obj) {
		const includeValue = obj[INCLUDE_KEY];
		const otherKeys = Object.keys(obj).filter((k) => k !== INCLUDE_KEY);
		const included = this.resolveInclude(includeValue);
		if (otherKeys.length === 0) return included;
		if (!isPlainObject$2(included)) throw new ConfigIncludeError("Sibling keys require included content to be an object", typeof includeValue === "string" ? includeValue : INCLUDE_KEY);
		const rest = {};
		for (const key of otherKeys) rest[key] = this.process(obj[key]);
		return deepMerge(included, rest);
	}
	resolveInclude(value) {
		if (typeof value === "string") return this.loadFile(value);
		if (Array.isArray(value)) return value.reduce((merged, item) => {
			if (typeof item !== "string") throw new ConfigIncludeError(`Invalid $include array item: expected string, got ${typeof item}`, String(item));
			return deepMerge(merged, this.loadFile(item));
		}, {});
		throw new ConfigIncludeError(`Invalid $include value: expected string or array of strings, got ${typeof value}`, String(value));
	}
	loadFile(includePath) {
		const resolvedPath = this.resolvePath(includePath);
		this.checkCircular(resolvedPath);
		this.checkDepth(includePath);
		const raw = this.readFile(includePath, resolvedPath);
		const parsed = this.parseFile(includePath, resolvedPath, raw);
		return this.processNested(resolvedPath, parsed);
	}
	resolvePath(includePath) {
		const configDir = path.dirname(this.basePath);
		const resolved = path.isAbsolute(includePath) ? includePath : path.resolve(configDir, includePath);
		const normalized = path.normalize(resolved);
		if (!isPathInside(this.rootDir, normalized)) throw new ConfigIncludeError(`Include path escapes config directory: ${includePath} (root: ${this.rootDir})`, includePath);
		try {
			const real = fs.realpathSync(normalized);
			if (!isPathInside(this.rootRealDir, real)) throw new ConfigIncludeError(`Include path resolves outside config directory (symlink): ${includePath} (root: ${this.rootDir})`, includePath);
		} catch (err) {
			if (err instanceof ConfigIncludeError) throw err;
		}
		return normalized;
	}
	checkCircular(resolvedPath) {
		if (this.visited.has(resolvedPath)) throw new CircularIncludeError([...this.visited, resolvedPath]);
	}
	checkDepth(includePath) {
		if (this.depth >= MAX_INCLUDE_DEPTH) throw new ConfigIncludeError(`Maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded at: ${includePath}`, includePath);
	}
	readFile(includePath, resolvedPath) {
		try {
			return this.resolver.readFile(resolvedPath);
		} catch (err) {
			throw new ConfigIncludeError(`Failed to read include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
		}
	}
	parseFile(includePath, resolvedPath, raw) {
		try {
			return this.resolver.parseJson(raw);
		} catch (err) {
			throw new ConfigIncludeError(`Failed to parse include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
		}
	}
	processNested(resolvedPath, parsed) {
		const nested = new IncludeProcessor(resolvedPath, this.resolver, this.rootDir);
		nested.visited = new Set([...this.visited, resolvedPath]);
		nested.depth = this.depth + 1;
		return nested.process(parsed);
	}
};
function safeRealpath(target) {
	try {
		return fs.realpathSync(target);
	} catch {
		return target;
	}
}
const defaultResolver = {
	readFile: (p) => fs.readFileSync(p, "utf-8"),
	parseJson: (raw) => JSON5.parse(raw)
};
/**
* Resolves all $include directives in a parsed config object.
*/
function resolveConfigIncludes(obj, configPath, resolver = defaultResolver) {
	return new IncludeProcessor(configPath, resolver).process(obj);
}

//#endregion
//#region src/config/merge-patch.ts
function isObjectWithStringId(value) {
	if (!isPlainObject$2(value)) return false;
	return typeof value.id === "string" && value.id.length > 0;
}
/**
* Merge arrays of object-like entries keyed by `id`.
*
* Contract:
* - Base array must be fully id-keyed; otherwise return undefined (caller should replace).
* - Patch entries with valid id merge by id (or append when the id is new).
* - Patch entries without valid id append as-is, avoiding destructive full-array replacement.
*/
function mergeObjectArraysById(base, patch, options) {
	if (!base.every(isObjectWithStringId)) return;
	const merged = [...base];
	const indexById = /* @__PURE__ */ new Map();
	for (const [index, entry] of merged.entries()) {
		if (!isObjectWithStringId(entry)) return;
		indexById.set(entry.id, index);
	}
	for (const patchEntry of patch) {
		if (!isObjectWithStringId(patchEntry)) {
			merged.push(structuredClone(patchEntry));
			continue;
		}
		const existingIndex = indexById.get(patchEntry.id);
		if (existingIndex === void 0) {
			merged.push(structuredClone(patchEntry));
			indexById.set(patchEntry.id, merged.length - 1);
			continue;
		}
		merged[existingIndex] = applyMergePatch(merged[existingIndex], patchEntry, options);
	}
	return merged;
}
function applyMergePatch(base, patch, options = {}) {
	if (!isPlainObject$2(patch)) return patch;
	const result = isPlainObject$2(base) ? { ...base } : {};
	for (const [key, value] of Object.entries(patch)) {
		if (value === null) {
			delete result[key];
			continue;
		}
		if (options.mergeObjectArraysById && Array.isArray(result[key]) && Array.isArray(value)) {
			const mergedArray = mergeObjectArraysById(result[key], value, options);
			if (mergedArray) {
				result[key] = mergedArray;
				continue;
			}
		}
		if (isPlainObject$2(value)) {
			const baseValue = result[key];
			result[key] = applyMergePatch(isPlainObject$2(baseValue) ? baseValue : {}, value, options);
			continue;
		}
		result[key] = value;
	}
	return result;
}

//#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 = new Set(["paths", "pathPrepend"]);
function normalizeStringValue(key, value) {
	if (!PATH_VALUE_RE.test(value.trim())) return value;
	if (!key) return value;
	if (PATH_KEY_RE.test(key) || PATH_LIST_KEYS.has(key)) return resolveUserPath(value);
	return value;
}
function normalizeAny(key, value) {
	if (typeof value === "string") return normalizeStringValue(key, value);
	if (Array.isArray(value)) {
		const normalizeChildren = Boolean(key && PATH_LIST_KEYS.has(key));
		return value.map((entry) => {
			if (typeof entry === "string") return normalizeChildren ? normalizeStringValue(key, entry) : entry;
			if (Array.isArray(entry)) return normalizeAny(void 0, entry);
			if (isPlainObject$2(entry)) return normalizeAny(void 0, entry);
			return entry;
		});
	}
	if (!isPlainObject$2(value)) return value;
	for (const [childKey, childValue] of Object.entries(value)) {
		const next = normalizeAny(childKey, childValue);
		if (next !== childValue) value[childKey] = next;
	}
	return value;
}
/**
* Normalize "~" paths in path-ish config fields.
*
* Goal: accept `~/...` consistently across config file + env overrides, while
* keeping the surface area small and predictable.
*/
function normalizeConfigPaths(cfg) {
	if (!cfg || typeof cfg !== "object") return cfg;
	normalizeAny(void 0, cfg);
	return cfg;
}

//#endregion
//#region src/config/config-paths.ts
const BLOCKED_KEYS = new Set([
	"__proto__",
	"prototype",
	"constructor"
]);
function parseConfigPath(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return {
		ok: false,
		error: "Invalid path. Use dot notation (e.g. foo.bar)."
	};
	const parts = trimmed.split(".").map((part) => part.trim());
	if (parts.some((part) => !part)) return {
		ok: false,
		error: "Invalid path. Use dot notation (e.g. foo.bar)."
	};
	if (parts.some((part) => BLOCKED_KEYS.has(part))) return {
		ok: false,
		error: "Invalid path segment."
	};
	return {
		ok: true,
		path: parts
	};
}
function setConfigValueAtPath(root, path, value) {
	let cursor = root;
	for (let idx = 0; idx < path.length - 1; idx += 1) {
		const key = path[idx];
		const next = cursor[key];
		if (!isPlainObject$2(next)) cursor[key] = {};
		cursor = cursor[key];
	}
	cursor[path[path.length - 1]] = value;
}
function unsetConfigValueAtPath(root, path) {
	const stack = [];
	let cursor = root;
	for (let idx = 0; idx < path.length - 1; idx += 1) {
		const key = path[idx];
		const next = cursor[key];
		if (!isPlainObject$2(next)) return false;
		stack.push({
			node: cursor,
			key
		});
		cursor = next;
	}
	const leafKey = path[path.length - 1];
	if (!(leafKey in cursor)) return false;
	delete cursor[leafKey];
	for (let idx = stack.length - 1; idx >= 0; idx -= 1) {
		const { node, key } = stack[idx];
		const child = node[key];
		if (isPlainObject$2(child) && Object.keys(child).length === 0) delete node[key];
		else break;
	}
	return true;
}
function getConfigValueAtPath(root, path) {
	let cursor = root;
	for (const key of path) {
		if (!isPlainObject$2(cursor)) return;
		cursor = cursor[key];
	}
	return cursor;
}

//#endregion
//#region src/config/runtime-overrides.ts
let overrides = {};
function mergeOverrides(base, override) {
	if (!isPlainObject$2(base) || !isPlainObject$2(override)) return override;
	const next = { ...base };
	for (const [key, value] of Object.entries(override)) {
		if (value === void 0) continue;
		next[key] = mergeOverrides(base[key], value);
	}
	return next;
}
function getConfigOverrides() {
	return overrides;
}
function resetConfigOverrides() {
	overrides = {};
}
function setConfigOverride(pathRaw, value) {
	const parsed = parseConfigPath(pathRaw);
	if (!parsed.ok || !parsed.path) return {
		ok: false,
		error: parsed.error ?? "Invalid path."
	};
	setConfigValueAtPath(overrides, parsed.path, value);
	return { ok: true };
}
function unsetConfigOverride(pathRaw) {
	const parsed = parseConfigPath(pathRaw);
	if (!parsed.ok || !parsed.path) return {
		ok: false,
		removed: false,
		error: parsed.error ?? "Invalid path."
	};
	return {
		ok: true,
		removed: unsetConfigValueAtPath(overrides, parsed.path)
	};
}
function applyConfigOverrides(cfg) {
	if (!overrides || Object.keys(overrides).length === 0) return cfg;
	return mergeOverrides(cfg, overrides);
}

//#endregion
//#region src/config/version.ts
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-(\d+))?/;
function parseMinionVersion(raw) {
	if (!raw) return null;
	const match = raw.trim().match(VERSION_RE);
	if (!match) return null;
	const [, major, minor, patch, revision] = match;
	return {
		major: Number.parseInt(major, 10),
		minor: Number.parseInt(minor, 10),
		patch: Number.parseInt(patch, 10),
		revision: revision ? Number.parseInt(revision, 10) : 0
	};
}
function compareMinionVersions(a, b) {
	const parsedA = parseMinionVersion(a);
	const parsedB = parseMinionVersion(b);
	if (!parsedA || !parsedB) return null;
	if (parsedA.major !== parsedB.major) return parsedA.major < parsedB.major ? -1 : 1;
	if (parsedA.minor !== parsedB.minor) return parsedA.minor < parsedB.minor ? -1 : 1;
	if (parsedA.patch !== parsedB.patch) return parsedA.patch < parsedB.patch ? -1 : 1;
	if (parsedA.revision !== parsedB.revision) return parsedA.revision < parsedB.revision ? -1 : 1;
	return 0;
}
/** @deprecated Use compareMinionVersions */
const compareOpenClawVersions = compareMinionVersions;

//#endregion
//#region src/config/io.ts
const SHELL_ENV_EXPECTED_KEYS = [
	"OPENAI_API_KEY",
	"ANTHROPIC_API_KEY",
	"ANTHROPIC_OAUTH_TOKEN",
	"GEMINI_API_KEY",
	"ZAI_API_KEY",
	"OPENROUTER_API_KEY",
	"AI_GATEWAY_API_KEY",
	"MINIMAX_API_KEY",
	"SYNTHETIC_API_KEY",
	"ELEVENLABS_API_KEY",
	"TELEGRAM_BOT_TOKEN",
	"DISCORD_BOT_TOKEN",
	"SLACK_BOT_TOKEN",
	"SLACK_APP_TOKEN",
	"OPENCLAW_GATEWAY_TOKEN",
	"OPENCLAW_GATEWAY_PASSWORD"
];
const CONFIG_AUDIT_LOG_FILENAME = "config-audit.jsonl";
const loggedInvalidConfigs = /* @__PURE__ */ new Set();
const _warnedConfigDetails = /* @__PURE__ */ new Set();
function hashConfigRaw(raw) {
	return crypto.createHash("sha256").update(raw ?? "").digest("hex");
}
function resolveConfigSnapshotHash(snapshot) {
	if (typeof snapshot.hash === "string") {
		const trimmed = snapshot.hash.trim();
		if (trimmed) return trimmed;
	}
	if (typeof snapshot.raw !== "string") return null;
	return hashConfigRaw(snapshot.raw);
}
function coerceConfig(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return {};
	return value;
}
function isPlainObject(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function hasConfigMeta(value) {
	if (!isPlainObject(value)) return false;
	const meta = value.meta;
	return isPlainObject(meta);
}
function resolveGatewayMode(value) {
	if (!isPlainObject(value)) return null;
	const gateway = value.gateway;
	if (!isPlainObject(gateway) || typeof gateway.mode !== "string") return null;
	const trimmed = gateway.mode.trim();
	return trimmed.length > 0 ? trimmed : null;
}
function cloneUnknown(value) {
	return structuredClone(value);
}
function createMergePatch(base, target) {
	if (!isPlainObject(base) || !isPlainObject(target)) return cloneUnknown(target);
	const patch = {};
	const keys = new Set([...Object.keys(base), ...Object.keys(target)]);
	for (const key of keys) {
		const hasBase = key in base;
		if (!(key in target)) {
			patch[key] = null;
			continue;
		}
		const targetValue = target[key];
		if (!hasBase) {
			patch[key] = cloneUnknown(targetValue);
			continue;
		}
		const baseValue = base[key];
		if (isPlainObject(baseValue) && isPlainObject(targetValue)) {
			const childPatch = createMergePatch(baseValue, targetValue);
			if (isPlainObject(childPatch) && Object.keys(childPatch).length === 0) continue;
			patch[key] = childPatch;
			continue;
		}
		if (!isDeepStrictEqual(baseValue, targetValue)) patch[key] = cloneUnknown(targetValue);
	}
	return patch;
}
function collectEnvRefPaths(value, path, output) {
	if (typeof value === "string") {
		if (containsEnvVarReference(value)) output.set(path, value);
		return;
	}
	if (Array.isArray(value)) {
		value.forEach((item, index) => {
			collectEnvRefPaths(item, `${path}[${index}]`, output);
		});
		return;
	}
	if (isPlainObject(value)) for (const [key, child] of Object.entries(value)) collectEnvRefPaths(child, path ? `${path}.${key}` : key, output);
}
function collectChangedPaths(base, target, path, output) {
	if (Array.isArray(base) && Array.isArray(target)) {
		const max = Math.max(base.length, target.length);
		for (let index = 0; index < max; index += 1) {
			const childPath = path ? `${path}[${index}]` : `[${index}]`;
			if (index >= base.length || index >= target.length) {
				output.add(childPath);
				continue;
			}
			collectChangedPaths(base[index], target[index], childPath, output);
		}
		return;
	}
	if (isPlainObject(base) && isPlainObject(target)) {
		const keys = new Set([...Object.keys(base), ...Object.keys(target)]);
		for (const key of keys) {
			const childPath = path ? `${path}.${key}` : key;
			const hasBase = key in base;
			if (!(key in target) || !hasBase) {
				output.add(childPath);
				continue;
			}
			collectChangedPaths(base[key], target[key], childPath, output);
		}
		return;
	}
	if (!isDeepStrictEqual(base, target)) output.add(path);
}
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) {
	if (typeof value === "string") {
		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);
			if (updated !== item) changed = true;
			return updated;
		});
		return changed ? next : value;
	}
	if (isPlainObject(value)) {
		let changed = false;
		const next = {};
		for (const [key, child] of Object.entries(value)) {
			const updated = restoreEnvRefsFromMap(child, path ? `${path}.${key}` : key, envRefMap, changedPaths);
			if (updated !== child) changed = true;
			next[key] = updated;
		}
		return changed ? next : value;
	}
	return value;
}
function resolveConfigAuditLogPath(env, homedir) {
	return path.join(resolveStateDir(env, homedir), "logs", CONFIG_AUDIT_LOG_FILENAME);
}
function resolveConfigWriteSuspiciousReasons(params) {
	const reasons = [];
	if (!params.existsBefore) return reasons;
	if (typeof params.previousBytes === "number" && typeof params.nextBytes === "number" && params.previousBytes >= 512 && params.nextBytes < Math.floor(params.previousBytes * .5)) reasons.push(`size-drop:${params.previousBytes}->${params.nextBytes}`);
	if (!params.hasMetaBefore) reasons.push("missing-meta-before-write");
	if (params.gatewayModeBefore && !params.gatewayModeAfter) reasons.push("gateway-mode-removed");
	return reasons;
}
async function appendConfigWriteAuditRecord(deps, record) {
	try {
		const auditPath = resolveConfigAuditLogPath(deps.env, deps.homedir);
		await deps.fs.promises.mkdir(path.dirname(auditPath), {
			recursive: true,
			mode: 448
		});
		await deps.fs.promises.appendFile(auditPath, `${JSON.stringify(record)}\n`, {
			encoding: "utf-8",
			mode: 384
		});
	} catch {}
}
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 stampConfigVersion(cfg) {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	return {
		...cfg,
		meta: {
			...cfg.meta,
			lastTouchedVersion: VERSION,
			lastTouchedAt: now
		}
	};
}
function warnIfConfigFromFuture(cfg, logger) {
	const touched = cfg.meta?.lastTouchedVersion;
	if (!touched) return;
	const cmp = compareOpenClawVersions(VERSION, touched);
	if (cmp === null) return;
	if (cmp < 0) logger.warn(`Config was last written by a newer OpenClaw (${touched}); current version is ${VERSION}.`);
}
function resolveConfigPathForDeps(deps) {
	if (deps.configPath) return deps.configPath;
	return resolveConfigPath(deps.env, resolveStateDir(deps.env, deps.homedir));
}
function normalizeDeps(overrides = {}) {
	return {
		fs: overrides.fs ?? fs,
		json5: overrides.json5 ?? JSON5,
		env: overrides.env ?? process.env,
		homedir: overrides.homedir ?? (() => resolveRequiredHomeDir(overrides.env ?? process.env, os.homedir)),
		configPath: overrides.configPath ?? "",
		logger: overrides.logger ?? console
	};
}
function maybeLoadDotEnvForConfig(env) {
	if (env !== process.env) return;
	loadDotEnv({ quiet: true });
}
function parseConfigJson5(raw, json5 = JSON5) {
	try {
		return {
			ok: true,
			parsed: json5.parse(raw)
		};
	} catch (err) {
		return {
			ok: false,
			error: String(err)
		};
	}
}
function resolveConfigIncludesForRead(parsed, configPath, deps) {
	return resolveConfigIncludes(parsed, configPath, {
		readFile: (candidate) => deps.fs.readFileSync(candidate, "utf-8"),
		parseJson: (raw) => deps.json5.parse(raw)
	});
}
function resolveConfigForRead(resolvedIncludes, env) {
	if (resolvedIncludes && typeof resolvedIncludes === "object" && "env" in resolvedIncludes) applyConfigEnvVars(resolvedIncludes, env);
	return {
		resolvedConfigRaw: resolveConfigEnvVars(resolvedIncludes, env),
		envSnapshotForRestore: { ...env }
	};
}
function createConfigIO(overrides = {}) {
	const deps = normalizeDeps(overrides);
	const requestedConfigPath = resolveConfigPathForDeps(deps);
	const configPath = (deps.configPath ? [requestedConfigPath] : resolveDefaultConfigCandidates(deps.env, deps.homedir)).find((candidate) => deps.fs.existsSync(candidate)) ?? requestedConfigPath;
	function loadConfig() {
		try {
			maybeLoadDotEnvForConfig(deps.env);
			if (!deps.fs.existsSync(configPath)) {
				if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) loadShellEnvFallback({
					enabled: true,
					env: deps.env,
					expectedKeys: SHELL_ENV_EXPECTED_KEYS,
					logger: deps.logger,
					timeoutMs: resolveShellEnvFallbackTimeoutMs(deps.env)
				});
				return {};
			}
			const raw = deps.fs.readFileSync(configPath, "utf-8");
			const parsed = deps.json5.parse(raw);
			const stateDir = resolveStateDir(deps.env, deps.homedir);
			const { resolvedConfigRaw: resolvedConfig } = resolveConfigForRead(resolveConfigIncludesForRead(parsed, configPath, deps), deps.env);
			warnOnConfigMiskeys(resolvedConfig, deps.logger);
			if (typeof resolvedConfig !== "object" || resolvedConfig === null) return {};
			const preValidationDuplicates = findDuplicateAgentDirs(resolvedConfig, {
				env: deps.env,
				homedir: deps.homedir
			});
			if (preValidationDuplicates.length > 0) throw new DuplicateAgentDirError(preValidationDuplicates);
			const validated = validateConfigObjectWithPlugins(resolvedConfig);
			if (!validated.ok) {
				const details = validated.issues.map((iss) => `- ${iss.path || "<root>"}: ${iss.message}`).join("\n");
				if (!loggedInvalidConfigs.has(configPath)) {
					loggedInvalidConfigs.add(configPath);
					deps.logger.error(`Invalid config at ${configPath}:\\n${details}`);
				}
				const error = /* @__PURE__ */ new Error("Invalid config");
				error.code = "INVALID_CONFIG";
				error.details = details;
				throw error;
			}
			if (validated.warnings.length > 0) {
				const newWarnings = validated.warnings.filter((iss) => {
					const line = `${iss.path || "<root>"}: ${iss.message}`;
					if (_warnedConfigDetails.has(line)) return false;
					_warnedConfigDetails.add(line);
					return true;
				});
				if (newWarnings.length > 0) {
					const details = newWarnings.map((iss) => `- ${iss.path || "<root>"}: ${iss.message}`).join("\n");
					deps.logger.warn(`Config warnings:\n${details}`);
				}
			}
			warnIfConfigFromFuture(validated.config, deps.logger);
			const cfg = applyModelDefaults(applyCompactionDefaults(applyContextPruningDefaults(applyAgentDefaults(applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config)))))));
			normalizeConfigPaths(cfg);
			const duplicates = findDuplicateAgentDirs(cfg, {
				env: deps.env,
				homedir: deps.homedir
			});
			if (duplicates.length > 0) throw new DuplicateAgentDirError(duplicates);
			applyConfigEnvVars(cfg, deps.env);
			if ((shouldEnableShellEnvFallback(deps.env) || cfg.env?.shellEnv?.enabled === true) && !shouldDeferShellEnvFallback(deps.env)) loadShellEnvFallback({
				enabled: true,
				env: deps.env,
				expectedKeys: SHELL_ENV_EXPECTED_KEYS,
				logger: deps.logger,
				timeoutMs: cfg.env?.shellEnv?.timeoutMs ?? resolveShellEnvFallbackTimeoutMs(deps.env)
			});
			if (cfg.agents?.list) for (let i = 0; i < cfg.agents.list.length; i++) {
				const agent = cfg.agents.list[i];
				if (!agent?.id) continue;
				const perAgentPath = resolveAgentConfigPathWithFallback(stateDir, agent.id);
				if (!deps.fs.existsSync(perAgentPath)) continue;
				try {
					const perAgentRaw = deps.fs.readFileSync(perAgentPath, "utf-8");
					const perAgentParsed = deps.json5.parse(perAgentRaw);
					if (perAgentParsed && typeof perAgentParsed === "object" && !Array.isArray(perAgentParsed)) cfg.agents.list[i] = {
						...agent,
						...perAgentParsed
					};
				} catch {}
			}
			try {
				const agentsBaseDir = path.join(stateDir, "agents");
				const manifests = discoverAgentManifests({
					agentsBaseDir,
					logger: deps.logger
				});
				if (cfg.agents?.list?.length) migrateAgentsListToManifests({
					agentsList: cfg.agents.list,
					agentsBaseDir,
					logger: deps.logger
				});
				if (manifests.length > 0) {
					if (!cfg.agents) cfg.agents = {};
					cfg.agents.list = injectDiscoveredAgents({
						manifests,
						existingList: cfg.agents.list ?? [],
						logger: deps.logger
					});
				}
			} catch (err) {
				deps.logger.warn(`Agent manifest discovery failed: ${String(err)}`);
			}
			if (cfg.agents?.list) {
				const globalGuardrails = cfg.guardrails;
				for (const agent of cfg.agents.list) {
					if (!agent) continue;
					try {
						const agentGuardrails = agent.guardrails;
						const typeProfile = AGENT_TYPE_GUARDRAIL_PROFILES[agent.agentType ?? DEFAULT_AGENT_TYPE];
						const effective = mergeGuardrailConfig({
							global: globalGuardrails,
							perAgent: agentGuardrails,
							agentTypeProfile: typeProfile
						});
						if (effective) agent._effectiveGuardrails = effective;
					} catch {
						deps.logger.warn(`Guardrail merge failed for agent '${agent.id}': using type defaults`);
					}
				}
			}
			return applyConfigOverrides(cfg);
		} catch (err) {
			if (err instanceof DuplicateAgentDirError) {
				deps.logger.error(err.message);
				throw err;
			}
			if (err?.code === "INVALID_CONFIG") return {};
			deps.logger.error(`Failed to read config at ${configPath}`, err);
			return {};
		}
	}
	async function readConfigFileSnapshotInternal() {
		maybeLoadDotEnvForConfig(deps.env);
		if (!deps.fs.existsSync(configPath)) {
			const hash = hashConfigRaw(null);
			return { snapshot: {
				path: configPath,
				exists: false,
				raw: null,
				parsed: {},
				resolved: {},
				valid: true,
				config: applyTalkApiKey(applyModelDefaults(applyCompactionDefaults(applyContextPruningDefaults(applyAgentDefaults(applySessionDefaults(applyMessageDefaults({}))))))),
				hash,
				issues: [],
				warnings: [],
				legacyIssues: []
			} };
		}
		try {
			const raw = deps.fs.readFileSync(configPath, "utf-8");
			const hash = hashConfigRaw(raw);
			const parsedRes = parseConfigJson5(raw, deps.json5);
			if (!parsedRes.ok) return { snapshot: {
				path: configPath,
				exists: true,
				raw,
				parsed: {},
				resolved: {},
				valid: false,
				config: {},
				hash,
				issues: [{
					path: "",
					message: `JSON5 parse failed: ${parsedRes.error}`
				}],
				warnings: [],
				legacyIssues: []
			} };
			let resolved;
			try {
				resolved = resolveConfigIncludesForRead(parsedRes.parsed, configPath, deps);
			} catch (err) {
				const message = err instanceof ConfigIncludeError ? err.message : `Include resolution failed: ${String(err)}`;
				return { snapshot: {
					path: configPath,
					exists: true,
					raw,
					parsed: parsedRes.parsed,
					resolved: coerceConfig(parsedRes.parsed),
					valid: false,
					config: coerceConfig(parsedRes.parsed),
					hash,
					issues: [{
						path: "",
						message
					}],
					warnings: [],
					legacyIssues: []
				} };
			}
			let readResolution;
			try {
				readResolution = resolveConfigForRead(resolved, deps.env);
			} catch (err) {
				const message = err instanceof MissingEnvVarError ? err.message : `Env var substitution failed: ${String(err)}`;
				return { snapshot: {
					path: configPath,
					exists: true,
					raw,
					parsed: parsedRes.parsed,
					resolved: coerceConfig(resolved),
					valid: false,
					config: coerceConfig(resolved),
					hash,
					issues: [{
						path: "",
						message
					}],
					warnings: [],
					legacyIssues: []
				} };
			}
			const resolvedConfigRaw = readResolution.resolvedConfigRaw;
			const legacyIssues = findLegacyConfigIssues(resolvedConfigRaw);
			const validated = validateConfigObjectWithPlugins(resolvedConfigRaw);
			if (!validated.ok) return { snapshot: {
				path: configPath,
				exists: true,
				raw,
				parsed: parsedRes.parsed,
				resolved: coerceConfig(resolvedConfigRaw),
				valid: false,
				config: coerceConfig(resolvedConfigRaw),
				hash,
				issues: validated.issues,
				warnings: validated.warnings,
				legacyIssues
			} };
			warnIfConfigFromFuture(validated.config, deps.logger);
			return {
				snapshot: {
					path: configPath,
					exists: true,
					raw,
					parsed: parsedRes.parsed,
					resolved: coerceConfig(resolvedConfigRaw),
					valid: true,
					config: applySnapshotDefaults(validated.config),
					hash,
					issues: [],
					warnings: validated.warnings,
					legacyIssues
				},
				envSnapshotForRestore: readResolution.envSnapshotForRestore
			};
		} catch (err) {
			return { snapshot: {
				path: configPath,
				exists: true,
				raw: null,
				parsed: {},
				resolved: {},
				valid: false,
				config: {},
				hash: hashConfigRaw(null),
				issues: [{
					path: "",
					message: `read failed: ${String(err)}`
				}],
				warnings: [],
				legacyIssues: []
			} };
		}
	}
	async function readConfigFileSnapshot() {
		return (await readConfigFileSnapshotInternal()).snapshot;
	}
	async function readConfigFileSnapshotForWrite() {
		const result = await readConfigFileSnapshotInternal();
		return {
			snapshot: result.snapshot,
			writeOptions: {
				envSnapshotForRestore: result.envSnapshotForRestore,
				expectedConfigPath: configPath
			}
		};
	}
	async function writeConfigFile(cfg, options = {}) {
		clearConfigCache();
		let persistCandidate = cfg;
		const { snapshot } = await readConfigFileSnapshotInternal();
		let envRefMap = null;
		let changedPaths = null;
		if (snapshot.valid && snapshot.exists) {
			const patch = createMergePatch(snapshot.config, cfg);
			persistCandidate = applyMergePatch(snapshot.resolved, patch);
			try {
				const resolvedIncludes = resolveConfigIncludes(snapshot.parsed, configPath, {
					readFile: (candidate) => deps.fs.readFileSync(candidate, "utf-8"),
					parseJson: (raw) => deps.json5.parse(raw)
				});
				const collected = /* @__PURE__ */ new Map();
				collectEnvRefPaths(resolvedIncludes, "", collected);
				if (collected.size > 0) {
					envRefMap = collected;
					changedPaths = /* @__PURE__ */ new Set();
					collectChangedPaths(snapshot.config, cfg, "", changedPaths);
				}
			} catch {
				envRefMap = null;
			}
		}
		if (persistCandidate && typeof persistCandidate === "object" && persistCandidate.agents) {
			const agents = persistCandidate.agents;
			if (Array.isArray(agents.list)) {
				for (const agent of agents.list) if (agent && typeof agent === "object") delete agent._effectiveGuardrails;
			}
		}
		const validated = validateConfigObjectRawWithPlugins(persistCandidate);
		if (!validated.ok) {
			const issue = validated.issues[0];
			const pathLabel = issue?.path ? issue.path : "<root>";
			throw new Error(`Config validation failed: ${pathLabel}: ${issue?.message ?? "invalid"}`);
		}
		if (validated.warnings.length > 0) {
			const newWarnings = validated.warnings.filter((warning) => {
				const line = `${warning.path || "<root>"}: ${warning.message}`;
				if (_warnedConfigDetails.has(line)) return false;
				_warnedConfigDetails.add(line);
				return true;
			});
			if (newWarnings.length > 0) {
				const details = newWarnings.map((warning) => `- ${warning.path || "<root>"}: ${warning.message}`).join("\n");
				deps.logger.warn(`Config warnings:\n${details}`);
			}
		}
		let cfgToWrite = validated.config;
		try {
			if (deps.fs.existsSync(configPath)) {
				const parsedRes = parseConfigJson5(await deps.fs.promises.readFile(configPath, "utf-8"), deps.json5);
				if (parsedRes.ok) {
					const envForRestore = options.envSnapshotForRestore ?? deps.env;
					cfgToWrite = restoreEnvVarRefs(cfgToWrite, parsedRes.parsed, envForRestore);
				}
			}
		} catch {}
		const dir = path.dirname(configPath);
		await deps.fs.promises.mkdir(dir, {
			recursive: true,
			mode: 448
		});
		const stampedOutputConfig = stampConfigVersion(envRefMap && changedPaths ? restoreEnvRefsFromMap(cfgToWrite, "", envRefMap, changedPaths) : cfgToWrite);
		let commentSource = null;
		try {
			if (deps.fs.existsSync(configPath)) commentSource = parseWithComments(await deps.fs.promises.readFile(configPath, "utf-8"));
		} catch {}
		const json = stringifyWithComments(commentSource ? transferComments(commentSource, stampedOutputConfig) : stampedOutputConfig, 2).trimEnd().concat("\n");
		const nextHash = hashConfigRaw(json);
		const previousHash = resolveConfigSnapshotHash(snapshot);
		const changedPathCount = changedPaths?.size;
		const previousBytes = typeof snapshot.raw === "string" ? Buffer.byteLength(snapshot.raw, "utf-8") : null;
		const nextBytes = Buffer.byteLength(json, "utf-8");
		const hasMetaBefore = hasConfigMeta(snapshot.parsed);
		const hasMetaAfter = hasConfigMeta(stampedOutputConfig);
		const gatewayModeBefore = resolveGatewayMode(snapshot.resolved);
		const gatewayModeAfter = resolveGatewayMode(stampedOutputConfig);
		const suspiciousReasons = resolveConfigWriteSuspiciousReasons({
			existsBefore: snapshot.exists,
			previousBytes,
			nextBytes,
			hasMetaBefore,
			gatewayModeBefore,
			gatewayModeAfter
		});
		const logConfigOverwrite = () => {
			if (!snapshot.exists) return;
			const isVitest = deps.env.VITEST === "true";
			const shouldLogInVitest = deps.env.OPENCLAW_TEST_CONFIG_OVERWRITE_LOG === "1";
			if (isVitest && !shouldLogInVitest) return;
			const changeSummary = typeof changedPathCount === "number" ? `, changedPaths=${changedPathCount}` : "";
			deps.logger.warn(`Config overwrite: ${configPath} (sha256 ${previousHash ?? "unknown"} -> ${nextHash}, backup=${configPath}.bak${changeSummary})`);
		};
		const logConfigWriteAnomalies = () => {
			if (suspiciousReasons.length === 0) return;
			const isVitest = deps.env.VITEST === "true";
			const shouldLogInVitest = deps.env.OPENCLAW_TEST_CONFIG_WRITE_ANOMALY_LOG === "1";
			if (isVitest && !shouldLogInVitest) return;
			deps.logger.warn(`Config write anomaly: ${configPath} (${suspiciousReasons.join(", ")})`);
		};
		const auditRecordBase = {
			ts: (/* @__PURE__ */ new Date()).toISOString(),
			source: "config-io",
			event: "config.write",
			configPath,
			pid: process.pid,
			ppid: process.ppid,
			cwd: process.cwd(),
			argv: process.argv.slice(0, 8),
			execArgv: process.execArgv.slice(0, 8),
			watchMode: deps.env.OPENCLAW_WATCH_MODE === "1",
			watchSession: typeof deps.env.OPENCLAW_WATCH_SESSION === "string" && deps.env.OPENCLAW_WATCH_SESSION.trim().length > 0 ? deps.env.OPENCLAW_WATCH_SESSION.trim() : null,
			watchCommand: typeof deps.env.OPENCLAW_WATCH_COMMAND === "string" && deps.env.OPENCLAW_WATCH_COMMAND.trim().length > 0 ? deps.env.OPENCLAW_WATCH_COMMAND.trim() : null,
			existsBefore: snapshot.exists,
			previousHash: previousHash ?? null,
			nextHash,
			previousBytes,
			nextBytes,
			changedPathCount: typeof changedPathCount === "number" ? changedPathCount : null,
			hasMetaBefore,
			hasMetaAfter,
			gatewayModeBefore,
			gatewayModeAfter,
			suspicious: suspiciousReasons
		};
		const appendWriteAudit = async (result, err) => {
			const errorCode = err && typeof err === "object" && "code" in err && typeof err.code === "string" ? err.code : void 0;
			const errorMessage = err && typeof err === "object" && "message" in err && typeof err.message === "string" ? err.message : void 0;
			await appendConfigWriteAuditRecord(deps, {
				...auditRecordBase,
				result,
				nextHash: result === "failed" ? null : auditRecordBase.nextHash,
				nextBytes: result === "failed" ? null : auditRecordBase.nextBytes,
				errorCode,
				errorMessage
			});
		};
		const tmp = path.join(dir, `${path.basename(configPath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
		try {
			await deps.fs.promises.writeFile(tmp, json, {
				encoding: "utf-8",
				mode: 384
			});
			if (deps.fs.existsSync(configPath)) {
				await rotateConfigBackups(configPath, deps.fs.promises);
				await deps.fs.promises.copyFile(configPath, `${configPath}.bak`).catch(() => {});
			}
			try {
				await deps.fs.promises.rename(tmp, configPath);
			} catch (err) {
				const code = err.code;
				if (code === "EPERM" || code === "EEXIST") {
					await deps.fs.promises.copyFile(tmp, configPath);
					await deps.fs.promises.chmod(configPath, 384).catch(() => {});
					await deps.fs.promises.unlink(tmp).catch(() => {});
					logConfigOverwrite();
					logConfigWriteAnomalies();
					await appendWriteAudit("copy-fallback");
					return;
				}
				await deps.fs.promises.unlink(tmp).catch(() => {});
				throw err;
			}
			logConfigOverwrite();
			logConfigWriteAnomalies();
			await appendWriteAudit("rename");
		} catch (err) {
			await appendWriteAudit("failed", err);
			throw err;
		}
	}
	return {
		configPath,
		loadConfig,
		readConfigFileSnapshot,
		readConfigFileSnapshotForWrite,
		writeConfigFile
	};
}
const DEFAULT_CONFIG_CACHE_MS = 200;
let configCache = null;
function resolveConfigCacheMs(env) {
	const raw = env.OPENCLAW_CONFIG_CACHE_MS?.trim();
	if (raw === "" || raw === "0") return 0;
	if (!raw) return DEFAULT_CONFIG_CACHE_MS;
	const parsed = Number.parseInt(raw, 10);
	if (!Number.isFinite(parsed)) return DEFAULT_CONFIG_CACHE_MS;
	return Math.max(0, parsed);
}
function shouldUseConfigCache(env) {
	if (env.OPENCLAW_DISABLE_CONFIG_CACHE?.trim()) return false;
	return resolveConfigCacheMs(env) > 0;
}
function clearConfigCache() {
	configCache = null;
}
/**
* Full normalization pipeline applied when building a config snapshot.
* Ensures consistent defaults so that diffConfigPaths doesn't see phantom diffs
* between a snapshot config and a freshly-validated config.
*/
function applySnapshotDefaults(cfg) {
	return normalizeConfigPaths(applyTalkApiKey(applyModelDefaults(applyAgentDefaults(applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(cfg)))))));
}
function loadConfig() {
	const io = createConfigIO();
	const configPath = io.configPath;
	const now = Date.now();
	if (shouldUseConfigCache(process.env)) {
		const cached = configCache;
		if (cached && cached.configPath === configPath && cached.expiresAt > now) return cached.config;
	}
	const config = io.loadConfig();
	if (shouldUseConfigCache(process.env)) {
		const cacheMs = resolveConfigCacheMs(process.env);
		if (cacheMs > 0) configCache = {
			configPath,
			expiresAt: now + cacheMs,
			config
		};
	}
	return config;
}
async function readConfigFileSnapshot() {
	return await createConfigIO().readConfigFileSnapshot();
}
async function readConfigFileSnapshotForWrite() {
	return await createConfigIO().readConfigFileSnapshotForWrite();
}
async function writeConfigFile(cfg, options = {}) {
	const io = createConfigIO();
	const sameConfigPath = options.expectedConfigPath === void 0 || options.expectedConfigPath === io.configPath;
	await io.writeConfigFile(cfg, { envSnapshotForRestore: sameConfigPath ? options.envSnapshotForRestore : void 0 });
}

//#endregion
//#region src/config/legacy-migrate.ts
function migrateLegacyConfig(raw) {
	const { next, changes } = applyLegacyMigrations(raw);
	if (!next) return {
		config: null,
		changes: []
	};
	const validated = validateConfigObjectWithPlugins(next);
	if (!validated.ok) {
		changes.push("Migration applied, but config still invalid; fix remaining issues manually.");
		return {
			config: null,
			changes
		};
	}
	return {
		config: validated.config,
		changes
	};
}

//#endregion
//#region src/config/config.ts
var config_exports = /* @__PURE__ */ __exportAll({
	CONFIG_PATH: () => CONFIG_PATH,
	DEFAULT_GATEWAY_PORT: () => DEFAULT_GATEWAY_PORT,
	STATE_DIR: () => STATE_DIR,
	applyConfigOverrides: () => applyConfigOverrides,
	getConfigOverrides: () => getConfigOverrides,
	isNixMode: () => isNixMode,
	loadConfig: () => loadConfig,
	resetConfigOverrides: () => resetConfigOverrides,
	resolveAgentConfigPath: () => resolveAgentConfigPath,
	resolveAgentConfigPathWithFallback: () => resolveAgentConfigPathWithFallback,
	resolveCanonicalConfigPath: () => resolveCanonicalConfigPath,
	resolveConfigPath: () => resolveConfigPath,
	resolveConfigPathCandidate: () => resolveConfigPathCandidate,
	resolveDefaultConfigCandidates: () => resolveDefaultConfigCandidates,
	resolveGatewayLockDir: () => resolveGatewayLockDir,
	resolveGatewayPort: () => resolveGatewayPort,
	resolveIsNixMode: () => resolveIsNixMode,
	resolveLegacyStateDirs: () => resolveLegacyStateDirs,
	resolveNewStateDir: () => resolveNewStateDir,
	resolveOAuthDir: () => resolveOAuthDir,
	resolveOAuthPath: () => resolveOAuthPath,
	resolveStateDir: () => resolveStateDir,
	setConfigOverride: () => setConfigOverride,
	unsetConfigOverride: () => unsetConfigOverride
});

//#endregion
export { SCOPE_PRESETS as A, collectConfigEnvVars as C, AGENT_TYPE_SCOPES as D, loadDotEnv as E, ALL_SCOPES as O, MAX_INCLUDE_DEPTH as S, resolveRuntimeServiceVersion as T, parseConfigPath as _, createConfigIO as a, applyMergePatch as b, readConfigFileSnapshot as c, writeConfigFile as d, getConfigOverrides as f, getConfigValueAtPath as g, unsetConfigOverride as h, clearConfigCache as i, DEFAULT_AGENT_TYPE as k, readConfigFileSnapshotForWrite as l, setConfigOverride as m, migrateLegacyConfig as n, loadConfig as o, resetConfigOverrides as p, applySnapshotDefaults as r, parseConfigJson5 as s, config_exports as t, resolveConfigSnapshotHash as u, setConfigValueAtPath as v, VERSION as w, INCLUDE_KEY as x, unsetConfigValueAtPath as y };