UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,364 lines 54.8 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js";
import { C as resolveExpiresAtMsFromDurationMs, j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { _ as uniqueStrings, l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import "./number-coercion-Z7n6tXLk.js";
import { m as resolveSecretInputRef } from "./types.secrets-_0JOMGE5.js";
import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js";
import { a as resolveSecretRefValue } from "./resolve-DPFnV1p0.js";
import { i as resolveManifestContractOwnerPluginId } from "./plugin-registry-ChcJPHWl.js";
import { t as assertExpectedResolvedSecretValue } from "./secret-value-DmVDGy1Y.js";
import { r as createResolverContext } from "./runtime-shared-DR5938Io.js";
import { i as setPathExistingStrict, n as getPath } from "./path-utils-BBmSOFBL.js";
import { r as discoverConfigSecretTargetsByIds } from "./target-registry-0k0oK6Ia.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import "./config-C9RxTsn1.js";
import { n as channelRouteDedupeKey } from "./channel-route-D7X5briz.js";
import { n as getLoadedChannelPlugin } from "./registry-MkxNn1Ue.js";
import { t as resolveChannelApprovalAdapter } from "./plugins-t2ejWcVy.js";
import { i as normalizeMessageChannel, t as isDeliverableMessageChannel } from "./message-channel-normalize-BLLf0ubu.js";
import "./message-channel-BiOeMu0l.js";
import { c as buildPluginApprovalRequestMessage, o as approvalDecisionLabel, s as buildPluginApprovalExpiredMessage, u as resolvePluginApprovalRequestAllowedDecisions } from "./plugin-approvals-CcsweryB.js";
import { j as resolveExecApprovalRequestAllowedDecisions } from "./exec-approvals-C6M6SGY3.js";
import { i as getActiveSecretsRuntimeEnv, o as getActiveSecretsRuntimeSnapshot } from "./runtime-state-B3MH_XLp.js";
import { n as collectCommandSecretAssignmentsFromSnapshot, t as analyzeCommandSecretAssignmentsFromSnapshot } from "./command-config-VqdSXtgm.js";
import { i as resolveBundledExplicitWebSearchProvidersFromPublicArtifacts } from "./web-provider-public-artifacts.explicit-BNgX1hwM.js";
import { t as resolveRuntimeWebTools } from "./runtime-web-tools-jt1BGljv.js";
import { t as matchesApprovalRequestFilters } from "./approval-request-filters-W2XYHus7.js";
import { d as formatExecApprovalExpiresIn } from "./exec-approval-reply-BXMsffTv.js";
import { i as sanitizeExecApprovalWarningText, t as resolveExecApprovalCommandDisplay } from "./exec-approval-command-display-BYD_3EWn.js";
import { i as buildPluginApprovalResolvedReplyPayload, n as buildApprovalResolvedReplyPayload, r as buildPluginApprovalPendingReplyPayload, t as buildApprovalPendingReplyPayload } from "./approval-renderers-Cf0JuAfU.js";
import { l as roleScopesAllow } from "./pairing-token-BEUQW6ez.js";
import { l as listDevicePairing, s as hasEffectivePairedDeviceRole } from "./device-pairing-Bcmp5CuV.js";
import { a as diffConfigPaths, t as buildGatewayReloadPlan } from "./config-reload-plan-D2TNMpG_.js";
import { d as shouldClearStoredApnsRegistration, f as resolveApnsRelayConfigFromEnv, l as sendApnsExecApprovalAlert, o as resolveApnsAuthConfigFromEnv, r as loadApnsRegistrations, t as clearApnsRegistrationIfCurrent, u as sendApnsExecApprovalResolvedWake } from "./push-apns-2937xPaj.js";
import { a as setCurrentSharedGatewaySessionGeneration, n as disconnectStaleSharedGatewayAuthClients } from "./server-shared-auth-generation-kMfFmuwl.js";
import { t as GATEWAY_AUX_METHODS } from "./server-aux-methods-oZh-aSQp.js";
import { randomUUID } from "node:crypto";
//#region src/infra/exec-approval-forwarder.ts
const log = createSubsystemLogger("gateway/exec-approvals");
const DEFAULT_MODE = "session";
const SYNTHETIC_APPROVAL_REQUEST_ID = "__approval-routing__";
let execApprovalForwarderRuntimePromise = null;
function loadExecApprovalForwarderRuntime() {
	execApprovalForwarderRuntimePromise ??= import("./exec-approval-forwarder.runtime.js");
	return execApprovalForwarderRuntimePromise;
}
function normalizeMode(mode) {
	return mode ?? DEFAULT_MODE;
}
function shouldForwardRoute(params) {
	const config = params.config;
	if (!config?.enabled) return false;
	return matchesApprovalRequestFilters({
		request: params.routeRequest,
		agentFilter: config.agentFilter,
		sessionFilter: config.sessionFilter,
		fallbackAgentIdFromSessionKey: true
	});
}
function buildTargetKey(target) {
	return channelRouteDedupeKey({
		channel: normalizeMessageChannel(target.channel) ?? target.channel,
		to: target.to,
		accountId: target.accountId,
		threadId: target.threadId
	});
}
function buildSyntheticApprovalRequest(routeRequest) {
	return {
		id: SYNTHETIC_APPROVAL_REQUEST_ID,
		request: {
			command: "",
			agentId: routeRequest.agentId ?? null,
			sessionKey: routeRequest.sessionKey ?? null,
			turnSourceChannel: routeRequest.turnSourceChannel ?? null,
			turnSourceTo: routeRequest.turnSourceTo ?? null,
			turnSourceAccountId: routeRequest.turnSourceAccountId ?? null,
			turnSourceThreadId: routeRequest.turnSourceThreadId ?? null
		},
		createdAtMs: 0,
		expiresAtMs: 0
	};
}
function shouldSkipForwardingFallback(params) {
	const channel = normalizeMessageChannel(params.target.channel) ?? params.target.channel;
	if (!channel) return false;
	return resolveChannelApprovalAdapter(getLoadedChannelPlugin(channel))?.delivery?.shouldSuppressForwardingFallback?.({
		cfg: params.cfg,
		approvalKind: params.approvalKind,
		target: params.target,
		request: buildSyntheticApprovalRequest(params.routeRequest)
	}) ?? false;
}
function formatApprovalCommand(command) {
	if (!command.includes("\n") && !command.includes("`")) return {
		inline: true,
		text: `\`${command}\``
	};
	let fence = "```";
	while (command.includes(fence)) fence += "`";
	return {
		inline: false,
		text: `${fence}\n${command}\n${fence}`
	};
}
function buildExecApprovalRequestMessage(request, nowMs) {
	const allowedDecisions = resolveExecApprovalRequestAllowedDecisions(request.request);
	const decisionText = allowedDecisions.join("|");
	const lines = ["🔒 Exec approval required", `ID: ${request.id}`];
	const warningText = request.request.warningText?.trim();
	if (warningText) lines.push("", warningText);
	const analysisWarningLines = normalizeStringEntries(request.request.commandAnalysis?.warningLines.map(sanitizeExecApprovalWarningText)).slice(0, 5);
	if (analysisWarningLines && analysisWarningLines.length > 0) {
		lines.push("", "Command analysis:");
		for (const line of analysisWarningLines) lines.push(`- ${line}`);
	}
	const command = formatApprovalCommand(resolveExecApprovalCommandDisplay(request.request).commandText);
	if (command.inline) lines.push(`Command: ${command.text}`);
	else {
		lines.push("Command:");
		lines.push(command.text);
	}
	if (request.request.cwd) lines.push(`CWD: ${request.request.cwd}`);
	if (request.request.nodeId) lines.push(`Node: ${request.request.nodeId}`);
	if (Array.isArray(request.request.envKeys) && request.request.envKeys.length > 0) lines.push(`Env overrides: ${request.request.envKeys.join(", ")}`);
	if (request.request.host) lines.push(`Host: ${request.request.host}`);
	if (request.request.agentId) lines.push(`Agent: ${request.request.agentId}`);
	if (request.request.security) lines.push(`Security: ${request.request.security}`);
	if (request.request.ask) lines.push(`Ask: ${request.request.ask}`);
	lines.push(`Expires in: ${formatExecApprovalExpiresIn(request.expiresAtMs, nowMs)}`);
	lines.push("Mode: foreground (interactive approvals available in this chat).");
	lines.push(allowedDecisions.includes("allow-always") ? "Background mode note: non-interactive runs cannot wait for chat approvals; use pre-approved policy (allow-always or ask=off)." : "Background mode note: non-interactive runs cannot wait for chat approvals; the effective policy still requires per-run approval unless ask=off.");
	lines.push(`Reply with: /approve ${request.id} ${decisionText}`);
	if (!allowedDecisions.includes("allow-always")) lines.push("Allow Always is unavailable because the effective policy requires approval every time.");
	return lines.join("\n");
}
const decisionLabel = approvalDecisionLabel;
function buildResolvedMessage(resolved) {
	return `${`✅ Exec approval ${decisionLabel(resolved.decision)}.`}${resolved.resolvedBy ? ` Resolved by ${resolved.resolvedBy}.` : ""} ID: ${resolved.id}`;
}
function buildExpiredMessage(request) {
	return `⏱️ Exec approval expired. ID: ${request.id}`;
}
function normalizeTurnSourceChannel(value) {
	const normalized = value ? normalizeMessageChannel(value) : void 0;
	return normalized && isDeliverableMessageChannel(normalized) ? normalized : void 0;
}
function extractApprovalRouteRequest(request) {
	if (!request) return null;
	return {
		agentId: request.agentId ?? null,
		sessionKey: request.sessionKey ?? null,
		turnSourceChannel: request.turnSourceChannel ?? null,
		turnSourceTo: request.turnSourceTo ?? null,
		turnSourceAccountId: request.turnSourceAccountId ?? null,
		turnSourceThreadId: request.turnSourceThreadId ?? null
	};
}
function defaultResolveSessionTarget(params) {
	return loadExecApprovalForwarderRuntime().then(({ resolveExecApprovalSessionTarget }) => {
		const resolvedTarget = resolveExecApprovalSessionTarget({
			cfg: params.cfg,
			request: params.request,
			turnSourceChannel: normalizeTurnSourceChannel(params.request.request.turnSourceChannel),
			turnSourceTo: normalizeOptionalString(params.request.request.turnSourceTo),
			turnSourceAccountId: normalizeOptionalString(params.request.request.turnSourceAccountId),
			turnSourceThreadId: params.request.request.turnSourceThreadId ?? void 0
		});
		if (!resolvedTarget?.channel || !resolvedTarget.to) return null;
		const channel = resolvedTarget.channel;
		if (!isDeliverableMessageChannel(channel)) return null;
		return {
			channel,
			to: resolvedTarget.to,
			accountId: resolvedTarget.accountId,
			threadId: resolvedTarget.threadId
		};
	});
}
async function deliverToTargets(params) {
	const deliveries = params.targets.map(async (target) => {
		if (params.shouldSend && !params.shouldSend()) return;
		const channel = normalizeMessageChannel(target.channel) ?? target.channel;
		if (!isDeliverableMessageChannel(channel)) return;
		try {
			const payload = params.buildPayload(target);
			await params.beforeDeliver?.(target, payload);
			const send = await params.deliver({
				cfg: params.cfg,
				channel,
				to: target.to,
				accountId: target.accountId,
				threadId: target.threadId,
				payloads: [payload]
			});
			if (send.status === "failed" || send.status === "partial_failed") throw send.error;
		} catch (err) {
			log.error(`exec approvals: failed to deliver to ${channel}:${target.to}: ${String(err)}`);
		}
	});
	await Promise.allSettled(deliveries);
}
function buildApprovalRenderPayload(params) {
	const channel = normalizeMessageChannel(params.target.channel) ?? params.target.channel;
	return (channel ? params.resolveRenderer(resolveChannelApprovalAdapter(getLoadedChannelPlugin(channel)))?.(params.renderParams) : null) ?? params.buildFallback();
}
function buildExecPendingPayload(params) {
	return buildApprovalRenderPayload({
		target: params.target,
		renderParams: params,
		resolveRenderer: (adapter) => adapter?.render?.exec?.buildPendingPayload,
		buildFallback: () => buildApprovalPendingReplyPayload({
			approvalId: params.request.id,
			approvalSlug: params.request.id.slice(0, 8),
			text: buildExecApprovalRequestMessage(params.request, params.nowMs),
			agentId: params.request.request.agentId ?? null,
			allowedDecisions: resolveExecApprovalRequestAllowedDecisions(params.request.request),
			sessionKey: params.request.request.sessionKey ?? null
		})
	});
}
function buildExecResolvedPayload(params) {
	return buildApprovalRenderPayload({
		target: params.target,
		renderParams: params,
		resolveRenderer: (adapter) => adapter?.render?.exec?.buildResolvedPayload,
		buildFallback: () => buildApprovalResolvedReplyPayload({
			approvalId: params.resolved.id,
			approvalSlug: params.resolved.id.slice(0, 8),
			text: buildResolvedMessage(params.resolved)
		})
	});
}
function buildPluginPendingPayload(params) {
	return buildApprovalRenderPayload({
		target: params.target,
		renderParams: params,
		resolveRenderer: (adapter) => adapter?.render?.plugin?.buildPendingPayload,
		buildFallback: () => buildPluginApprovalPendingReplyPayload({
			request: params.request,
			nowMs: params.nowMs,
			text: buildPluginApprovalRequestMessage(params.request, params.nowMs),
			allowedDecisions: resolvePluginApprovalRequestAllowedDecisions(params.request.request)
		})
	});
}
function buildPluginResolvedPayload(params) {
	return buildApprovalRenderPayload({
		target: params.target,
		renderParams: params,
		resolveRenderer: (adapter) => adapter?.render?.plugin?.buildResolvedPayload,
		buildFallback: () => buildPluginApprovalResolvedReplyPayload({ resolved: params.resolved })
	});
}
async function resolveForwardTargets(params) {
	const mode = normalizeMode(params.config?.mode);
	const targets = [];
	const seen = /* @__PURE__ */ new Set();
	if (mode === "session" || mode === "both") {
		const sessionTarget = await params.resolveSessionTarget({
			cfg: params.cfg,
			request: buildSyntheticApprovalRequest(params.routeRequest)
		});
		if (sessionTarget) {
			const key = buildTargetKey(sessionTarget);
			if (!seen.has(key)) {
				seen.add(key);
				targets.push({
					...sessionTarget,
					source: "session"
				});
			}
		}
	}
	if (mode === "targets" || mode === "both") {
		const explicitTargets = params.config?.targets ?? [];
		for (const target of explicitTargets) {
			const key = buildTargetKey(target);
			if (seen.has(key)) continue;
			seen.add(key);
			targets.push({
				...target,
				source: "target"
			});
		}
	}
	return targets;
}
function createApprovalHandlers(params) {
	const pending = /* @__PURE__ */ new Map();
	const handleRequested = async (request) => {
		const cfg = params.getConfig();
		const config = params.strategy.config(cfg);
		const requestId = params.strategy.getRequestId(request);
		const routeRequest = params.strategy.getRouteRequestFromRequest(request);
		const filteredTargets = [...shouldForwardRoute({
			config,
			routeRequest
		}) ? await resolveForwardTargets({
			cfg,
			config,
			routeRequest,
			resolveSessionTarget: params.resolveSessionTarget
		}) : []].filter((target) => !shouldSkipForwardingFallback({
			approvalKind: params.strategy.kind,
			target,
			cfg,
			routeRequest
		}));
		if (filteredTargets.length === 0) return false;
		const expiresInMs = Math.max(0, params.strategy.getExpiresAtMs(request) - params.nowMs());
		const timeoutId = setTimeout(() => {
			(async () => {
				const entry = pending.get(requestId);
				if (!entry) return;
				pending.delete(requestId);
				await deliverToTargets({
					cfg,
					targets: entry.targets,
					buildPayload: () => ({ text: params.strategy.buildExpiredText(request) }),
					deliver: params.deliver
				});
			})().catch((err) => {
				log.error(`${params.strategy.kind} approvals: failed to deliver expiry notification for ${requestId}: ${String(err)}`);
			});
		}, expiresInMs);
		timeoutId.unref?.();
		const pendingEntry = {
			routeRequest,
			targets: filteredTargets,
			timeoutId
		};
		pending.set(requestId, pendingEntry);
		if (pending.get(requestId) !== pendingEntry) return false;
		deliverToTargets({
			cfg,
			targets: filteredTargets,
			buildPayload: (target) => params.strategy.buildPendingPayload({
				cfg,
				request,
				target,
				routeRequest,
				nowMs: params.nowMs()
			}),
			beforeDeliver: async (target, payload) => {
				const channel = normalizeMessageChannel(target.channel) ?? target.channel;
				if (!channel) return;
				await getLoadedChannelPlugin(channel)?.outbound?.beforeDeliverPayload?.({
					cfg,
					target,
					payload,
					hint: {
						kind: "approval-pending",
						approvalKind: params.strategy.kind
					}
				});
			},
			deliver: params.deliver,
			shouldSend: () => pending.get(requestId) === pendingEntry
		}).catch((err) => {
			log.error(`${params.strategy.kind} approvals: failed to deliver request ${requestId}: ${String(err)}`);
		});
		return true;
	};
	const handleResolved = async (resolved) => {
		const resolvedId = params.strategy.getResolvedId(resolved);
		const entry = pending.get(resolvedId);
		if (entry?.timeoutId) clearTimeout(entry.timeoutId);
		if (entry) pending.delete(resolvedId);
		const cfg = params.getConfig();
		let targets = entry?.targets;
		if (!targets) {
			const routeRequest = params.strategy.getRouteRequestFromResolved(resolved);
			if (routeRequest) {
				const config = params.strategy.config(cfg);
				targets = [...shouldForwardRoute({
					config,
					routeRequest
				}) ? await resolveForwardTargets({
					cfg,
					config,
					routeRequest,
					resolveSessionTarget: params.resolveSessionTarget
				}) : []].filter((target) => !shouldSkipForwardingFallback({
					approvalKind: params.strategy.kind,
					target,
					cfg,
					routeRequest
				}));
			}
		}
		if (!targets?.length) return;
		await deliverToTargets({
			cfg,
			targets,
			buildPayload: (target) => params.strategy.buildResolvedPayload({
				cfg,
				resolved,
				target,
				routeRequest: entry?.routeRequest ?? params.strategy.getRouteRequestFromResolved(resolved) ?? {}
			}),
			deliver: params.deliver
		});
	};
	const stop = () => {
		for (const entry of pending.values()) if (entry.timeoutId) clearTimeout(entry.timeoutId);
		pending.clear();
	};
	return {
		handleRequested,
		handleResolved,
		stop
	};
}
function createApprovalStrategy(params) {
	return {
		kind: params.kind,
		config: params.config,
		getRequestId: (request) => request.id,
		getResolvedId: (resolved) => resolved.id,
		getExpiresAtMs: (request) => request.expiresAtMs,
		getRouteRequestFromRequest: (request) => extractApprovalRouteRequest(request.request) ?? {},
		getRouteRequestFromResolved: (resolved) => extractApprovalRouteRequest(resolved.request),
		buildExpiredText: params.buildExpiredText,
		buildPendingPayload: params.buildPendingPayload,
		buildResolvedPayload: params.buildResolvedPayload
	};
}
const execApprovalStrategy = createApprovalStrategy({
	kind: "exec",
	config: (cfg) => cfg.approvals?.exec,
	buildExpiredText: buildExpiredMessage,
	buildPendingPayload: ({ cfg, request, target, nowMs }) => buildExecPendingPayload({
		cfg,
		request,
		target,
		nowMs
	}),
	buildResolvedPayload: ({ cfg, resolved, target }) => buildExecResolvedPayload({
		cfg,
		resolved,
		target
	})
});
const pluginApprovalStrategy = createApprovalStrategy({
	kind: "plugin",
	config: (cfg) => cfg.approvals?.plugin,
	buildExpiredText: buildPluginApprovalExpiredMessage,
	buildPendingPayload: ({ cfg, request, target, nowMs }) => buildPluginPendingPayload({
		cfg,
		request,
		target,
		nowMs
	}),
	buildResolvedPayload: ({ cfg, resolved, target }) => buildPluginResolvedPayload({
		cfg,
		resolved,
		target
	})
});
function createExecApprovalForwarder(deps = {}) {
	const getConfig = deps.getConfig ?? getRuntimeConfig;
	const deliver = deps.deliver ?? (async (params) => {
		const { sendDurableMessageBatch } = await loadExecApprovalForwarderRuntime();
		return sendDurableMessageBatch(params);
	});
	const nowMs = deps.nowMs ?? Date.now;
	const resolveSessionTarget = deps.resolveSessionTarget ?? defaultResolveSessionTarget;
	const execHandlers = createApprovalHandlers({
		strategy: execApprovalStrategy,
		getConfig,
		deliver,
		nowMs,
		resolveSessionTarget
	});
	const pluginHandlers = createApprovalHandlers({
		strategy: pluginApprovalStrategy,
		getConfig,
		deliver,
		nowMs,
		resolveSessionTarget
	});
	return {
		handleRequested: execHandlers.handleRequested,
		handleResolved: execHandlers.handleResolved,
		handlePluginApprovalRequested: pluginHandlers.handleRequested,
		handlePluginApprovalResolved: pluginHandlers.handleResolved,
		stop: () => {
			execHandlers.stop();
			pluginHandlers.stop();
		}
	};
}
//#endregion
//#region src/secrets/runtime-command-secrets.ts
/** Resolves command-scoped secrets, including web provider override credentials. */
function hasProviderOverrides(overrides) {
	return normalizeOptionalString(overrides?.webSearch) !== void 0 || normalizeOptionalString(overrides?.webFetch) !== void 0;
}
function applyProviderOverridesToConfig(config, overrides) {
	if (!hasProviderOverrides(overrides)) return config;
	const next = structuredClone(config);
	const tools = next.tools ??= {};
	const web = tools.web ??= {};
	const webSearch = normalizeOptionalString(overrides?.webSearch);
	if (webSearch) {
		const search = web.search ??= {};
		search.provider = webSearch;
	}
	const webFetch = normalizeOptionalString(overrides?.webFetch);
	if (webFetch) {
		const fetch = web.fetch ??= {};
		fetch.provider = webFetch;
	}
	return next;
}
function pluginIdFromRuntimeWebPath(path) {
	return /^plugins\.entries\.([^.]+)\.config\.(webSearch|webFetch)\.apiKey$/.exec(path)?.[1];
}
function searchProviderFromDirectWebPath(path) {
	return /^tools\.web\.search\.([^.]+)\.apiKey$/.exec(path)?.[1];
}
function fetchProviderFromDirectWebPath(path) {
	return /^tools\.web\.fetch\.([^.]+)\.apiKey$/.exec(path)?.[1];
}
function isWebCommandSecretPath(path) {
	return path === "tools.web.search.apiKey" || /^tools\.web\.(search|fetch)\.[^.]+\.apiKey$/.test(path) || /^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(path);
}
function webSearchProviderUsesSharedSearchCredential(params) {
	const sentinel = "__openclaw_shared_web_search_probe__";
	const pluginId = resolveManifestContractOwnerPluginId({
		contract: "webSearchProviders",
		value: params.provider,
		origin: "bundled",
		config: params.config
	});
	if (!pluginId) return false;
	const provider = resolveBundledExplicitWebSearchProvidersFromPublicArtifacts({ onlyPluginIds: [pluginId] })?.find((entry) => entry.id === params.provider);
	return provider?.credentialPath === "tools.web.search.apiKey" || provider?.getCredentialValue({ apiKey: sentinel }) === sentinel || provider?.getConfiguredCredentialFallback?.(params.config)?.path === "tools.web.search.apiKey";
}
function isProviderOverridePath(params) {
	const webSearch = normalizeOptionalString(params.providerOverrides?.webSearch);
	if (webSearch) {
		if (params.config.tools?.web?.search?.enabled === false) return false;
		if (params.path === "tools.web.search.apiKey") return webSearchProviderUsesSharedSearchCredential({
			config: params.config,
			provider: webSearch
		});
		const directProvider = searchProviderFromDirectWebPath(params.path);
		if (directProvider) return directProvider === webSearch;
		const pluginId = pluginIdFromRuntimeWebPath(params.path);
		if (pluginId && params.path.endsWith(".config.webSearch.apiKey")) return resolveManifestContractOwnerPluginId({
			contract: "webSearchProviders",
			value: webSearch,
			origin: "bundled",
			config: params.config
		}) === pluginId;
	}
	const webFetch = normalizeOptionalString(params.providerOverrides?.webFetch);
	if (webFetch) {
		if (params.config.tools?.web?.fetch?.enabled === false) return false;
		const directProvider = fetchProviderFromDirectWebPath(params.path);
		if (directProvider) return directProvider === webFetch;
		const pluginId = pluginIdFromRuntimeWebPath(params.path);
		if (pluginId && params.path.endsWith(".config.webFetch.apiKey")) return resolveManifestContractOwnerPluginId({
			contract: "webFetchProviders",
			value: webFetch,
			origin: "bundled",
			config: params.config
		}) === pluginId;
	}
	return false;
}
function restoreInactiveWebCommandSecretTargets(params) {
	if (!hasProviderOverrides(params.providerOverrides)) return params.inactiveRefPaths;
	const inactive = new Set(params.inactiveRefPaths);
	const defaults = params.sourceConfig.secrets?.defaults;
	for (const target of discoverConfigSecretTargetsByIds(params.sourceConfig, params.targetIds)) {
		if (params.allowedPaths && !params.allowedPaths.has(target.path)) continue;
		if (!isWebCommandSecretPath(target.path)) continue;
		const { ref } = resolveSecretInputRef({
			value: target.value,
			refValue: target.refValue,
			defaults
		});
		if (!ref) continue;
		if (params.forcedActivePaths?.has(target.path) || params.optionalActivePaths?.has(target.path)) continue;
		if (isProviderOverridePath({
			config: params.sourceConfig,
			path: target.path,
			providerOverrides: params.providerOverrides
		})) continue;
		inactive.add(target.path);
		setPathExistingStrict(params.resolvedConfig, target.pathSegments, target.value);
	}
	return [...inactive];
}
function filterInactiveRefPaths(params) {
	return params.inactiveRefPaths.filter((path) => {
		if (params.allowedPaths && !params.allowedPaths.has(path)) return false;
		if (params.forcedActivePaths?.has(path) || params.optionalActivePaths?.has(path)) return false;
		if (!hasProviderOverrides(params.providerOverrides)) return true;
		return !isProviderOverridePath({
			config: params.config,
			path,
			providerOverrides: params.providerOverrides
		});
	});
}
function mirrorResolvedProviderCredentialToDirectPath(params) {
	const provider = normalizeOptionalString(params.provider);
	if (!provider) return;
	const pluginId = resolveManifestContractOwnerPluginId({
		contract: params.contract,
		value: provider,
		origin: "bundled",
		config: params.config
	});
	if (!pluginId) return;
	const directSegments = [
		...params.directPathPrefix.split("."),
		provider,
		"apiKey"
	];
	if (getPath(params.config, directSegments) === void 0) return;
	const resolvedValue = getPath(params.resolvedConfig, [
		"plugins",
		"entries",
		pluginId,
		"config",
		params.pluginConfigKey,
		"apiKey"
	]);
	if (typeof resolvedValue !== "string" || resolvedValue.length === 0) return;
	setPathExistingStrict(params.resolvedConfig, directSegments, resolvedValue);
}
function mirrorResolvedProviderCredentialToDirectPaths(params) {
	const configuredSearchProvider = normalizeOptionalString(params.providerOverrides?.webSearch) ?? normalizeOptionalString(params.config.tools?.web?.search?.provider);
	const configuredFetchProvider = normalizeOptionalString(params.providerOverrides?.webFetch) ?? normalizeOptionalString(params.config.tools?.web?.fetch?.provider);
	mirrorResolvedProviderCredentialToDirectPath({
		config: params.config,
		resolvedConfig: params.resolvedConfig,
		contract: "webSearchProviders",
		provider: configuredSearchProvider,
		directPathPrefix: "tools.web.search",
		pluginConfigKey: "webSearch"
	});
	mirrorResolvedProviderCredentialToDirectPath({
		config: params.config,
		resolvedConfig: params.resolvedConfig,
		contract: "webFetchProviders",
		provider: configuredFetchProvider,
		directPathPrefix: "tools.web.fetch",
		pluginConfigKey: "webFetch"
	});
	const webSearch = configuredSearchProvider;
	if (webSearch && webSearchProviderUsesSharedSearchCredential({
		config: params.config,
		provider: webSearch
	}) && getPath(params.config, [
		"tools",
		"web",
		"search",
		"apiKey"
	]) !== void 0) {
		const pluginId = resolveManifestContractOwnerPluginId({
			contract: "webSearchProviders",
			value: webSearch,
			origin: "bundled",
			config: params.config
		});
		const resolvedValue = pluginId ? getPath(params.resolvedConfig, [
			"plugins",
			"entries",
			pluginId,
			"config",
			"webSearch",
			"apiKey"
		]) : void 0;
		if (typeof resolvedValue === "string" && resolvedValue.length > 0) setPathExistingStrict(params.resolvedConfig, [
			"tools",
			"web",
			"search",
			"apiKey"
		], resolvedValue);
	}
}
async function resolveForcedActiveCommandSecretTargets(params) {
	const activePaths = new Set([...params.forcedActivePaths ?? [], ...params.optionalActivePaths ?? []]);
	if (activePaths.size === 0) return;
	const context = createResolverContext({
		sourceConfig: params.sourceConfig,
		env: getActiveSecretsRuntimeEnv()
	});
	const defaults = params.sourceConfig.secrets?.defaults;
	for (const target of discoverConfigSecretTargetsByIds(params.sourceConfig, params.targetIds)) {
		if (params.allowedPaths && !params.allowedPaths.has(target.path)) continue;
		if (!activePaths.has(target.path)) continue;
		const { ref } = resolveSecretInputRef({
			value: target.value,
			refValue: target.refValue,
			defaults
		});
		if (!ref) continue;
		try {
			const resolved = await resolveSecretRefValue(ref, {
				config: params.sourceConfig,
				env: context.env,
				cache: context.cache
			});
			assertExpectedResolvedSecretValue({
				value: resolved,
				expected: target.entry.expectedResolvedValue,
				errorMessage: target.entry.expectedResolvedValue === "string" ? `${target.path} resolved to a non-string or empty value.` : `${target.path} resolved to an unsupported value type.`
			});
			setPathExistingStrict(params.resolvedConfig, target.pathSegments, resolved);
		} catch {}
	}
}
/**
* Resolves command-scoped SecretRef assignments from the active runtime snapshot.
* Provider overrides are evaluated against cloned snapshot config.
*/
/** Resolves command secret assignments from the active prepared runtime snapshot. */
function resolveCommandSecretsFromActiveRuntimeSnapshot(params) {
	const activeSnapshot = getActiveSecretsRuntimeSnapshot();
	if (!activeSnapshot) throw new Error("Secrets runtime snapshot is not active.");
	if (params.targetIds.size === 0) return Promise.resolve({
		assignments: [],
		diagnostics: [],
		inactiveRefPaths: []
	});
	return resolveCommandSecretsFromSnapshot({
		activeSnapshot,
		commandName: params.commandName,
		targetIds: params.targetIds,
		allowedPaths: params.allowedPaths,
		forcedActivePaths: params.forcedActivePaths,
		optionalActivePaths: params.optionalActivePaths,
		providerOverrides: params.providerOverrides
	});
}
async function resolveCommandSecretsFromSnapshot(params) {
	const hasOverrides = hasProviderOverrides(params.providerOverrides);
	const sourceConfig = applyProviderOverridesToConfig(params.activeSnapshot.sourceConfig, params.providerOverrides);
	const resolvedConfig = applyProviderOverridesToConfig(params.activeSnapshot.config, params.providerOverrides);
	const context = hasOverrides ? createResolverContext({
		sourceConfig,
		env: getActiveSecretsRuntimeEnv()
	}) : void 0;
	if (context) await resolveRuntimeWebTools({
		sourceConfig,
		resolvedConfig,
		context
	});
	mirrorResolvedProviderCredentialToDirectPaths({
		config: sourceConfig,
		resolvedConfig,
		providerOverrides: params.providerOverrides
	});
	await resolveForcedActiveCommandSecretTargets({
		sourceConfig,
		resolvedConfig,
		targetIds: params.targetIds,
		allowedPaths: params.allowedPaths,
		forcedActivePaths: params.forcedActivePaths,
		optionalActivePaths: params.optionalActivePaths
	});
	const warningSource = context?.warnings ?? params.activeSnapshot.warnings;
	let inactiveRefPaths = filterInactiveRefPaths({
		config: sourceConfig,
		providerOverrides: params.providerOverrides,
		allowedPaths: params.allowedPaths,
		forcedActivePaths: params.forcedActivePaths,
		optionalActivePaths: params.optionalActivePaths,
		inactiveRefPaths: [...new Set(warningSource.filter((warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE").map((warning) => warning.path))]
	});
	inactiveRefPaths = restoreInactiveWebCommandSecretTargets({
		sourceConfig,
		resolvedConfig,
		targetIds: params.targetIds,
		inactiveRefPaths,
		providerOverrides: params.providerOverrides,
		allowedPaths: params.allowedPaths,
		forcedActivePaths: params.forcedActivePaths,
		optionalActivePaths: params.optionalActivePaths
	});
	let analyzed = analyzeCommandSecretAssignmentsFromSnapshot({
		sourceConfig,
		resolvedConfig,
		targetIds: params.targetIds,
		inactiveRefPaths: new Set(inactiveRefPaths),
		...params.allowedPaths ? { allowedPaths: params.allowedPaths } : {}
	});
	if (hasOverrides) {
		const impliedInactivePaths = analyzed.unresolved.filter((entry) => isWebCommandSecretPath(entry.path)).filter((entry) => !isProviderOverridePath({
			config: sourceConfig,
			path: entry.path,
			providerOverrides: params.providerOverrides
		})).map((entry) => entry.path);
		if (impliedInactivePaths.length > 0) {
			inactiveRefPaths = uniqueStrings([...inactiveRefPaths, ...impliedInactivePaths]);
			analyzed = analyzeCommandSecretAssignmentsFromSnapshot({
				sourceConfig,
				resolvedConfig,
				targetIds: params.targetIds,
				inactiveRefPaths: new Set(inactiveRefPaths),
				...params.allowedPaths ? { allowedPaths: params.allowedPaths } : {}
			});
		}
	}
	const optionalActiveUnresolvedPaths = analyzed.unresolved.filter((entry) => params.optionalActivePaths?.has(entry.path)).map((entry) => entry.path);
	if (optionalActiveUnresolvedPaths.length > 0) {
		inactiveRefPaths = uniqueStrings([...inactiveRefPaths, ...optionalActiveUnresolvedPaths]);
		analyzed = analyzeCommandSecretAssignmentsFromSnapshot({
			sourceConfig,
			resolvedConfig,
			targetIds: params.targetIds,
			inactiveRefPaths: new Set(inactiveRefPaths),
			...params.allowedPaths ? { allowedPaths: params.allowedPaths } : {}
		});
	}
	const selectedProviderUnresolved = analyzed.unresolved.filter((entry) => isProviderOverridePath({
		config: sourceConfig,
		path: entry.path,
		providerOverrides: params.providerOverrides
	}));
	const forcedActiveUnresolved = analyzed.unresolved.filter((entry) => params.forcedActivePaths?.has(entry.path));
	if (selectedProviderUnresolved.length > 0 || forcedActiveUnresolved.length > 0) return {
		assignments: analyzed.assignments,
		diagnostics: analyzed.diagnostics,
		inactiveRefPaths
	};
	const resolved = collectCommandSecretAssignmentsFromSnapshot({
		sourceConfig,
		resolvedConfig,
		commandName: params.commandName,
		targetIds: params.targetIds,
		inactiveRefPaths: new Set(inactiveRefPaths),
		...params.allowedPaths ? { allowedPaths: params.allowedPaths } : {}
	});
	return {
		assignments: resolved.assignments,
		diagnostics: resolved.diagnostics,
		inactiveRefPaths
	};
}
//#endregion
//#region src/gateway/exec-approval-ios-push.ts
const APPROVALS_SCOPE = "operator.approvals";
const OPERATOR_ROLE = "operator";
function isIosPlatform(platform) {
	const normalized = normalizeOptionalLowercaseString(platform) ?? "";
	return normalized.startsWith("ios") || normalized.startsWith("ipados");
}
function resolveActiveOperatorToken(device) {
	const operatorToken = device.tokens?.[OPERATOR_ROLE];
	if (!operatorToken || operatorToken.revokedAtMs) return null;
	return operatorToken;
}
function canApproveExecRequests(device) {
	const operatorToken = resolveActiveOperatorToken(device);
	if (!operatorToken) return false;
	return roleScopesAllow({
		role: OPERATOR_ROLE,
		requestedScopes: [APPROVALS_SCOPE],
		allowedScopes: operatorToken.scopes
	});
}
function shouldTargetDevice(params) {
	if (!isIosPlatform(params.device.platform)) return false;
	if (!hasEffectivePairedDeviceRole(params.device, OPERATOR_ROLE)) return false;
	if (!params.requireApprovalScope) return true;
	return canApproveExecRequests(params.device);
}
async function loadRegisteredTargets(params) {
	if (params.deviceIds.length === 0) return [];
	return await loadApnsRegistrations(params.deviceIds);
}
async function resolvePairedTargets(params) {
	return await loadRegisteredTargets({ deviceIds: (await listDevicePairing()).paired.filter((device) => {
		if (!shouldTargetDevice({
			device,
			requireApprovalScope: params.requireApprovalScope
		})) return false;
		const operatorToken = resolveActiveOperatorToken(device);
		if (params.isTargetVisible && !params.isTargetVisible({
			deviceId: device.deviceId,
			scopes: operatorToken?.scopes ?? []
		})) return false;
		return true;
	}).map((device) => device.deviceId) });
}
async function resolveDeliveryPlan(params) {
	const targets = params.explicitNodeIds?.length ? await loadRegisteredTargets({ deviceIds: params.explicitNodeIds }) : await resolvePairedTargets({
		requireApprovalScope: params.requireApprovalScope,
		isTargetVisible: params.isTargetVisible
	});
	if (targets.length === 0) return { targets: [] };
	const needsDirect = targets.some((target) => target.registration.transport === "direct");
	const needsRelay = targets.some((target) => target.registration.transport === "relay");
	let directAuth;
	if (needsDirect) {
		const auth = await resolveApnsAuthConfigFromEnv(process.env);
		if (auth.ok) directAuth = auth.value;
		else params.log.warn?.(`exec approvals: iOS direct APNs auth unavailable: ${auth.error}`);
	}
	const relayConfigByNodeId = /* @__PURE__ */ new Map();
	if (needsRelay) for (const target of targets) {
		if (target.registration.transport !== "relay") continue;
		const relay = resolveApnsRelayConfigFromEnv(process.env, getRuntimeConfig().gateway, { registrationRelayOrigin: target.registration.relayOrigin });
		if (relay.ok) relayConfigByNodeId.set(target.nodeId, relay.value);
		else params.log.warn?.(`exec approvals: iOS relay APNs config unavailable: ${relay.error}`);
	}
	const relayConfig = relayConfigByNodeId.values().next().value;
	return {
		targets: targets.filter((target) => target.registration.transport === "direct" ? Boolean(directAuth) : relayConfigByNodeId.has(target.nodeId) && relayConfigByNodeId.get(target.nodeId)?.baseUrl === relayConfig?.baseUrl),
		directAuth,
		relayConfig
	};
}
async function clearStaleApnsRegistrationIfNeeded(params) {
	if (shouldClearStoredApnsRegistration({
		registration: params.registration,
		result: params.result
	})) await clearApnsRegistrationIfCurrent({
		nodeId: params.nodeId,
		registration: params.registration
	});
}
async function sendRequestedPushes(params) {
	return await sendApprovalPushes({
		approvalId: params.request.id,
		plan: params.plan,
		log: params.log,
		label: "request",
		logThrown: true,
		send: async ({ target, approvalId, plan }) => target.registration.transport === "direct" ? await sendApnsExecApprovalAlert({
			registration: target.registration,
			nodeId: target.nodeId,
			approvalId,
			auth: plan.directAuth
		}) : await sendApnsExecApprovalAlert({
			registration: target.registration,
			nodeId: target.nodeId,
			approvalId,
			relayConfig: plan.relayConfig
		})
	});
}
async function sendApprovalPushes(params) {
	const results = await Promise.allSettled(params.plan.targets.map(async (target) => {
		const result = await params.send({
			target,
			approvalId: params.approvalId,
			plan: params.plan
		});
		await clearStaleApnsRegistrationIfNeeded({
			nodeId: target.nodeId,
			registration: target.registration,
			result
		});
		if (!result.ok) params.log.warn?.(`exec approvals: iOS ${params.label} push failed node=${target.nodeId} status=${result.status} reason=${result.reason ?? "unknown"}`);
		return {
			nodeId: target.nodeId,
			ok: result.ok
		};
	}));
	for (const result of results) if (params.logThrown && result.status === "rejected") {
		const message = formatErrorMessage(result.reason);
		params.log.warn?.(`exec approvals: iOS ${params.label} push threw error: ${message}`);
	}
	return {
		attempted: params.plan.targets.length,
		delivered: results.filter((result) => result.status === "fulfilled" && result.value.ok).length
	};
}
async function sendResolvedPushes(params) {
	await sendApprovalPushes({
		approvalId: params.approvalId,
		plan: params.plan,
		log: params.log,
		label: "cleanup",
		logThrown: false,
		send: async ({ target, approvalId, plan }) => target.registration.transport === "direct" ? await sendApnsExecApprovalResolvedWake({
			registration: target.registration,
			nodeId: target.nodeId,
			approvalId,
			auth: plan.directAuth
		}) : await sendApnsExecApprovalResolvedWake({
			registration: target.registration,
			nodeId: target.nodeId,
			approvalId,
			relayConfig: plan.relayConfig
		})
	});
}
function createExecApprovalIosPushDelivery(params) {
	const approvalDeliveriesById = /* @__PURE__ */ new Map();
	const pendingDeliveryStateById = /* @__PURE__ */ new Map();
	const sendCleanupPushForApproval = async (approvalId) => {
		const deliveryState = approvalDeliveriesById.get(approvalId) ?? await pendingDeliveryStateById.get(approvalId);
		approvalDeliveriesById.delete(approvalId);
		pendingDeliveryStateById.delete(approvalId);
		if (!deliveryState?.nodeIds.length) {
			params.log.debug?.(`exec approvals: iOS cleanup push skipped approvalId=${approvalId} reason=missing-targets`);
			return;
		}
		await deliveryState.requestPushPromise;
		const plan = await resolveDeliveryPlan({
			requireApprovalScope: false,
			explicitNodeIds: deliveryState.nodeIds,
			log: params.log
		});
		if (plan.targets.length === 0) return;
		await sendResolvedPushes({
			approvalId,
			plan,
			log: params.log
		});
	};
	return {
		/** Sends the initial approval notification to visible iOS operator devices. */
		async handleRequested(request, opts) {
			const deliveryStatePromise = (async () => {
				const plan = await resolveDeliveryPlan({
					requireApprovalScope: true,
					isTargetVisible: opts?.isTargetVisible,
					log: params.log
				});
				if (plan.targets.length === 0) {
					approvalDeliveriesById.delete(request.id);
					return null;
				}
				const deliveryState = {
					nodeIds: plan.targets.map((target) => target.nodeId),
					requestPushPromise: sendRequestedPushes({
						request,
						plan,
						log: params.log
					}).catch((err) => {
						const message = formatErrorMessage(err);
						params.log.error?.(`exec approvals: iOS request push failed: ${message}`);
						return {
							attempted: plan.targets.length,
							delivered: 0
						};
					})
				};
				approvalDeliveriesById.set(request.id, deliveryState);
				return deliveryState;
			})();
			pendingDeliveryStateById.set(request.id, deliveryStatePromise);
			const deliveryState = await deliveryStatePromise;
			if (pendingDeliveryStateById.get(request.id) === deliveryStatePromise) pendingDeliveryStateById.delete(request.id);
			if (!deliveryState) return false;
			const { attempted, delivered } = await deliveryState.requestPushPromise;
			if (attempted > 0 && delivered === 0) {
				params.log.warn?.(`exec approvals: iOS request push reached no devices approvalId=${request.id} attempted=${attempted}`);
				if (approvalDeliveriesById.get(request.id)?.requestPushPromise === deliveryState.requestPushPromise) approvalDeliveriesById.delete(request.id);
				return false;
			}
			return true;
		},
		/** Sends cleanup wakes for resolved approval requests. */
		async handleResolved(resolved) {
			await sendCleanupPushForApproval(resolved.id);
		},
		/** Sends cleanup wakes for expired approval requests. */
		async handleExpired(request) {
			await sendCleanupPushForApproval(request.id);
		}
	};
}
//#endregion
//#region src/gateway/exec-approval-manager.ts
const RESOLVED_ENTRY_GRACE_MS = 15e3;
function unrefTimer(timer) {
	const unref = timer.unref;
	if (typeof unref === "function") unref.call(timer);
}
function scheduleResolvedEntryCleanup(cleanup) {
	unrefTimer(setTimeout(cleanup, RESOLVED_ENTRY_GRACE_MS));
}
function resolveApprovalTimeoutMs(timeoutMs) {
	return resolveTimerTimeoutMs(timeoutMs, 1);
}
var ExecApprovalManager = class {
	constructor() {
		this.pending = /* @__PURE__ */ new Map();
	}
	create(request, timeoutMs, id) {
		const now = Date.now();
		const expiresAtMs = resolveExpiresAtMsFromDurationMs(resolveApprovalTimeoutMs(timeoutMs), { nowMs: now });
		if (expiresAtMs === void 0) throw new Error("approval expiry is unavailable");
		return {
			id: id && id.trim().length > 0 ? id.trim() : randomUUID(),
			request,
			createdAtMs: now,
			expiresAtMs
		};
	}
	/**
	* Register an approval record and return a promise that resolves when the decision is made.
	* This separates registration (synchronous) from waiting (async), allowing callers to
	* confirm registration before the decision is made.
	*/
	register(record, timeoutMs) {
		const existing = this.pending.get(record.id);
		if (existing) {
			if (existing.record.resolvedAtMs === void 0) return existing.promise;
			throw new Error(`approval id '${record.id}' already resolved`);
		}
		let resolvePromise;
		let rejectPromise;
		const promise = new Promise((resolve, reject) => {
			resolvePromise = resolve;
			rejectPromise = reject;
		});
		const entry = {
			record,
			resolve: resolvePromise,
			reject: rejectPromise,
			timer: null,
			promise
		};
		const timerDelayMs = resolveApprovalTimeoutMs(timeoutMs);
		entry.timer = setTimeout(() => {
			this.expire(record.id);
		}, timerDelayMs);
		this.pending.set(record.id, entry);
		return promise;
	}
	/**
	* @deprecated Use register() instead for explicit separation of registration and waiting.
	*/
	async waitForDecision(record, timeoutMs) {
		return this.register(record, timeoutMs);
	}
	resolve(recordId, decision, resolvedBy) {
		const pending = this.pending.get(recordId);
		if (!pending) return false;
		if (pending.record.resolvedAtMs !== void 0) return false;
		clearTimeout(pending.timer);
		pending.record.resolvedAtMs = Date.now();
		pending.record.decision = decision;
		pending.record.resolvedBy = resolvedBy ?? null;
		pending.resolve(decision);
		scheduleResolvedEntryCleanup(() => {
			if (this.pending.get(recordId) === pending) this.pending.delete(recordId);
		});
		return true;
	}
	expire(recordId, resolvedBy) {
		const pending = this.pending.get(recordId);
		if (!pending) return false;
		if (pending.record.resolvedAtMs !== void 0) return false;
		clearTimeout(pending.timer);
		pending.record.resolvedAtMs = Date.now();
		pending.record.decision = void 0;
		pending.record.resolvedBy = resolvedBy ?? null;
		pending.resolve(null);
		scheduleResolvedEntryCleanup(() => {
			if (this.pending.get(recordId) === pending) this.pending.delete(recordId);
		});
		return true;
	}
	getSnapshot(recordId) {
		return this.pending.get(recordId)?.record ?? null;
	}
	listPendingRecords() {
		return Array.from(this.pending.values()).map((entry) => entry.record).filter((record) => record.resolvedAtMs === void 0);
	}
	consumeAllowOnce(recordId) {
		const entry = this.pending.get(recordId);
		if (!entry) return false;
		const record = entry.record;
		if (record.decision !== "allow-once") return false;
		record.consumedDecision = record.decision;
		record.decision = void 0;
		return true;
	}
	/**
	* Wait for decision on an already-registered approval.
	* Returns the decision promise if the ID is pending, null otherwise.
	*/
	awaitDecision(recordId) {
		return this.pending.get(recordId)?.promise ?? null;
	}
	lookupApprovalId(input, opts = {}) {
		const normalized = input.trim();
		if (!normalized) return { kind: "none" };
		const exact = this.pending.get(normalized);
		if (exact) return (opts.includeResolved || exact.record.resolvedAtMs === void 0) && (opts.filter?.(exact.record) ?? true) ? {
			kind: "exact",
			id: normalized
		} : { kind: "none" };
		const lowerPrefix = normalizeLowercaseStringOrEmpty(normalized);
		const matches = [];
		for (const [id, entry] of this.pending.entries()) {
			if (!opts.includeResolved && entry.record.resolvedAtMs !== void 0) continue;
			if (opts.filter && !opts.filter(entry.record)) continue;
			if (normalizeLowercaseStringOrEmpty(id).startsWith(lowerPrefix)) matches.push(id);
		}
		if (matches.length === 1) return {
			kind: "prefix",
			id: matches[0]
		};
		if (matches.length > 1) return {
			kind: "ambiguous",
			ids: matches
		};
		return { kind: "none" };
	}
	lookupPendingId(input) {
		return this.lookupApprovalId(input);
	}
};
//#endregion
//#region src/gateway/server-aux-handlers.ts
async function activateSecretsRuntimeSnapshot(snapshot) {
	(await import("./runtime-BzZlvYH7.js")).activateSecretsRuntimeSnapshot(snapshot);
}
function createLazyHandler(method, loadHandlers) {
	return async (opts) => {
		const handler = (await loadHandlers())[method];
		if (!handler) throw new Error(`lazy gateway handler not found: ${method}`);
		await handler(opts);
	};
}
/** Create auxiliary gateway handlers that are not part of the core descriptor set. */
function createGatewayAuxHandlers(params) {
	const execApprovalManager = new ExecApprovalManager();
	const execApprovalForwarder = createExecApprovalForwarder();
	const execApprovalIosPushDelivery = createExecApprovalIosPushDelivery({ log: params.log });
	let execApprovalHandlersPromise = null;
	const loadExecApprovalHandlers = () => execApprovalHandlersPromise ??= import("./exec-approval-6DrbEpfz.js").then(({ createExecApprovalHandlers }) => createExecApprovalHandlers(execApprovalManager, {
		forwarder: execApprovalForwarder,
		iosPushDelivery: execApprovalIosPushDelivery
	}));
	const buildReloadPlan = params.buildReloadPlan ?? buildGatewayReloadPlan;
	const pluginApprovalManager = new ExecApprovalManager();
	let pluginApprovalHandlersPromise = null;
	const loadPluginApprovalHandlers = () => pluginApprovalHandlersPromise ??= import("./plugin-approval-CquOxoLD.js").then(({ createPluginApprovalHandlers }) => createPluginApprovalHandlers(pluginApprovalManager, { forwarder: execApprovalForwarder }));
	let reloadInFlight = null;
	const runExclusiveReload = (fn) => {
		if (reloadInFlight) return reloadInFlight;
		const run = (async () => {
			try {
				return await fn();
			} finally {
				reloadInFlight = null;
			}
		})();
		reloadInFlight = run;
		return run;
	};
	let secretsHandlersPromise = null;
	const loadSecretsHandlers = () => secretsHandlersPromise ??= import("./secrets-IPKFcNxQ.js").then(({ createSecretsHandlers }) => createSecretsHandlers({
		reloadSecrets: () => runExclusiveReload(async () => {
			const previousSnapshot = getActiveSecretsRuntimeSnapshot();
			if (!previousSnapshot) throw new Error("Secrets runtime snapshot is not active.");
			const previousSharedGatewaySessionGeneration = params.sharedGatewaySessionGenerationState.current;
			const previousSharedGatewaySessionGenerationRequired = params.sharedGatewaySessionGenerationState.required;
			let nextSharedGatewaySessionGeneration;
			let sharedGatewaySessionGenerationChanged = false;
			const stoppedChannels = [];
			const restartedChannels = /* @__PURE__ */ new Set();
			try {
				const prepared = await params.activateRuntimeSecrets(previousSnapshot.sourceConfig, {
					reason: "reload",
					activate: true
				});
				nextSharedGatewaySessionGeneration = params.resolveSharedGatewaySessionGenerationForConfig(prepared.config);
				const plan = buildReloadPlan(diffConfigPaths(previousSnapshot.config, prepared.config));
				setCurrentSharedGatewaySessionGeneration(params.sharedGatewaySessionGenerationState, nextSharedGatewaySessionGeneration);
				sharedGatewaySessionGenerationChanged = previousSharedGatewaySessionGeneration !== nextSharedGatewaySessionGeneration;
				if (sharedGatewaySessionGenerationChanged) disconnectStaleSharedGatewayAuthClients({
					clients: params.clients,
					expectedGeneration: nextSharedGatewaySessionGeneration
				});
				if (plan.restartChannels.size > 0) {
					const restartChannels = [...plan.restartChannels];
					if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CHANNELS) || isTruthyEnvValue(process.env.OPENCLAW_SKIP_PROVIDERS)) throw new Error(`secrets.reload requires restarting channels: ${restartChannels.join(", ")}`);
					const restartFailures = [];
					for (const channel of restartChannels) {
						params.logChannels.info(`restarting ${channel} channel after secrets reload`);
						stoppedChannels.push(channel);
						try {
							await params.stopChannel(channel);
							await params.startChannel(channel);
							restartedChannels.add(channel);
						} catch {
							params.logChannels.info(`failed to restart ${channel} channel after secrets reload`);
							restartFailures.push(channel);
						}
					}
					if (restartFailures.length > 0) throw new Error(`failed to restart channels after secrets reload: ${restartFailures.join(", ")}`);
				}
				return { warningCount: prepared.warnings.length };
			} catch (err) {
				await activateSecretsRuntimeSnapshot(previousSnapshot);
				params.sharedGatewaySessionGenerationState.current = previousSharedGatewaySessionGeneration;
				params.sharedGatewaySessionGenerationState.required = previousSharedGatewaySessionGenerationRequired;
				if (sharedGatewaySessionGenerationChanged) disconnectStaleSharedGatewayAuthClients({
					clients: params.clients,
					expectedGeneration: previousSharedGatewaySessionGeneration
				});
				for (const channel of stoppedChannels) {
					params.logChannels.info(`rolling back ${channel} channel after secrets reload failure`);
					try {
						if (restartedChannels.has(channel)) await params.stopChannel(channel);
						await params.startChannel(channel);
					} catch {
						params.logChannels.info(`failed to roll back ${channel} channel after secrets reload`);
					}
				}
				throw err;
			}
		}),
		log: params.log,
		resolveSecrets: async ({ allowedPaths, commandName, forcedActivePaths, optionalActivePaths, providerOverrides, targetIds }) => {
			const { assignments, diagnostics, inactiveRefPaths } = await resolveCommandSecretsFromActiveRuntimeSnapshot({
				commandName,
				targetIds: new Set(targetIds),
				...allowedPaths ? { allowedPaths: new Set(allowedPaths) } : {},
				...forcedActivePaths ? { forcedActivePaths: new Set(forcedActivePaths) } : {},
				...optionalActivePaths ? { optionalActivePaths: new Set(optionalActivePaths) } : {},
				...providerOverrides ? { providerOverrides } : {}
			});
			if (assignments.length === 0) return {
				assignments: [],
				diagnostics,
				inactiveRefPaths
			};
			return {
				assignments,
				diagnostics,
				inactiveRefPaths
			};
		}
	}));
	return {
		execApprovalManager,
		pluginApprovalManager,
		extraHandlers: {
			"exec.approval.get": createLazyHandler("exec.approval.get", loadExecApprovalHandlers),
			"exec.approval.list": createLazyHandler("exec.approval.list", loadExecApprovalHandlers),
			"exec.approval.request": createLazyHandler("exec.approval.request", loadExecApprovalHandlers),
			"exec.approval.waitDecision": createLazyHandler("exec.approval.waitDecision", loadExecApprovalHandlers),
			"exec.approval.resolve": createLazyHandler("exec.approval.resolve", loadExecApprovalHandlers),
			"plugin.approval.list": createLazyHandler("plugin.approval.list", loadPluginApprovalHandlers),
			"plugin.approval.request": createLazyHandler("plugin.approval.request", loadPluginApprovalHandlers),
			"plugin.approval.waitDecision": createLazyHandler("plugin.approval.waitDecision", loadPluginApprovalHandlers),
			"plugin.approval.resolve": createLazyHandler("plugin.approval.resolve", loadPluginApprovalHandlers),
			"secrets.reload": createLazyHandler("secrets.reload", loadSecretsHandlers),
			"secrets.resolve": createLazyHandler("secrets.resolve", loadSecretsHandlers)
		}
	};
}
//#endregion
export { GATEWAY_AUX_METHODS, createGatewayAuxHandlers };