UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

18,160 lines • 683 kB
import { S as isRfc8215LocalUseNat64Ipv6Address, T as parseCanonicalIpAddress, b as isLoopbackIpAddress, c as loadManagedProxyTlsOptionsSync, l as resolveManagedProxyCaFileForUrl, t as forceResetGlobalDispatcher, u as getActiveManagedProxyLoopbackMode, w as normalizeIpAddress, x as isPrivateOrLoopbackIpAddress$1 } from "./undici-global-dispatcher-9vinYKHQ.mjs";
import { a as extractErrorCodeOrErrno, i as extractErrorCode } from "./error-coercion-CwIUUFnu.mjs";
import { A as getPluginCacheRoot, C as VERSION, D as bindPluginCacheRoot, E as PluginCacheFactInvalidatedError, F as materializePluginCacheError, I as InvocationFrame, L as getPluginExecutionFrame, M as getScopedPluginCache, N as getScopedPluginCaches, O as getPluginCache, P as invalidatePluginCacheMetadata, T as resolveOpenClawPackageRootSync, i as resolveOpenClawStateDirForDatabasePath, j as getProcessPluginCache, k as getPluginCacheRetirementSignal, o as resolveOpenClawStateSqlitePath, p as StartupMaintenanceRequiredError, w as resolveCompatibilityHostVersion } from "./openclaw-state-db.paths-JnUZZAu1.mjs";
import { A as asOptionalRecord, E as resolvePositiveTimerTimeoutMs, F as normalizeOptionalLowercaseString, I as normalizeOptionalString, M as isRecord, N as normalizeLowercaseStringOrEmpty, O as asNullableRecord, S as asSafeIntegerInRange, a as isNotFoundPathError, b as asPositiveFiniteNumber, c as normalizeWindowsPathPreservingCase, h as tryProcessCwd, i as sleepWithAbort, j as asRecord, l as resolveConfigDir, m as resolveUserPath, n as RetrySupervisor, o as isPathInside$2, r as resolveSleepDelayMs, s as normalizeWindowsPathForComparison$1, t as CONFIG_DIR, v as asFiniteNumber, y as asNonNegativeFiniteNumber } from "./utils-Dor5uhSv.mjs";
import { $ as resolveSecretRefProviderSourceMismatch, B as readFileDescriptorBoundedSync$1, E as readConfigIncludeFileWithGuards, F as resolvePathViaExistingAncestorSync$1, G as normalizeSecretInputString, H as resolveConfigEnvVars, I as resolveRootPathSync, J as ENV_SECRET_REF_ID_RE$1, K as resolveSecretInputRef, L as matchRootFileOpenFailure, N as isMissingPathError, O as resolveConfigIncludes, Q as normalizeAndGroupSecretRefs, R as openRootFileSync, U as coerceSecretRef, V as containsEnvVarReference, W as hasConfiguredSecretInput, X as isBuiltInDefaultSecretProviderRef, Y as SINGLE_VALUE_FILE_REF_ID$1, Z as isValidSecretProviderAlias, _ as resolveGatewayPort, at as isBlockedObjectKey$1, b as resolveStateDir, d as resolveGlobalSingleton, et as secretRefKey, i as redactToolPayloadText, it as parseConfigPathArrayIndex, j as hasErrnoCode, k as parseJsonWithJson5Fallback, l as registerSecretValueForRedaction, m as resolveConfigPath, nt as formatConcreteConfigPath, r as redactSensitiveText, rt as tokenizeConcreteConfigPath, st as expectDefined, u as resolveGlobalSet, ut as redactSensitiveUrlLikeString, v as resolveIncludeRoots, x as isVitestRuntimeEnv, y as resolveIsNixMode, z as readFileDescriptorBounded$1 } from "./redact-BwKH5fIe.mjs";
import { S as safeParseJson, _ as getConfigResolutionFacts, a as resolveAgentWorkspaceDir, b as isDeeplyFrozenPlainData, c as isArtifactPreservingStateRead, f as err, g as getAuthoredConfigSecretRef, h as copyConfigResolutionFactsExcept, i as readSecureFile, l as withExistingOpenClawStateDatabaseArtifactPreservingReadOnly, m as cloneConfigWithResolutionFacts, n as runOpenClawStateWriteTransaction, o as listAgentIds, p as ok, r as FsSafeError, s as getActiveOpenClawStateDatabaseReadSnapshot, t as openOpenClawStateDatabase, u as withExistingOpenClawStateDatabaseReadOnly, v as hasUnresolvedConfigPath, x as stableStringify, y as resolveConfigSecretRef } from "./openclaw-state-db-B0wY-r9I.mjs";
import { a as normalizeTrimmedStringList, c as normalizeUniqueTrimmedStringList, i as normalizeStringEntries, l as sortUniqueStrings, n as normalizeOptionalTrimmedStringList, o as normalizeUniqueSingleOrTrimmedStringList, r as normalizeSortedUniqueTrimmedStringList, t as normalizeArrayBackedTrimmedStringList, u as uniqueStrings } from "./string-normalization-BgvYsJM9.mjs";
import { t as createAbortError } from "./abort-signal-C9_EQ1qN.mjs";
import { n as formatErrorMessageWithCode, t as formatErrorMessage } from "./errors-C_Op1bVg.mjs";
import { a as READ_SCOPE, i as QUESTIONS_SCOPE, n as APPROVALS_SCOPE, o as TALK_SECRETS_SCOPE, r as PAIRING_SCOPE, s as WRITE_SCOPE, t as ADMIN_SCOPE } from "./operator-scopes-C844xNSs.mjs";
import { a as resolvePreauthHandshakeTimeoutMs, i as resolveFiniteTimeoutDelayMs, n as clearGatewayConnectTimeout, o as resolveSafeTimeoutDelayMs, r as resolveConnectChallengeTimeoutMs, s as startGatewayConnectTimeout, t as DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS } from "./timeouts-B9zvH0xD.mjs";
import { l as getOrCreatePromise, o as isTruthyEnvValue } from "./host-env-security-Dw0EukUB.mjs";
import { t as SUBAGENT_EXEC_ENV_VAR } from "./openclaw-exec-env-_vbUXwNA.mjs";
import { n as compareValidSemver } from "./semver-aYpwYdrQ.mjs";
import { B as tableExists, Gt as boolean, Qt as union, Wt as array, Xt as object, Yt as number, Zt as string, bn as compareOpenClawVersions, h as acquireSqliteStagingToken, j as createSqliteWorkerOperationAdmission, m as SQLITE_STAGING_TOKEN_FILES, mt as decodeMountInfoPath, nt as executeSqliteQueryTakeFirstSync, qt as literal, rn as isStateDatabaseReadAdmissionInvalidatedError, rt as getNodeSqliteKysely, tt as executeSqliteQuerySync, vn as applyConfigEnvVars, wt as isSqliteLockError } from "./openclaw-state-db-cache-D6Da40pQ.mjs";
import "./crypto-digest-DFe1GlHl.mjs";
import "./executable-path-RkpztJ_i.mjs";
import { l as sanitizeForLog, t as createSubsystemLogger } from "./subsystem-EYGCijzb.mjs";
import { r as runOpenClawStateWorkerOperation } from "./openclaw-state-worker-store-Ca0Joqz6.mjs";
import { t as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-DchKMzil.mjs";
import { t as runCommandWithTimeout } from "./exec-CigxEnwa.mjs";
import { a as signEd25519Payload, i as publicKeyRawBase64UrlFromEd25519Pem, n as deriveCanonicalEd25519PublicKeyRaw, t as deriveCanonicalEd25519PrivateKeyRaw } from "./ed25519-signature-Bu4nRTMI.mjs";
import "./temp-artifact-cleanup-CSHllIcc.mjs";
import { i as resolvePluginSourceCapturesDirectory, n as isLegacyPluginSourceCaptureName, r as resolvePluginSourceCaptureFallbackPrefix, t as PLUGIN_SOURCE_CAPTURE_PREFIX } from "./plugin-source-capture-path-DMdpmFBx.mjs";
import { _ as MANIFEST_KEY, h as resolveOfficialExternalPluginId, o as getOfficialExternalPluginCatalogManifest } from "./official-external-plugin-catalog-source-Dd1ssY-Z.mjs";
import { a as isPrereleaseResolutionAllowed, i as resolveOfficialExternalPluginLegacyIds, o as parseRegistryNpmSpec, r as resolveOfficialExternalPluginInstall, s as BUNDLED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_ENTRIES, t as getOfficialExternalPluginCatalogEntryForPackage } from "./official-external-plugin-catalog-Cm1VZUKg.mjs";
import { n as logError, t as logDebug } from "./logger-AMFCO2Ef.mjs";
import { createRequire } from "node:module";
import fsSync, { lstatSync, realpathSync } from "node:fs";
import path from "node:path";
import os, { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
import { tryReadJsonSync } from "@openclaw/fs-safe/json";
import { isPathInside, safeStatSync } from "@openclaw/fs-safe/path";
import { formatPosixMode, readRegularFileSync, safePathSegmentHashed } from "@openclaw/fs-safe/advanced";
import crypto, { X509Certificate, createHash, randomBytes, randomUUID } from "node:crypto";
import { AsyncLocalStorage } from "node:async_hooks";
import fsPromises from "node:fs/promises";
import { prerelease, satisfies, valid } from "semver";
import "@openclaw/fs-safe/durability";
import { Buffer as Buffer$1 } from "node:buffer";
import net, { isIP } from "node:net";
import "commander";
import "@openclaw/fs-safe/atomic";
import pMap from "p-map";
import { inspectPathPermissions, safeStat } from "@openclaw/fs-safe/permissions";
import { TLSSocket, checkServerIdentity } from "node:tls";
import { installGlobalProxy } from "@openclaw/proxyline";
import "typebox/guard";
import "typebox/schema";
import { Compile } from "typebox/compile";
import { Type } from "typebox";
//#region packages/gateway-client/src/event-loop-ready.ts
const DEFAULT_MAX_WAIT_MS = 1e4;
const DEFAULT_INTERVAL_MS = 1;
const DEFAULT_DRIFT_THRESHOLD_MS = 200;
const DEFAULT_CONSECUTIVE_READY_CHECKS = 2;
function resolvePositiveInteger(value, fallback) {
	return Number.isFinite(value) && value !== void 0 ? Math.max(1, Math.floor(value)) : fallback;
}
/** Waits until timer drift stays low for consecutive checks, or aborts/times out. */
async function waitForEventLoopReady(options = {}) {
	const maxWaitMs = resolveFiniteTimeoutDelayMs(options.maxWaitMs, DEFAULT_MAX_WAIT_MS, { minMs: 0 });
	const intervalMs = resolveFiniteTimeoutDelayMs(options.intervalMs, DEFAULT_INTERVAL_MS);
	const driftThresholdMs = resolvePositiveInteger(options.driftThresholdMs, DEFAULT_DRIFT_THRESHOLD_MS);
	const consecutiveReadyChecks = resolvePositiveInteger(options.consecutiveReadyChecks, DEFAULT_CONSECUTIVE_READY_CHECKS);
	const signal = options.signal;
	const startedAt = Date.now();
	let readyChecks = 0;
	let checks = 0;
	let maxDriftMs = 0;
	return await new Promise((resolve) => {
		let settled = false;
		let timer = null;
		const clearTimer = () => {
			if (timer) {
				clearTimeout(timer);
				timer = null;
			}
		};
		const finish = (ready, aborted = false) => {
			if (settled) return;
			settled = true;
			clearTimer();
			signal?.removeEventListener("abort", onAbort);
			resolve({
				ready,
				elapsedMs: Math.max(0, Date.now() - startedAt),
				maxDriftMs,
				checks,
				aborted
			});
		};
		const onAbort = () => {
			finish(false, true);
		};
		if (signal?.aborted) {
			finish(false, true);
			return;
		}
		signal?.addEventListener("abort", onAbort, { once: true });
		const scheduleNext = () => {
			if (signal?.aborted) {
				finish(false, true);
				return;
			}
			const elapsedMs = Math.max(0, Date.now() - startedAt);
			const remainingMs = maxWaitMs - elapsedMs;
			if (remainingMs <= 0) {
				finish(false);
				return;
			}
			const delayMs = Math.min(intervalMs, remainingMs);
			const scheduledAt = Date.now();
			timer = setTimeout(() => {
				timer = null;
				checks += 1;
				const driftMs = Math.max(0, Date.now() - scheduledAt - delayMs);
				maxDriftMs = Math.max(maxDriftMs, driftMs);
				if (driftMs > driftThresholdMs) readyChecks = 0;
				else readyChecks += 1;
				if (readyChecks >= consecutiveReadyChecks) {
					finish(true);
					return;
				}
				scheduleNext();
			}, delayMs);
		};
		scheduleNext();
	});
}
//#endregion
//#region packages/gateway-client/src/readiness.ts
function resolveGatewayClientStartReadinessTimeoutMs(options = {}) {
	if (typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs)) return options.timeoutMs;
	const clientOptions = options.clientOptions ?? {};
	return resolveConnectChallengeTimeoutMs(clientOptions.connectChallengeTimeoutMs, {
		env: clientOptions.env,
		configuredTimeoutMs: clientOptions.preauthHandshakeTimeoutMs
	});
}
/** Starts a gateway client only after the supplied readiness probe succeeds. */
async function startGatewayClientWithReadinessWait(waitForReady, client, options = {}) {
	const readiness = await waitForReady({
		maxWaitMs: resolveGatewayClientStartReadinessTimeoutMs(options),
		signal: options.signal
	});
	if (readiness.ready && !readiness.aborted && options.signal?.aborted !== true) client.start();
	return readiness;
}
/** Starts a gateway client after the default event-loop readiness probe succeeds. */
async function startGatewayClientWhenEventLoopReady(client, options = {}) {
	return startGatewayClientWithReadinessWait(waitForEventLoopReady, client, options);
}
//#endregion
//#region packages/gateway-protocol/src/client-info.ts
/** Canonical client ids accepted in gateway hello/connect payloads. */
const GATEWAY_CLIENT_IDS = {
	WEBCHAT_UI: "webchat-ui",
	CONTROL_UI: "openclaw-control-ui",
	BROWSER_COPILOT: "openclaw-browser-copilot",
	TUI: "openclaw-tui",
	WEBCHAT: "webchat",
	CLI: "cli",
	GATEWAY_CLIENT: "gateway-client",
	MACOS_APP: "openclaw-macos",
	LINUX_APP: "openclaw-linux",
	IOS_APP: "openclaw-ios",
	WATCHOS_APP: "openclaw-watchos",
	ANDROID_APP: "openclaw-android",
	NODE_HOST: "node-host",
	WORKER: "openclaw-worker",
	TEST: "test",
	FINGERPRINT: "fingerprint",
	PROBE: "openclaw-probe"
};
const GATEWAY_CLIENT_NAMES = GATEWAY_CLIENT_IDS;
/** Coarse modes let policy group clients without matching every product id. */
const GATEWAY_CLIENT_MODES = {
	WEBCHAT: "webchat",
	CLI: "cli",
	UI: "ui",
	BACKEND: "backend",
	NODE: "node",
	WORKER: "worker",
	PROBE: "probe",
	TEST: "test"
};
new Set(Object.values(GATEWAY_CLIENT_IDS));
new Set(Object.values(GATEWAY_CLIENT_MODES));
//#endregion
//#region packages/gateway-protocol/src/protocol-value-normalization.ts
/** Checks string presence without changing wire-significant whitespace. */
function isNonEmptyProtocolString(value) {
	return typeof value === "string" && value.length > 0;
}
//#endregion
//#region packages/gateway-protocol/src/connect-error-details.ts
/**
* Shared gateway connect-error detail helpers.
*
* These details cross client/server boundaries, so readers normalize untrusted
* payloads before using them in reconnect decisions or user-facing messages.
*/
/** Structured connect-error codes carried in gateway error `details.code`. */
const ConnectErrorDetailCodes = {
	AUTH_REQUIRED: "AUTH_REQUIRED",
	AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
	AUTH_TOKEN_MISSING: "AUTH_TOKEN_MISSING",
	AUTH_TOKEN_MISMATCH: "AUTH_TOKEN_MISMATCH",
	AUTH_TOKEN_NOT_CONFIGURED: "AUTH_TOKEN_NOT_CONFIGURED",
	AUTH_PASSWORD_MISSING: "AUTH_PASSWORD_MISSING",
	AUTH_PASSWORD_MISMATCH: "AUTH_PASSWORD_MISMATCH",
	AUTH_PASSWORD_NOT_CONFIGURED: "AUTH_PASSWORD_NOT_CONFIGURED",
	AUTH_BOOTSTRAP_TOKEN_INVALID: "AUTH_BOOTSTRAP_TOKEN_INVALID",
	AUTH_DEVICE_TOKEN_MISMATCH: "AUTH_DEVICE_TOKEN_MISMATCH",
	AUTH_SCOPE_MISMATCH: "AUTH_SCOPE_MISMATCH",
	AUTH_RATE_LIMITED: "AUTH_RATE_LIMITED",
	AUTH_TAILSCALE_IDENTITY_MISSING: "AUTH_TAILSCALE_IDENTITY_MISSING",
	AUTH_TAILSCALE_PROXY_MISSING: "AUTH_TAILSCALE_PROXY_MISSING",
	AUTH_TAILSCALE_WHOIS_FAILED: "AUTH_TAILSCALE_WHOIS_FAILED",
	AUTH_TAILSCALE_IDENTITY_MISMATCH: "AUTH_TAILSCALE_IDENTITY_MISMATCH",
	AUTH_IDENTITY_HEADER_REQUIRED: "AUTH_IDENTITY_HEADER_REQUIRED",
	AUTH_VERIFIED_USER_REQUIRED: "AUTH_VERIFIED_USER_REQUIRED",
	AUTHENTICATED_PROFILE_UNAVAILABLE: "AUTHENTICATED_PROFILE_UNAVAILABLE",
	OPERATOR_ACCESS_DENIED: "OPERATOR_ACCESS_DENIED",
	CONTROL_UI_BUILD_MISMATCH: "CONTROL_UI_BUILD_MISMATCH",
	CONTROL_UI_ORIGIN_NOT_ALLOWED: "CONTROL_UI_ORIGIN_NOT_ALLOWED",
	PROTOCOL_MISMATCH: "PROTOCOL_MISMATCH",
	CONTROL_UI_DEVICE_IDENTITY_REQUIRED: "CONTROL_UI_DEVICE_IDENTITY_REQUIRED",
	DEVICE_IDENTITY_REQUIRED: "DEVICE_IDENTITY_REQUIRED",
	DEVICE_AUTH_INVALID: "DEVICE_AUTH_INVALID",
	DEVICE_AUTH_DEVICE_ID_MISMATCH: "DEVICE_AUTH_DEVICE_ID_MISMATCH",
	DEVICE_AUTH_SIGNATURE_EXPIRED: "DEVICE_AUTH_SIGNATURE_EXPIRED",
	DEVICE_AUTH_NONCE_REQUIRED: "DEVICE_AUTH_NONCE_REQUIRED",
	DEVICE_AUTH_NONCE_MISMATCH: "DEVICE_AUTH_NONCE_MISMATCH",
	DEVICE_AUTH_SIGNATURE_INVALID: "DEVICE_AUTH_SIGNATURE_INVALID",
	DEVICE_AUTH_PUBLIC_KEY_INVALID: "DEVICE_AUTH_PUBLIC_KEY_INVALID",
	PAIRING_REQUIRED: "PAIRING_REQUIRED",
	CLIENT_VERSION_MISMATCH: "CLIENT_VERSION_MISMATCH"
};
/** Pairing-specific reasons clients can display and use for reconnect policy. */
const ConnectPairingRequiredReasons = {
	NOT_PAIRED: "not-paired",
	ROLE_UPGRADE: "role-upgrade",
	SCOPE_UPGRADE: "scope-upgrade",
	METADATA_UPGRADE: "metadata-upgrade"
};
const CONNECT_RECOVERY_NEXT_STEP_VALUES = /* @__PURE__ */ new Set([
	"retry_with_device_token",
	"update_auth_configuration",
	"update_auth_credentials",
	"wait_then_retry",
	"review_auth_configuration"
]);
const CONNECT_PAIRING_REQUIRED_REASON_VALUES = new Set(Object.values(ConnectPairingRequiredReasons));
const PAIRING_CONNECT_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const PAIRING_CONNECT_REASON_METADATA = {
	"not-paired": {
		requirement: "device is not approved yet",
		remediationHint: "Approve this device from the pending pairing requests.",
		recoveryTitle: "Gateway pairing approval required."
	},
	"role-upgrade": {
		requirement: "device is asking for a higher role than currently approved",
		remediationHint: "Review the requested role upgrade, then approve the pending request.",
		recoveryTitle: "Gateway role upgrade approval required."
	},
	"scope-upgrade": {
		requirement: "device is asking for more scopes than currently approved",
		remediationHint: "Review the requested scopes, then approve the pending upgrade.",
		recoveryTitle: "Gateway scope upgrade approval required."
	},
	"metadata-upgrade": {
		requirement: "device identity changed and must be re-approved",
		remediationHint: "Review the refreshed device details, then approve the pending request.",
		recoveryTitle: "Gateway device refresh approval required."
	}
};
const CONNECT_PAIRING_REQUIRED_MESSAGE_BY_REASON = {
	"not-paired": "device pairing required",
	"role-upgrade": "role upgrade pending approval",
	"scope-upgrade": "scope upgrade pending approval",
	"metadata-upgrade": "device metadata change pending approval"
};
/** Reads a non-empty detail code from an untrusted error details payload. */
function readConnectErrorDetailCode(details) {
	if (!isRecord(details)) return null;
	const code = details.code;
	return typeof code === "string" && code.trim().length > 0 ? code.trim() : null;
}
/** Extracts normalized retry advice from untrusted connect-error details. */
function readConnectErrorRecoveryAdvice(details) {
	if (!isRecord(details)) return {};
	return {
		canRetryWithDeviceToken: typeof details.canRetryWithDeviceToken === "boolean" ? details.canRetryWithDeviceToken : void 0,
		recommendedNextStep: normalizeConnectRecoveryNextStep(details.recommendedNextStep)
	};
}
function normalizeConnectRecoveryNextStep(value) {
	const normalized = normalizeOptionalString(value) ?? "";
	return CONNECT_RECOVERY_NEXT_STEP_VALUES.has(normalized) ? normalized : void 0;
}
function normalizePairingConnectReason(value) {
	const normalized = normalizeOptionalString(value) ?? "";
	return CONNECT_PAIRING_REQUIRED_REASON_VALUES.has(normalized) ? normalized : void 0;
}
/** Normalizes pairing request ids before echoing them in close reasons or UI text. */
function normalizePairingConnectRequestId(value) {
	const normalized = normalizeOptionalString(value);
	return normalized && PAIRING_CONNECT_REQUEST_ID_PATTERN.test(normalized) ? normalized : void 0;
}
function createPairingConnectErrorDetails(params) {
	return {
		code: ConnectErrorDetailCodes.PAIRING_REQUIRED,
		...params.reason ? { reason: params.reason } : {},
		...params.requestId ? { requestId: params.requestId } : {},
		...params.remediationHint ? { remediationHint: params.remediationHint } : {},
		...params.recommendedNextStep ? { recommendedNextStep: params.recommendedNextStep } : {},
		...params.retryable !== void 0 ? { retryable: params.retryable } : {},
		...params.pauseReconnect !== void 0 ? { pauseReconnect: params.pauseReconnect } : {},
		...params.deviceId ? { deviceId: params.deviceId } : {},
		...params.requestedRole ? { requestedRole: params.requestedRole } : {},
		...params.requestedScopes ? { requestedScopes: params.requestedScopes } : {},
		...params.approvedRoles ? { approvedRoles: params.approvedRoles } : {},
		...params.approvedScopes ? { approvedScopes: params.approvedScopes } : {}
	};
}
function buildPairingConnectRemediationHint(reason) {
	return reason ? PAIRING_CONNECT_REASON_METADATA[reason].remediationHint : "Approve the pending device request before retrying.";
}
/** Reads and backfills pairing-required details from an untrusted details object. */
function readPairingConnectErrorDetails(details) {
	if (readConnectErrorDetailCode(details) !== ConnectErrorDetailCodes.PAIRING_REQUIRED) return null;
	if (!isRecord(details)) return null;
	const reason = normalizePairingConnectReason(details.reason);
	const requestId = normalizePairingConnectRequestId(details.requestId);
	const remediationHint = normalizeOptionalString(details.remediationHint) ?? buildPairingConnectRemediationHint(reason);
	const recommendedNextStep = normalizeConnectRecoveryNextStep(details.recommendedNextStep);
	const deviceId = normalizeOptionalString(details.deviceId);
	const requestedRole = normalizeOptionalString(details.requestedRole);
	const requestedScopes = normalizeOptionalTrimmedStringList(details.requestedScopes);
	const approvedRoles = normalizeOptionalTrimmedStringList(details.approvedRoles);
	const approvedScopes = normalizeOptionalTrimmedStringList(details.approvedScopes);
	return createPairingConnectErrorDetails({
		reason,
		requestId,
		remediationHint,
		recommendedNextStep,
		retryable: typeof details.retryable === "boolean" ? details.retryable : void 0,
		pauseReconnect: typeof details.pauseReconnect === "boolean" ? details.pauseReconnect : void 0,
		deviceId,
		requestedRole,
		requestedScopes,
		approvedRoles,
		approvedScopes
	});
}
/** Formats pairing-required details into the canonical user-facing message. */
function formatConnectPairingRequiredMessage(details) {
	const pairing = readPairingConnectErrorDetails(details);
	const base = CONNECT_PAIRING_REQUIRED_MESSAGE_BY_REASON[pairing?.reason ?? ConnectPairingRequiredReasons.NOT_PAIRED];
	return pairing?.requestId ? `${base} (requestId: ${pairing.requestId})` : base;
}
/** Formats connect errors using structured details before falling back to raw messages. */
function formatConnectErrorMessage(params) {
	if (readConnectErrorDetailCode(params.details) === ConnectErrorDetailCodes.PAIRING_REQUIRED) return formatConnectPairingRequiredMessage(params.details);
	if (readConnectErrorDetailCode(params.details) === ConnectErrorDetailCodes.PROTOCOL_MISMATCH) return formatProtocolMismatchMessage(params.message, params.details);
	return normalizeOptionalString(params.message) ?? "gateway request failed";
}
function formatProtocolMismatchMessage(message, details) {
	const raw = details;
	const clientMin = normalizeProtocolNumber(raw.clientMinProtocol);
	const clientMax = normalizeProtocolNumber(raw.clientMaxProtocol);
	const expected = normalizeProtocolNumber(raw.expectedProtocol);
	const probeMin = normalizeProtocolNumber(raw.minimumProbeProtocol);
	const parts = [];
	if (clientMin !== void 0 && clientMax !== void 0) parts.push(clientMin === clientMax ? `Control UI v${clientMin}` : `Control UI v${clientMin}-v${clientMax}`);
	if (expected !== void 0) parts.push(`Gateway v${expected}`);
	if (probeMin !== void 0) parts.push(`probe min v${probeMin}`);
	const normalized = normalizeOptionalString(message) ?? "protocol mismatch";
	return parts.length > 0 ? `${normalized}: ${parts.join(", ")}` : normalized;
}
function normalizeProtocolNumber(value) {
	return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
}
//#endregion
//#region packages/gateway-protocol/src/gateway-error-details.ts
/** Stable discriminants for structured method-level failures. */
const GatewayErrorDetailCodes = {
	CRON_JOB_NOT_FOUND: "CRON_JOB_NOT_FOUND",
	MISSING_SCOPE: "MISSING_SCOPE",
	MCP_APP_VIEW_EXPIRED: "MCP_APP_VIEW_EXPIRED",
	OUTBOUND_DELIVERY_QUEUED: "OUTBOUND_DELIVERY_QUEUED",
	USER_PREFS_LIMIT_EXCEEDED: "USER_PREFS_LIMIT_EXCEEDED",
	SESSION_COMPANION_BUSY: "SESSION_COMPANION_BUSY",
	SKILL_PROPOSAL_REVISION_CHANGED: "SKILL_PROPOSAL_REVISION_CHANGED",
	PROJECT_CLONE_FAILED: "PROJECT_CLONE_FAILED",
	UNKNOWN_AGENT_ID: "UNKNOWN_AGENT_ID",
	WIZARD_NOT_FOUND: "WIZARD_NOT_FOUND",
	SETUP_ADMISSION_BUSY: "SETUP_ADMISSION_BUSY",
	GITHUB_PUBLICATION_SELECTION_REJECTED: "GITHUB_PUBLICATION_SELECTION_REJECTED",
	SESSION_WORKSPACE_RECOVERY_REQUIRED: "SESSION_WORKSPACE_RECOVERY_REQUIRED",
	TASK_WORKTREE_SOURCE_REQUIRED: "TASK_WORKTREE_SOURCE_REQUIRED",
	TASK_HISTORY_PREVIEW_CAPACITY: "TASK_HISTORY_PREVIEW_CAPACITY"
};
/** Reads validated missing-scope details from an untrusted protocol payload. */
function readMissingScopeErrorDetails(details) {
	const record = asNullableRecord(details);
	if (record?.code !== GatewayErrorDetailCodes.MISSING_SCOPE) return null;
	const missingScope = typeof record.missingScope === "string" ? record.missingScope.trim() : "";
	const requiredScopes = Array.isArray(record.requiredScopes) ? record.requiredScopes.map((scope) => typeof scope === "string" ? scope.trim() : "") : [];
	if (!missingScope || requiredScopes.length === 0 || requiredScopes.some((scope) => !scope)) return null;
	return {
		code: GatewayErrorDetailCodes.MISSING_SCOPE,
		missingScope,
		requiredScopes
	};
}
//#endregion
//#region src/config/gateway-dispatch-config.ts
const GATEWAY_DISPATCH_SHELL_ENV_EXPECTED_KEYS = ["OPENCLAW_GATEWAY_TOKEN", "OPENCLAW_GATEWAY_PASSWORD"];
const GATEWAY_DISPATCH_TOP_LEVEL_KEYS = [
	"agents",
	"env",
	"gateway",
	"plugins",
	"secrets",
	"session"
];
function resolveGatewayDispatchConfig(value, env) {
	if (!isRecord(value)) return {};
	if (Object.hasOwn(value, "env")) applyConfigEnvVars(value, env);
	const projected = {};
	for (const key of GATEWAY_DISPATCH_TOP_LEVEL_KEYS) if (Object.hasOwn(value, key)) projected[key] = value[key];
	return resolveConfigEnvVars(projected, env, { onMissing: () => void 0 });
}
function applyGatewayDispatchSessionDefaults(config) {
	if (config.session?.mainKey === void 0) return config;
	return {
		...config,
		session: {
			...config.session,
			mainKey: "main"
		}
	};
}
function resolveIncludesForGatewayDispatch(parsed, configPath, env) {
	return resolveConfigIncludes(parsed, configPath, {
		readFile: (candidate) => fsSync.readFileSync(candidate, "utf-8"),
		readFileWithGuards: ({ includePath, resolvedPath, rootRealDir }) => readConfigIncludeFileWithGuards({
			includePath,
			resolvedPath,
			rootRealDir,
			ioFs: fsSync
		}),
		parseJson: parseJsonWithJson5Fallback
	}, { allowedRoots: resolveIncludeRoots(env) });
}
function readRawGatewayDispatchConfig(options = {}) {
	const env = options.env ?? process.env;
	const configPath = options.configPath ?? resolveConfigPath(env);
	if (!fsSync.existsSync(configPath)) return {
		config: {},
		configPath
	};
	const raw = fsSync.readFileSync(configPath, "utf-8");
	return {
		config: applyGatewayDispatchSessionDefaults(resolveGatewayDispatchConfig(resolveIncludesForGatewayDispatch(parseJsonWithJson5Fallback(raw), configPath, env), env)),
		configPath
	};
}
function readGatewayDispatchConfig(options = {}) {
	return readRawGatewayDispatchConfig(options).config;
}
async function readGatewayDispatchConfigWithShellEnvFallback(options = {}) {
	const env = options.env ?? process.env;
	const firstRead = readRawGatewayDispatchConfig(options);
	const { loadShellEnvFallback, resolveShellEnvFallbackTimeoutMs, shouldDeferShellEnvFallback, shouldEnableShellEnvFallback } = await import("./shell-env-CLfTsB-3.mjs");
	if ((shouldEnableShellEnvFallback(env) || firstRead.config.env?.shellEnv?.enabled === true) && !shouldDeferShellEnvFallback(env)) loadShellEnvFallback({
		enabled: true,
		env,
		expectedKeys: [...GATEWAY_DISPATCH_SHELL_ENV_EXPECTED_KEYS],
		logger: options.logger ?? console,
		timeoutMs: firstRead.config.env?.shellEnv?.timeoutMs ?? resolveShellEnvFallbackTimeoutMs(env)
	});
	return readGatewayDispatchConfig({
		...options,
		configPath: path.resolve(firstRead.configPath)
	});
}
resolveGlobalSet(Symbol.for("openclaw.sessionRowChanges"), "close-and-restart");
resolveGlobalSet(Symbol.for("openclaw.sessionRowFactChanges"), "close-and-restart");
resolveGlobalSet(Symbol.for("openclaw.sessionRowProjectionChanges"), "close-and-restart");
new AsyncLocalStorage();
//#endregion
//#region src/config/runtime-snapshot.ts
let runtimeConfigSnapshot = null;
function getRuntimeConfigSnapshot() {
	return runtimeConfigSnapshot;
}
//#endregion
//#region src/shared/operator-scope-compat.ts
const OPERATOR_ROLE = "operator";
const OPERATOR_ADMIN_SCOPE = "operator.admin";
const OPERATOR_READ_SCOPE = "operator.read";
const OPERATOR_TALK_SCOPE = "operator.talk";
const OPERATOR_WRITE_SCOPE = "operator.write";
const OPERATOR_SESSION_READ_SCOPE = "operator.sessions.read";
const OPERATOR_SESSION_WRITE_SCOPE = "operator.sessions.write";
const OPERATOR_SCOPE_PREFIX = "operator.";
function operatorScopeSatisfied(requestedScope, granted) {
	if (!requestedScope.startsWith(OPERATOR_SCOPE_PREFIX)) return false;
	return granted.includes(requestedScope) || granted.includes(OPERATOR_ADMIN_SCOPE) || (requestedScope === OPERATOR_READ_SCOPE || requestedScope === OPERATOR_TALK_SCOPE || requestedScope === OPERATOR_SESSION_READ_SCOPE || requestedScope === OPERATOR_SESSION_WRITE_SCOPE) && granted.includes(OPERATOR_WRITE_SCOPE) || requestedScope === OPERATOR_SESSION_READ_SCOPE && (granted.includes(OPERATOR_READ_SCOPE) || granted.includes(OPERATOR_SESSION_WRITE_SCOPE));
}
/** Returns true when a role grant satisfies requested scopes, including operator implications. */
function roleScopesAllow(params) {
	return resolveMissingRequestedScope(params) === null;
}
/** Returns the original first requested scope not covered by the role's allowed scopes. */
function resolveMissingRequestedScope(params) {
	const role = params.role.trim();
	const prefix = `${role}.`;
	const allowedScopes = params.allowedScopes.map((scope) => scope.trim());
	for (const scope of params.requestedScopes) {
		const requestedScope = scope.trim();
		if (!requestedScope) continue;
		if (!(role === OPERATOR_ROLE ? operatorScopeSatisfied(requestedScope, allowedScopes) : requestedScope.startsWith(prefix) && allowedScopes.includes(requestedScope))) return scope;
	}
	return null;
}
//#endregion
//#region src/gateway/credential-planner.ts
/** Normalize optional Gateway credential strings to nonempty values. */
const trimToUndefined = normalizeOptionalString;
/**
* Like trimToUndefined but also rejects unresolved env var placeholders (e.g. `${VAR}`).
* This prevents literal placeholder strings like `${OPENCLAW_GATEWAY_TOKEN}` from being
* accepted as valid credentials when the referenced env var is missing.
* Note: legitimate credential values containing literal `${UPPER_CASE}` patterns will
* also be rejected, but this is an extremely unlikely edge case.
*/
function trimCredentialToUndefined(value) {
	const trimmed = trimToUndefined(value);
	if (trimmed && containsEnvVarReference(trimmed)) return;
	return trimmed;
}
/** Classify one configured credential input without resolving secret refs. */
function resolveConfiguredGatewayCredentialInput(params) {
	const resolutionFacts = getConfigResolutionFacts(params.config);
	if (hasUnresolvedConfigPath(params.config, params.path) || getAuthoredConfigSecretRef(params.config, params.path)) return {
		path: params.path,
		configured: true,
		refPath: params.path,
		hasSecretRef: false
	};
	if (resolutionFacts !== null && typeof params.value === "string") return {
		path: params.path,
		configured: Boolean(trimToUndefined(params.value)),
		value: trimToUndefined(params.value),
		hasSecretRef: false
	};
	const ref = resolveSecretInputRef({
		value: params.value,
		defaults: params.defaults
	}).ref;
	return {
		path: params.path,
		configured: hasConfiguredSecretInput(params.value, params.defaults),
		value: ref ? void 0 : trimToUndefined(params.value),
		refPath: ref ? params.path : void 0,
		hasSecretRef: ref !== null
	};
}
/** Build the shared credential plan for Gateway startup, local auth, and remote client auth. */
function createGatewayCredentialPlan(params) {
	const env = params.env ?? process.env;
	const gateway = params.config.gateway;
	const remote = gateway?.remote;
	const defaults = params.defaults ?? params.config.secrets?.defaults;
	const authMode = gateway?.auth?.mode;
	const envToken = trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
	const envPassword = trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
	const localToken = resolveConfiguredGatewayCredentialInput({
		config: params.config,
		value: gateway?.auth?.token,
		defaults,
		path: "gateway.auth.token"
	});
	const localPassword = resolveConfiguredGatewayCredentialInput({
		config: params.config,
		value: gateway?.auth?.password,
		defaults,
		path: "gateway.auth.password"
	});
	const remoteToken = resolveConfiguredGatewayCredentialInput({
		config: params.config,
		value: remote?.token,
		defaults,
		path: "gateway.remote.token"
	});
	const remotePassword = resolveConfiguredGatewayCredentialInput({
		config: params.config,
		value: remote?.password,
		defaults,
		path: "gateway.remote.password"
	});
	const localTokenCanWin = authMode !== "password" && authMode !== "none" && authMode !== "trusted-proxy";
	const tokenCanWin = Boolean(envToken || localToken.configured || remoteToken.configured);
	const passwordCanWin = authMode === "password" || authMode === "trusted-proxy" || authMode !== "token" && authMode !== "none" && !tokenCanWin;
	const localTokenSurfaceActive = localTokenCanWin && (authMode === "token" || authMode === void 0 && !(envPassword || localPassword.configured));
	const remoteMode = gateway?.mode === "remote";
	const remoteUrlConfigured = Boolean(trimToUndefined(remote?.url));
	const tailscaleRemoteExposure = gateway?.tailscale?.mode === "serve" || gateway?.tailscale?.mode === "funnel";
	const remoteConfiguredSurface = remoteMode || remoteUrlConfigured || tailscaleRemoteExposure;
	const remoteTokenFallbackActive = localTokenCanWin && !envToken && !localToken.configured;
	const remotePasswordFallbackActive = authMode !== "trusted-proxy" && !envPassword && !localPassword.configured && passwordCanWin;
	return {
		configuredMode: gateway?.mode === "remote" ? "remote" : "local",
		authMode,
		envToken,
		envPassword,
		localToken,
		localPassword,
		remoteToken,
		remotePassword,
		localTokenCanWin,
		localPasswordCanWin: passwordCanWin,
		localTokenSurfaceActive,
		tokenCanWin,
		passwordCanWin,
		remoteMode,
		remoteUrlConfigured,
		tailscaleRemoteExposure,
		remoteConfiguredSurface,
		remoteTokenFallbackActive,
		remoteTokenActive: remoteConfiguredSurface || remoteTokenFallbackActive,
		remotePasswordFallbackActive,
		remotePasswordActive: remoteConfiguredSurface || remotePasswordFallbackActive
	};
}
//#endregion
//#region src/gateway/credentials.ts
/** Trim caller-supplied Gateway credentials without consulting config or environment. */
function resolveExplicitGatewayAuth(auth) {
	return {
		token: trimToUndefined(auth?.token),
		password: trimToUndefined(auth?.password)
	};
}
const GATEWAY_SECRET_REF_UNAVAILABLE_ERROR_CODE = "GATEWAY_SECRET_REF_UNAVAILABLE";
/** Raised when a command path needs Gateway credentials before secret refs were resolved. */
var GatewaySecretRefUnavailableError = class extends Error {
	constructor(path) {
		super([
			`${path} is configured as a secret reference but is unavailable in this command path.`,
			"Fix: set OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD, pass explicit --token/--password,",
			"or run a gateway command path that resolves secret references before credential selection."
		].join("\n"));
		this.code = GATEWAY_SECRET_REF_UNAVAILABLE_ERROR_CODE;
		this.name = "GatewaySecretRefUnavailableError";
		this.path = path;
	}
};
/** Resolve direct token/password values with caller-selected env-vs-config precedence. */
function resolveGatewayCredentialsFromValues(params) {
	const env = params.env ?? process.env;
	const envToken = trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
	const envPassword = trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
	const configToken = trimCredentialToUndefined(params.configToken);
	const configPassword = trimCredentialToUndefined(params.configPassword);
	const tokenPrecedence = params.tokenPrecedence ?? "env-first";
	const passwordPrecedence = params.passwordPrecedence ?? "env-first";
	return {
		token: tokenPrecedence === "config-first" ? configToken || envToken : envToken || configToken,
		password: passwordPrecedence === "config-first" ? configPassword || envPassword : envPassword || configPassword
	};
}
function resolveLocalGatewayCredentials(params) {
	const tokenConfigFallback = params.plan.localToken.configured ? params.plan.localToken.value : params.plan.remoteToken.value;
	const passwordConfigFallback = params.plan.localPassword.configured ? params.plan.localPassword.value : params.plan.authMode === "trusted-proxy" ? void 0 : params.plan.remotePassword.value;
	const localResolved = {
		token: params.localPrecedence === "config-first" ? params.plan.localToken.value || params.plan.envToken || (params.plan.localToken.configured ? void 0 : params.plan.remoteToken.value) : params.plan.envToken || tokenConfigFallback,
		password: params.localPrecedence === "config-first" ? params.plan.localPassword.value || params.plan.envPassword || (params.plan.localPassword.configured ? void 0 : passwordConfigFallback) : params.plan.envPassword || passwordConfigFallback
	};
	const localPasswordCanWin = params.plan.authMode === "password" || params.plan.authMode === "trusted-proxy" || params.plan.authMode !== "token" && params.plan.authMode !== "none" && !localResolved.token;
	const localTokenCanWin = params.plan.authMode === "token" || params.plan.authMode !== "password" && params.plan.authMode !== "none" && params.plan.authMode !== "trusted-proxy" && !localResolved.password;
	if (params.plan.localToken.refPath && params.localPrecedence === "config-first" && !params.plan.localToken.value && Boolean(params.plan.envToken) && localTokenCanWin) throw new GatewaySecretRefUnavailableError(params.plan.localToken.refPath);
	if (params.plan.localPassword.refPath && params.localPrecedence === "config-first" && !params.plan.localPassword.value && Boolean(params.plan.envPassword) && localPasswordCanWin) throw new GatewaySecretRefUnavailableError(params.plan.localPassword.refPath);
	if (params.plan.localToken.refPath && !localResolved.token && !params.plan.envToken && localTokenCanWin) throw new GatewaySecretRefUnavailableError(params.plan.localToken.refPath);
	if (params.plan.localPassword.refPath && !localResolved.password && !params.plan.envPassword && localPasswordCanWin) throw new GatewaySecretRefUnavailableError(params.plan.localPassword.refPath);
	return localResolved;
}
function resolveRemoteGatewayCredentials(params) {
	const token = params.remoteTokenFallback === "remote-only" ? params.plan.remoteToken.value : params.remoteTokenPrecedence === "env-first" ? params.plan.envToken || params.plan.remoteToken.value || params.plan.localToken.value : params.plan.remoteToken.value || params.plan.envToken || params.plan.localToken.value;
	const password = params.remotePasswordFallback === "remote-only" ? params.plan.remotePassword.value : params.remotePasswordPrecedence === "env-first" ? params.plan.envPassword || params.plan.remotePassword.value || params.plan.localPassword.value : params.plan.remotePassword.value || params.plan.envPassword || params.plan.localPassword.value;
	const localTokenFallbackEnabled = params.remoteTokenFallback !== "remote-only";
	const localTokenFallback = params.remoteTokenFallback === "remote-only" ? void 0 : params.plan.localToken.value;
	const localPasswordFallback = params.remotePasswordFallback === "remote-only" ? void 0 : params.plan.localPassword.value;
	if (params.plan.remoteToken.refPath && !token && !params.plan.envToken && !localTokenFallback && !password) throw new GatewaySecretRefUnavailableError(params.plan.remoteToken.refPath);
	if (params.plan.remotePassword.refPath && !password && !params.plan.envPassword && !localPasswordFallback && !token) throw new GatewaySecretRefUnavailableError(params.plan.remotePassword.refPath);
	if (params.plan.localToken.refPath && localTokenFallbackEnabled && !token && !password && !params.plan.envToken && !params.plan.remoteToken.value && params.plan.localTokenCanWin) throw new GatewaySecretRefUnavailableError(params.plan.localToken.refPath);
	return {
		token,
		password
	};
}
/** Resolve Gateway credentials from config, explicit auth, URL overrides, and mode policy. */
function resolveGatewayCredentialsFromConfig(params) {
	const env = params.env ?? process.env;
	const explicitToken = trimToUndefined(params.explicitAuth?.token);
	const explicitPassword = trimToUndefined(params.explicitAuth?.password);
	if (explicitToken || explicitPassword) return {
		token: explicitToken,
		password: explicitPassword
	};
	if (trimToUndefined(params.urlOverride) && params.urlOverrideSource !== "env") return {};
	if (trimToUndefined(params.urlOverride) && params.urlOverrideSource === "env") return resolveGatewayCredentialsFromValues({
		configToken: void 0,
		configPassword: void 0,
		env,
		tokenPrecedence: "env-first",
		passwordPrecedence: "env-first"
	});
	const plan = createGatewayCredentialPlan({
		config: params.cfg,
		env
	});
	if ((params.modeOverride ?? plan.configuredMode) === "local") return resolveLocalGatewayCredentials({
		plan,
		localPrecedence: params.localPrecedence ?? "config-first"
	});
	const remoteTokenFallback = params.remoteTokenFallback ?? "remote-env-local";
	const remotePasswordFallback = params.remotePasswordFallback ?? "remote-env-local";
	return resolveRemoteGatewayCredentials({
		plan,
		remoteTokenPrecedence: params.remoteTokenPrecedence ?? "remote-first",
		remotePasswordPrecedence: params.remotePasswordPrecedence ?? "env-first",
		remoteTokenFallback,
		remotePasswordFallback
	});
}
//#endregion
//#region src/gateway/auth-resolve.ts
/** Merge sparse runtime auth overrides without erasing configured values. */
function mergeGatewayAuthConfig(base, override) {
	const merged = { ...base };
	if (!override) return merged;
	for (const key of [
		"mode",
		"token",
		"password",
		"allowTailscale",
		"rateLimit",
		"trustedProxy"
	]) if (override[key] !== void 0) Object.assign(merged, { [key]: override[key] });
	return merged;
}
function finalizeResolvedGatewayAuth(params) {
	const { authConfig, authOverride, token, password } = params;
	const mode = authOverride?.mode ?? authConfig.mode ?? (password ? "password" : token ? "token" : "token");
	return {
		mode,
		modeSource: authOverride?.mode !== void 0 ? "override" : authConfig.mode ? "config" : password ? "password" : token ? "token" : "default",
		token,
		password,
		allowTailscale: authConfig.allowTailscale ?? (params.tailscaleMode === "serve" && mode !== "password" && mode !== "trusted-proxy"),
		trustedProxy: authConfig.trustedProxy
	};
}
/** Resolve Gateway auth mode, credentials, trusted-proxy policy, and Tailscale allowance. */
function resolveGatewayAuth(params) {
	const runtimeConfig = getRuntimeConfigSnapshot();
	if (runtimeConfig && runtimeConfig.gateway?.auth === params.authConfig) return resolveGatewayAuthForConfig({
		config: runtimeConfig,
		authOverride: params.authOverride,
		env: params.env,
		tailscaleMode: params.tailscaleMode
	});
	const authOverride = params.authOverride ?? void 0;
	const authConfig = mergeGatewayAuthConfig(params.authConfig, authOverride);
	const env = params.env ?? process.env;
	const tokenRef = resolveSecretInputRef({ value: authConfig.token }).ref;
	const passwordRef = resolveSecretInputRef({ value: authConfig.password }).ref;
	const resolvedCredentials = resolveGatewayCredentialsFromValues({
		configToken: tokenRef ? void 0 : authConfig.token,
		configPassword: passwordRef ? void 0 : authConfig.password,
		env,
		tokenPrecedence: "config-first",
		passwordPrecedence: "config-first"
	});
	return finalizeResolvedGatewayAuth({
		authConfig,
		authOverride,
		token: resolvedCredentials.token,
		password: resolvedCredentials.password,
		tailscaleMode: params.tailscaleMode
	});
}
/** Resolve auth from an env-substituted config while retaining its resolution facts. */
function resolveGatewayAuthForConfig(params) {
	const authOverride = params.authOverride ?? void 0;
	const authConfig = mergeGatewayAuthConfig(params.config.gateway?.auth, authOverride);
	const config = {
		...params.config,
		gateway: {
			...params.config.gateway,
			auth: authConfig
		}
	};
	const overriddenPaths = [...authOverride?.token !== void 0 ? ["gateway.auth.token"] : [], ...authOverride?.password !== void 0 ? ["gateway.auth.password"] : []];
	copyConfigResolutionFactsExcept(params.config, config, overriddenPaths);
	const plan = createGatewayCredentialPlan({
		config,
		env: params.env
	});
	return finalizeResolvedGatewayAuth({
		authConfig,
		authOverride,
		token: plan.localToken.hasSecretRef ? void 0 : plan.localToken.value ?? plan.envToken ?? plan.remoteToken.value,
		password: plan.localPassword.hasSecretRef ? void 0 : plan.localPassword.value ?? plan.envPassword ?? (plan.authMode === "trusted-proxy" ? void 0 : plan.remotePassword.value),
		tailscaleMode: params.tailscaleMode
	});
}
//#endregion
//#region packages/session-url-contract/src/session-key.ts
const INCOGNITO_SESSION_RE = /^agent:[^:]+:(?:dashboard|subagent|internal-session-effects):incognito-[^:]+$/u;
/** Classifies process-only agent session keys without consulting runtime registry state. */
function isIncognitoSessionKey(sessionKey) {
	const raw = sessionKey?.trim().toLowerCase();
	return Boolean(raw && INCOGNITO_SESSION_RE.test(raw));
}
//#endregion
//#region src/shared/device-auth.ts
/** Normalize a device-auth role id without changing its case or namespace. */
function normalizeDeviceAuthRole(role) {
	return role.trim();
}
/** Normalize device-auth scopes, dedupe/sort them, and include implied operator scopes. */
function normalizeDeviceAuthScopes(scopes) {
	if (!Array.isArray(scopes)) return [];
	const out = /* @__PURE__ */ new Set();
	for (const scope of scopes) {
		if (typeof scope !== "string") continue;
		const trimmed = scope.trim();
		if (trimmed) out.add(trimmed);
	}
	if (out.has("operator.admin")) {
		out.add("operator.read");
		out.add("operator.write");
	} else if (out.has("operator.write")) out.add("operator.read");
	return [...out].toSorted();
}
//#endregion
//#region src/infra/device-auth-store.kernel.ts
function createDeviceAuthEntry(params) {
	return {
		token: params.token,
		role: normalizeDeviceAuthRole(params.role),
		scopes: normalizeDeviceAuthScopes(params.scopes),
		updatedAtMs: params.updatedAtMs ?? Date.now()
	};
}
//#endregion
//#region src/infra/device-auth-store.ts
const legacyPresenceCache = /* @__PURE__ */ new Map();
function assertNoLegacyDeviceAuth(env) {
	const stateDir = resolveStateDir(env);
	let hasLegacy = legacyPresenceCache.get(stateDir);
	if (hasLegacy === void 0) {
		hasLegacy = fsSync.existsSync(path.join(stateDir, "identity", "device-auth.json"));
		legacyPresenceCache.set(stateDir, hasLegacy);
	}
	if (hasLegacy) throw new Error("Legacy device auth requires migration; stop the Gateway and run `openclaw doctor --fix`.");
}
function captureDeviceAuthOperation(params) {
	assertNoLegacyDeviceAuth(params.env);
	const context = captureOpenClawStateWorkerContext({ env: params.env });
	const { signal, assertCurrent } = params;
	const assertActive = () => {
		context.admission.assertCurrent();
		signal?.throwIfAborted();
		assertCurrent?.();
	};
	return {
		context,
		signal,
		assertActive
	};
}
async function executeDeviceAuth(captured, type, input, readOnly = false) {
	const { context, signal, assertActive } = captured;
	const mutation = type === "deviceAuth.store" || type === "deviceAuth.storeOrigin" || type === "deviceAuth.clear" || type === "deviceAuth.clearOrigin";
	const operation = (scope) => scope.execute({
		type,
		input
	}, { signal });
	const options = {
		assertCurrent: assertActive,
		...mutation ? { createAdmission: () => ({
			nativeLocations: [context.admission.databasePath],
			admission: createSqliteWorkerOperationAdmission((request, grant) => {
				if (request.stage !== "transaction" && request.stage !== "commit") throw new Error("Device token mutation requires transaction admission");
				assertActive();
				grant();
			})
		}) } : {}
	};
	const result = await (readOnly ? runOpenClawStateWorkerOperation(context, operation, {
		...options,
		existingOnly: true
	}) : runOpenClawStateWorkerOperation(context, operation, options));
	if (!mutation) assertActive();
	return result;
}
/** Prepare the command runtime before connection work, without reading or caching token facts. */
async function prepareDeviceAuthStore(params) {
	await executeDeviceAuth(captureDeviceAuthOperation(params), "deviceAuth.prepare", void 0, params.readOnly === true);
}
async function readDeviceAuth(params, readOnly) {
	const { deviceId, role, gatewayScope, onSnapshot } = params;
	const captured = captureDeviceAuthOperation(params);
	const observation = await (gatewayScope === void 0 ? executeDeviceAuth(captured, "deviceAuth.read", {
		deviceId,
		role,
		readOnly
	}, readOnly) : executeDeviceAuth(captured, "deviceAuth.readOrigin", {
		deviceId,
		role,
		gatewayScope,
		readOnly
	}, readOnly)) ?? {
		entry: null,
		expectedToken: null
	};
	captured.assertActive();
	onSnapshot?.(observation);
	return observation.entry;
}
function loadDeviceAuthToken(params) {
	return readDeviceAuth(params, false);
}
function loadDeviceAuthTokenReadOnly(params) {
	return readDeviceAuth(params, true);
}
async function storeDeviceAuthToken(params) {
	const input = {
		deviceId: params.deviceId,
		...createDeviceAuthEntry(params),
		expectedToken: params.expectedToken
	};
	return await executeDeviceAuth(captureDeviceAuthOperation(params), "deviceAuth.store", input);
}
async function clearDeviceAuthToken(params) {
	const { deviceId, role, expectedToken, observedToken } = params;
	return await executeDeviceAuth(captureDeviceAuthOperation(params), "deviceAuth.clear", {
		deviceId,
		role,
		expectedToken,
		observedToken
	});
}
function loadOriginDeviceToken(params) {
	return readDeviceAuth(params, false);
}
function loadOriginDeviceTokenReadOnly(params) {
	return readDeviceAuth(params, true);
}
async function storeOriginDeviceToken(params) {
	const input = {
		gatewayScope: params.gatewayScope,
		deviceId: params.deviceId,
		...createDeviceAuthEntry(params),
		expectedToken: params.expectedToken
	};
	return await executeDeviceAuth(captureDeviceAuthOperation(params), "deviceAuth.storeOrigin", input);
}
async function clearOriginDeviceToken(params) {
	const { deviceId, role, gatewayScope, expectedToken, observedToken } = params;
	return await executeDeviceAuth(captureDeviceAuthOperation(params), "deviceAuth.clearOrigin", {
		deviceId,
		role,
		gatewayScope,
		expectedToken,
		observedToken
	});
}
//#endregion
//#region packages/model-catalog-core/src/provider-id.ts
function normalizeProviderId(provider) {
	return normalizeLowercaseStringOrEmpty(provider);
}
//#endregion
//#region src/infra/path-existence.ts
/** Only definite absence, including a non-directory ancestor, permits an absent verdict. */
function pathMayExistSync(filePath) {
	try {
		fsSync.lstatSync(filePath);
		return true;
	} catch (error) {
		return !isMissingPathError(error);
	}
}
//#endregion
//#region src/infra/device-identity-store.ts
const PRIMARY_DEVICE_IDENTITY_KEY = "primary";
var DeviceIdentityStorageError = class extends Error {
	constructor(message, options) {
		super(message, options);
		this.name = "DeviceIdentityStorageError";
	}
};
function normalizeIdentityKey(key) {
	const normalized = key ?? PRIMARY_DEVICE_IDENTITY_KEY;
	if (normalized.length === 0 || normalized !== normalized.trim()) throw new DeviceIdentityStorageError("Device identity key must be a non-empty string without surrounding whitespace.");
	if (normalized.length > 128) throw new DeviceIdentityStorageError("Device identity key exceeds 128 characters.");
	return normalized;
}
function invalidStoredIdentityError(identityKey, cause) {
	return new DeviceIdentityStorageError(`SQLite contains an invalid persisted device identity "${identityKey}". Run "openclaw doctor --fix" before starting the gateway or connecting this client.`, cause === void 0 ? void 0 : { cause });
}
function fingerprintPublicKey(publicKeyPem) {
	const raw = deriveCanonicalEd25519PublicKeyRaw(publicKeyPem);
	return crypto.createHash("sha256").update(raw).digest("hex");
}
/** Generate canonical Ed25519 material before entering a synchronous write transaction. */
function generateStoredDeviceIdentity(now = Date.now()) {
	const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
	const publicKeyPem = publicKey.export({
		type: "spki",
		format: "pem"
	});
	const privateKeyPem = privateKey.export({
		type: "pkcs8",
		format: "pem"
	});
	return {
		deviceId: fingerprintPublicKey(publicKeyPem),
		publicKeyPem,
		privateKeyPem,
		createdAtMs: now
	};
}
function keyPairMatches(publicKeyPem, privateKeyPem) {
	try {
		deriveCanonicalEd25519PublicKeyRaw(publicKeyPem);
		deriveCanonicalEd25519PrivateKeyRaw(privateKeyPem);
		const publicKey = crypto.createPublicKey(publicKeyPem);
		const privateKey = crypto.createPrivateKey(privateKeyPem);
		if (publicKey.asymmetricKeyType !== "ed25519" || privateKey.asymmetricKeyType !== "ed25519") return false;
		const derivedPublicKey = crypto.createPublicKey(privateKeyPem).export({
			type: "spki",
			format: "der"
		});
		const storedPublicKey = publicKey.export({
			type: "spki",
			format: "der"
		});
		return Buffer.from(derivedPublicKey).equals(Buffer.from(storedPublicKey));
	} catch {
		return false;
	}
}
function parseCreatedAtMs(value) {
	return asSafeIntegerInRange(value, { min: 0 }) ?? null;
}
/** Validate persisted key material and return the canonical runtime shape. */
function validateStoredDeviceIdentity(value, identityKey = PRIMARY_DEVICE_IDENTITY_KEY) {
	try {
		if (!value.deviceId || !/^[a-f0-9]{64}$/.test(value.deviceId) || !value.publicKeyPem || !value.privateKeyPem || parseCreatedAtMs(value.createdAtMs) === null || !keyPairMatches(value.publicKeyPem, value.privateKeyPem)) throw invalidStoredIdentityError(identityKey);
		if (fingerprintPublicKey(value.publicKeyPem) !== value.deviceId) throw invalidStoredIdentityError(identityKey);
		return {
			deviceId: value.deviceId,
			publicKeyPem: value.publicKeyPem,
			privateKeyPem: value.privateKeyPem
		};
	} catch (error) {
		if (error instanceof DeviceIdentityStorageError) throw error;
		throw invalidStoredIdentityError(identityKey, error);
	}
}
function rowToStoredIdentity(row, expectedIdentityKey) {
	if (row.identity_key !== expectedIdentityKey || typeof row.device_id !== "string" || typeof row.public_key_pem !== "string" || typeof row.private_key_pem !== "string" || parseCreatedAtMs(row.created_at_ms) === null || parseCreatedAtMs(row.updated_at_ms) === null) throw invalidStoredIdentityError(expectedIdentityKey);
	return {
		deviceId: row.device_id,
		publicKeyPem: row.public_key_pem,
		privateKeyPem: row.private_key_pem,
		createdAtMs: row.created_at_ms
	};
}
function storedIdentityToRow(identityKey, stored, updatedAtMs = stored.createdAtMs) {
	return {
		identity_key: identityKey,
		device_id: stored.deviceId,
		public_key_pem: stored.publicKeyPem,
		private_key_pem: stored.privateKeyPem,
		created_at_ms: stored.createdAtMs,
		updated_at_ms: updatedAtMs
	};
}
function readStoredIdentityRowFromDatabase(database, identityKey) {
	const db = getNodeSqliteKysely(database.db);
	return executeSqliteQueryTakeFirstSync(database.db, db.selectFrom("device_identities").selectAll().where("identity_key", "=", identityKey)) ?? null;
}
function readStoredIdentityFromDatabase(database, identityKey) {
	const row = readStoredIdentityRowFromDatabase(database, identityKey);
	return row ? rowToStoredIdentity(row, identityKey) : null;
}
function isEmptyBootstrapIdentityTableMiss(database, error) {
	if (!(error instanceof Error) || !hasErrnoCode(error, "ERR_SQLITE_ERROR") || !/\bno such table: device_identities\b/iu.test(error.message)) return false;
	const db = getNodeSqliteKysely(database.db);
	return !executeSqliteQueryTakeFirstSync(database.db, db.selectFrom("sqlite_master").select("name").where("name", "not like", "sqlite_%").limit(1));
}
/** Resolve the concrete database and row identity used by process caches and diagnostics. */
function resolveDeviceIdentityStore(options = {}) {
	return {
		databasePath: path.resolve(options.path ?? resolveOpenClawStateSqlitePath(options.env ?? process.env)),
		identityKey: normalizeIdentityKey(options.identityKey)
	};
}
function assertNoPendingLegacyIdentity(options) {
	const { databasePath, identityKey } = resolveDeviceIdentityStore(options);
	if (identityKey !== PRIMARY_DEVICE_IDENTITY_KEY) return;
	const legacyPath = path.join(resolveOpenClawStateDirForDatabasePath(databasePath), "identity", "device.json");
	if (pathMayExistSync(`${legacyPath}.doctor-importing`) || pathMayExistSync(`${legacyPath}.native-importing`) || pathMayExistSync(legacyPath)) throw new StartupMaintenanceRequiredError("state-migrations", `Legacy device identity exists at ${legacyPath}. Run "openclaw doctor --fix" before starting the gateway or connecting this client.`);
}
/** Read through the writable shared-state lifecycle, validating any existing row. */
function readStoredDeviceIdentity(options = {}) {
	const resolved = resolveDeviceIdentityStore(options);
	const stored = readStoredIdentityFromDatabase(openOpenClawStateDatabase({
		env: options.env,
		path: resolved.databasePath
	}), resolved.identityKey);
	if (stored) validateStoredDeviceIdentity(stored, resolved.identityKey);
	return stored;
}
/** Read without creating, repairing, chmodding, or joining the writer lifecycle. */
function readStoredDeviceIdentityReadOnly(options = {}) {
	const resolved = resolveDeviceIdentityStore(options);
	return withExistingOpenClawStateDatabaseArtifactPreservingReadOnly((database) => {
		let stored;
		try {
			stored = readStoredIdentityFromDatabase(database, resolved.identityKey);
		} catch (error) {
			if (isEmptyBootstrapIdentityTableMiss(database, error)) return null;
			throw error;
		}
		if (stored) validateStoredDeviceIdentity(stored, resolved.identityKey);
		return stored;
	}, {
		env: options.env,
		path: resolved.databasePath
	}) ?? null;
}
/** Insert a candidate only when the key is still absent, then return the authoritative row. */
function insertStoredDeviceIdentityIfAbsent(candidate, options = {}) {
	const resolved = resolveDeviceIdentityStore(options);
	validateStoredDeviceIdentity(candidate, resolved.identityKey);
	return runOpenClawStateWriteTransaction(({ db }) => {
		const existing = readStoredIdentityFromDatabase({ db }, resolved.identityKey);
		if (existing) validateStoredDeviceIdentity(existing, resolved.identityKey);
		else {
			assertNoPendingLegacyIdentity({
				...options,
				path: resolved.databasePath,
				identityKey: resolved.identityKey
			});
			const kysely = getNodeSqliteKysely(db);
			executeSqliteQuerySync(db, kysely.insertInto("device_identities").values(storedIdentityToRow(resolved.identityKey, candidate)).onConflict((conflict) => conflict.column("identity_key").doNothing()));
		}
		const authoritative = readStoredIdentityFromDatabase({ db }, resolved.identityKey);
		if (!authoritative) throw new DeviceIdentityStorageError(`SQLite device identity "${resolved.identityKey}" was not durable after insert.`);
		validateStoredDeviceIdentity(authoritative, resolved.identityKey);
		return authoritative;
	}, {
		env: options.env,
		path: resolved.databasePath
	}, { operationLabel: "device-identity.create" });
}
//#endregion
//#region src/infra/device-identity.ts
function toDeviceIdentity(stored) {
	return {
		deviceId: stored.deviceId,
		publicKeyPem: stored.publicKeyPem,
		privateKeyPem: stored.privateKeyPem
	};
}
/** Load a valid canonical identity or atomically create its SQLite row. */
function loadOrCreateDeviceIdentity(options = {}) {
	const resolved = resolveDeviceIdentityStore(options);
	const resolvedOptions = {
		...options,
		path: resolved.databasePath,
		identityKey: resolved.identityKey
	};
	const existing = pathMayExistSync(resolved.databasePath) ? readStoredDeviceIdentity(resolvedOptions) : null;
	if (existing) return toDeviceIdentity(existing);
	assertNoPendingLegacyIdentity(resolvedOptions);
	return toDeviceIdentity(insertStoredDeviceIdentityIfAbsent(generateStoredDeviceIdentity(), resolvedOptions));
}
/** Load a valid persisted identity without creating or mutating SQLite state. */
function loadDeviceIdentityIfPresent(options = {}) {
	const stored = readStoredDeviceIdentityReadOnly(options);
	if (stored) return toDeviceIdentity(stored);
	assertNoPendingLegacyIdentity(options);
	return null;
}
/** Sign a UTF-8 payload with a PEM Ed25519 private key and return base64url bytes. */
function signDevicePayload(privateKeyPem, payload) {
	return signEd25519Payload(privateKeyPem, payload);
}
/** Export a PEM Ed25519 public key as canonical raw base64url bytes. */
function publicKeyRawBase64UrlFromPem(publicKeyPem) {
	return publicKeyRawBase64UrlFromEd25519Pem(publicKeyPem);
}
//#endregion
//#region src/gateway/websocket-protocol.ts
/** Map the HTTP aliases accepted by WebSocket clients onto their canonical schemes. */
function normalizeWebSocketProtocol(protocol) {
	return protocol === "https:" ? "wss:" : protocol === "http:" ? "ws:" : protocol;
}
//#endregion
//#region src/gateway/net.ts
function isLoopbackAddress(ip) {
	return isLoopbackIpAddress(ip);
}
/**
* Returns true if the IP belongs to a private or loopback network range.
* Private ranges: RFC1918, link-local, ULA IPv6, and CGNAT (100.64/10), plus loopback.
* Excludes RFC8215 local-use NAT64: SSRF policy blocks that allocation, but
* Gateway trust decisions cannot infer a private mapped destination from it.
*/
function isPrivateOrLoopbackAddress(ip) {
	return isPrivateOrLoopbackIpAddress$1(ip) && !isRfc8215LocalUseNat64Ipv6Address(ip);
}
/**
* Check if a hostname or IP refers to the local machine.
* Handles: localhost, 127.x.x.x, ::1, [::1], ::ffff:127.x.x.x
* Note: 0.0.0.0 and :: are NOT loopback - they bind to all interfaces.
*/
function isLoopbackHost(host) {
	const parsed = parseHostForAddressChecks$1(host);
	if (!parsed) return false;
	if (parsed.isLocalhost) return true;
	return isLoopbackAddress(parsed.unbracketedHost);
}
function isLoopbackGatewayUrl(rawUrl) {
	try {
		const hostname = new URL(rawUrl).hostname.toLowerCase();
		const unbracketed = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
		return unbracketed === "localhost" || isLoopbackIpAddress(unbracketed);
	} catch {
		return false;
	}
}
/**
* Check if a hostname or IP refers to a private or loopback address.
* Handles the same hostname formats as isLoopbackHost, but also accepts
* RFC 1918, link-local, CGNAT, and IPv6 ULA/link-local addresses.
*/
function isPrivateOrLoopbackHost$1(host) {
	const parsed = parseHostForAddressChecks$1(host);
	if (!parsed) return false;
	if (parsed.isLocalhost) return true;
	const normalized = normalizeIpAddress(parsed.unbracketedHost);
	if (!normalized || !isPrivateOrLoopbackAddress(normalized)) return false;
	if (net.isIP(normalized) === 6) {
		if (normalized.startsWith("ff")) return false;
		if (normalized === "::") return false;
	}
	return true;
}
function parseHostForAddressChecks$1(host) {
	if (!host) return null;
	const normalizedHost = normalizeLowercaseStringOrEmpty(host);
	const canonicalHost = normalizedHost.replace(/\.+$/, "");
	if (canonicalHost === "localhost") return {
		isLocalhost: true,
		unbracketedHost: canonicalHost
	};
	return {
		isLocalhost: false,
		unbracketedHost: normalizedHost.startsWith("[") && normalizedHost.endsWith("]") ? normalizedHost.slice(1, -1) : normalizedHost
	};
}
/**
* Security check for WebSocket URLs (CWE-319: Cleartext Transmission of Sensitive Information).
*
* Returns true if the URL is secure for transmitting data:
* - wss:// (TLS) is always secure
* - ws:// is secure for loopback, private IP literals, .local, and Tailnet hosts
* - optional break-glass: other private-DNS ws:// hostnames can be enabled for trusted networks
*
* All other ws:// URLs are considered insecure because both credentials
* AND chat/conversation data would be exposed to network interception.
*/
function isSecureWebSocketUrl$1(url, opts) {
	let parsed;
	try {
		parsed = new URL(url);
	} catch {
		return false;
	}
	const protocol = normalizeWebSocketProtocol(parsed.protocol);
	if (protocol === "wss:") return true;
	if (protocol !== "ws:") return false;
	if (isLoopbackHost(parsed.hostname)) return true;
	if (isTrustedPlaintextWebSocketHost$1(parsed.hostname)) return true;
	if (opts?.allowPrivateWs) {
		if (isPrivateOrLoopbackHost$1(parsed.hostname)) return true;
		const hostForIpCheck = parsed.hostname.startsWith("[") && parsed.hostname.endsWith("]") ? parsed.hostname.slice(1, -1) : parsed.hostname;
		return net.isIP(hostForIpCheck) === 0;
	}
	return false;
}
function isTrustedPlaintextWebSocketHost$1(hostname) {
	if (isPrivateOrLoopbackHost$1(hostname)) return true;
	const normalized = normalizeLowercaseStringOrEmpty(hostname).replace(/\.+$/, "");
	return normalized.endsWith(".local") || normalized.endsWith(".ts.net");
}
//#endregion
//#region src/gateway/call-device-auth.ts
var GatewayCredentialsRequiredError = class extends Error {
	constructor(params) {
		super([
			`gateway ${params.method} requires credentials before opening a websocket`,
			"Fix: configure gateway.auth token/password, pair this device, or pass --token/--password.",
			`Config: ${params.configPath}`
		].join("\n"));
		this.name = "GatewayCredentialsRequiredError";
		this.method = params.method;
		this.configPath = params.configPath;
	}
};
var GatewayStoredDeviceAuthUnavailableError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "GatewayStoredDeviceAuthUnavailableError";
	}
};
var GatewayLocalBackendSharedAuthUnavailableError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "GatewayLocalBackendSharedAuthUnavailableError";
	}
};
function resolveDeviceIdentityForGatewayCall(sharedStateMode) {
	try {
		return sharedStateMode === "read-only" ? loadDeviceIdentityIfPresent() : loadOrCreateDeviceIdentity();
	} catch {
		return null;
	}
}
function resolveGatewayCallDeviceAuth(params) {
	const { opts } = params;
	const isLoopback = isLoopbackGatewayUrl(params.url);
	const clientOptions = {
		clientName: opts.clientName,
		mode: opts.mode,
		requireLocalBackendSharedAuth: opts.requireLocalBackendSharedAuth
	};
	if (opts.allowLocalBackendAuthNone && params.authMode === "none" && params.isImplicitLocalTarget && isLoopback && opts.clientName === GATEWAY_CLIENT_NAMES.CLI && opts.mode === GATEWAY_CLIENT_MODES.CLI && opts.deviceIdentity === void 0 && !opts.useStoredDeviceAuth && !opts.preparedDeviceAuth && !opts.approvalRuntimeToken && !opts.agentRuntimeIdentityToken) {
		clientOptions.clientName = GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
		clientOptions.mode = GATEWAY_CLIENT_MODES.BACKEND;
		clientOptions.requireLocalBackendSharedAuth = true;
	}
	const mode = clientOptions.mode ?? GATEWAY_CLIENT_MODES.CLI;
	const clientName = clientOptions.clientName ?? GATEWAY_CLIENT_NAMES.CLI;
	const allowAuthNone = clientOptions.requireLocalBackendSharedAuth === true && params.authMode === "none";
	const hasSharedSecretAuth = params.authMode === "token" && Boolean(params.token) || params.authMode === "password" && Boolean(params.password);
	const isLocalBackendSharedAuth = mode === GATEWAY_CLIENT_MODES.BACKEND && clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT && (hasSharedSecretAuth || allowAuthNone) && isLoopback;
	const isLocalCliSharedAuth = mode === GATEWAY_CLIENT_MODES.CLI && clientName === GATEWAY_CLIENT_NAMES.CLI && hasSharedSecretAuth && isLoopback;
	const omitDeviceIdentity = isLocalBackendSharedAuth || isLocalCliSharedAuth;
	if (clientOptions.requireLocalBackendSharedAuth && !omitDeviceIdentity) throw new GatewayLocalBackendSharedAuthUnavailableError("local backend shared auth requires a loopback gateway with token/password credentials or auth mode none");
	return {
		clientOptions,
		omitDeviceIdentity,
		deviceIdentity: opts.deviceIdentity === void 0 ? omitDeviceIdentity ? null : resolveDeviceIdentityForGatewayCall(opts.sharedStateMode) : opts.deviceIdentity
	};
}
async function loadStoredOperatorDeviceAuthToken(deviceIdentity, deviceAuthScope, sharedStateMode, env = process.env) {
	if (!deviceIdentity) return null;
	try {
		if (deviceAuthScope) return await (sharedStateMode === "read-only" ? loadOriginDeviceTokenReadOnly : loadOriginDeviceToken)({
			gatewayScope: deviceAuthScope,
			deviceId: deviceIdentity.deviceId,
			role: "operator",
			env
		});
		return await (sharedStateMode === "read-only" ? loadDeviceAuthTokenReadOnly : loadDeviceAuthToken)({
			deviceId: deviceIdentity.deviceId,
			role: "operator",
			env
		});
	} catch {
		return null;
	}
}
//#endregion
//#region src/config/gateway-public-origin.ts
function resolveGatewayPublicOrigin(config) {
	const raw = config?.gateway?.publicOrigin?.trim();
	if (!raw) return;
	try {
		const parsed = new URL(raw);
		return parsed.pathname === "/" && !parsed.search && !parsed.hash ? parsed.origin : void 0;
	} catch {
		return;
	}
}
//#endregion
//#region src/secrets/resolve-errors.ts
/** Error for failures that affect an entire configured secret provider. */
var SecretProviderResolutionError = class extends Error {
	constructor(params) {
		super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
		this.scope = "provider";
		this.name = "SecretProviderResolutionError";
		this.code = params.code;
		this.source = params.source;
		this.provider = params.provider;
	}
};
/** Error for failures limited to one SecretRef id under a provider. */
var SecretRefResolutionError = class extends Error {
	constructor(params) {
		super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
		this.scope = "ref";
		this.name = "SecretRefResolutionError";
		this.code = params.code;
		this.source = params.source;
		this.provider = params.provider;
		this.refId = params.refId;
	}
};
function isSecretResolutionError(value) {
	return value instanceof SecretProviderResolutionError || value instanceof SecretRefResolutionError;
}
/** Sanitized provider detail suitable for operator-facing diagnostics. */
function describeSecretResolutionOperatorDiagnostic(value) {
	if (value instanceof SecretRefResolutionError && value.code === "SECRET_REF_REDACTED_VALUE") return `Secret reference "${value.source}:${value.provider}:${value.refId}" resolves to a redaction placeholder`;
	if (value instanceof SecretProviderResolutionError && value.code === "SECRET_PROVIDER_PATH_SECURITY_UNVERIFIABLE") return "Windows path security could not be verified";
}
/** Sanitized recovery action suitable for operator-facing diagnostics. */
function describeSecretResolutionOperatorRecovery(value) {
	if (value instanceof SecretRefResolutionError && value.code === "SECRET_REF_REDACTED_VALUE") return "Run openclaw doctor --fix to repair a store-backed Gateway token; supply a real credential for other secrets, then restart the Gateway and reconnect or re-pair clients";
	if (!(value instanceof SecretProviderResolutionError) || value.code !== "SECRET_PROVIDER_PATH_SECURITY_UNVERIFIABLE") return;
	return value.source === "exec" ? "Restore Windows path security verification, or use an existing provider command whose owner and ACLs OpenClaw can verify" : "Restore Windows path security verification, or use an existing secret file whose owner and ACLs OpenClaw can verify";
}
function providerResolutionError(params) {
	return new SecretProviderResolutionError({
		...params,
		code: params.code ?? "SECRET_PROVIDER_UNAVAILABLE"
	});
}
function refResolutionError(params) {
	return new SecretRefResolutionError(params);
}
const REDACTED_SECRET_VALUES = /* @__PURE__ */ new Set([
	"__OPENCLAW_REDACTED__",
	"REDACTED",
	"xoxb-REDACTED",
	"xapp-REDACTED",
	"***",
	"[redacted]",
	"[REDACTED]",
	"<redacted>",
	"[REDACTED_PRIVATE_KEY]",
	"[REDACTED CREDENTIAL]"
]);
function isRedactedSecretValue(value) {
	return typeof value === "string" && REDACTED_SECRET_VALUES.has(value.trim());
}
//#endregion
//#region src/agents/workspace-dirs.ts
/** Lists unique workspace directories for configured agents and the default agent. */
function listAgentWorkspaceDirs(cfg, env = process.env) {
	const dirs = /* @__PURE__ */ new Set();
	for (const agentId of listAgentIds(cfg)) dirs.add(resolveAgentWorkspaceDir(cfg, agentId, env));
	return [...dirs];
}
//#endregion
//#region src/plugins/current-plugin-metadata-state.ts
/** Owns config identity reuse for the current immutable metadata snapshot. */
const currentPluginMetadataConfigIdentityCache = {
	add(config) {
		getProcessPluginCache().metadata.current.configIdentities.add(config);
	},
	clear() {
		getProcessPluginCache().metadata.current.configIdentities = /* @__PURE__ */ new WeakSet();
	},
	has(config) {
		return getProcessPluginCache().metadata.current.configIdentities.has(config);
	}
};
/** Stores the process-current plugin metadata snapshot and compatible config fingerprints. */
function setCurrentPluginMetadataSnapshotState(snapshot, configFingerprint, compatiblePolicyHashes, compatibleConfigFingerprints, modelIdNormalizationPolicies, owner = "operation", envFingerprint, defaultDiscoveryCompatible = false, agentWorkspaceFingerprint) {
	const state = getProcessPluginCache().metadata.current;
	state.snapshot = snapshot;
	state.owner = owner;
	state.configFingerprint = snapshot ? configFingerprint : void 0;
	state.agentWorkspaceFingerprint = snapshot ? agentWorkspaceFingerprint : void 0;
	state.envFingerprint = snapshot ? envFingerprint : void 0;
	state.defaultDiscoveryCompatible = Boolean(snapshot && defaultDiscoveryCompatible);
	state.compatiblePolicyHashes = snapshot ? compatiblePolicyHashes : void 0;
	state.compatibleConfigFingerprints = snapshot ? compatibleConfigFingerprints : void 0;
	state.revision = Symbol("plugin-metadata-snapshot");
}
/** Reads the boot inventory without importing discovery into lightweight consumers. */
function getGatewayPluginMetadataSnapshot() {
	const cache = getPluginCache();
	if (cache.kind === "process" && cache.metadata.current.owner === "gateway") return cache.metadata.current.snapshot;
}
/** Captures the current snapshot and the policies eligible for process-wide publication. */
function getCurrentPluginMetadataSnapshotState() {
	const state = getProcessPluginCache().metadata.current;
	return {
		snapshot: state.snapshot,
		owner: state.owner,
		configFingerprint: state.configFingerprint,
		agentWorkspaceFingerprint: state.agentWorkspaceFingerprint,
		envFingerprint: state.envFingerprint,
		defaultDiscoveryCompatible: state.defaultDiscoveryCompatible,
		compatiblePolicyHashes: state.compatiblePolicyHashes,
		compatibleConfigFingerprints: state.compatibleConfigFingerprints,
		revision: state.revision
	};
}
//#endregion
//#region src/state/config-machine-state.ts
function normalizeConfigMachineStateKey(key) {
	const normalized = key.trim();
	if (!normalized) throw new Error("config machine state key must not be empty");
	return normalized;
}
function readConfigMachineStateRowInDatabase(database, key) {
	if (!tableExists(database, "config_machine_state")) return;
	const db = getNodeSqliteKysely(database);
	return executeSqliteQueryTakeFirstSync(database, db.selectFrom("config_machine_state").select(["value_json", "updated_at_ms"]).where("state_key", "=", normalizeConfigMachineStateKey(key)));
}
function readConfigMachineStateWithMetadata(key, options = {}, behavior = {}) {
	const read = ({ db: database }) => {
		const row = readConfigMachineStateRowInDatabase(database, key);
		return row ? {
			value: JSON.parse(row.value_json),
			updatedAtMs: row.updated_at_ms
		} : void 0;
	};
	return behavior.artifactPreservingReadOnly ? withExistingOpenClawStateDatabaseArtifactPreservingReadOnly(read, options) : withExistingOpenClawStateDatabaseReadOnly(read, options);
}
function readConfigMachineState(key, options = {}, behavior = {}) {
	return readConfigMachineStateWithMetadata(key, options, behavior)?.value;
}
//#endregion
//#region src/plugins/install-root-context.ts
const pluginInstallRootContext = resolveGlobalSingleton(Symbol.for("openclaw.pluginInstallRootContext"), () => new AsyncLocalStorage());
/** Resolve the ordinary operator-owned plugin roots before a run redirects state. */
function resolvePluginInstallRoots(env = process.env, homedir = os.homedir) {
	const configDir = resolveConfigDir(env, homedir);
	return Object.freeze({
		extensionsDir: path.join(configDir, "extensions"),
		gitDir: path.join(configDir, "git"),
		npmDir: path.join(configDir, "npm"),
		stateDir: resolveStateDir(env, homedir)
	});
}
/** Return run-pinned install roots, or resolve the caller's ordinary roots. */
function resolveActivePluginInstallRoots(env = process.env, homedir = os.homedir) {
	return pluginInstallRootContext.getStore() ?? resolvePluginInstallRoots(env, homedir);
}
/** Artifact paths do not need the state directory's legacy-location filesystem probes. */
function resolveActivePluginInstallDir(kind, env = process.env, homedir = os.homedir) {
	const roots = pluginInstallRootContext.getStore();
	return roots ? roots[`${kind}Dir`] : path.join(resolveConfigDir(env, homedir), kind);
}
/** Return whether the current run pinned operator-owned plugin install roots. */
function hasActivePluginInstallRoots() {
	return pluginInstallRootContext.getStore() !== void 0;
}
//#endregion
//#region src/plugins/plugin-metadata-snapshot-readers.ts
const snapshotReaderSlot = resolveGlobalSingleton(Symbol.for("openclaw.pluginMetadataSnapshotReaders"), () => ({}));
resolveGlobalSingleton(Symbol.for("openclaw.pluginMetadataSnapshotReaderCustody"), () => ({ owners: 0 }));
/** Called at module evaluation; a retained Gateway keeps its registered readers. */
function registerPluginMetadataSnapshotReaders(readers) {
	Object.assign(snapshotReaderSlot, readers);
}
//#endregion
//#region src/plugins/plugin-source-capture-context.ts
const runInPluginSourceCaptureContext = resolveGlobalSingleton(Symbol.for("openclaw.pluginSourceCaptureContext"), () => AsyncLocalStorage.snapshot());
resolveGlobalSingleton(Symbol.for("openclaw.pluginSourceCaptureStateDir"), () => new AsyncLocalStorage());
resolveGlobalSingleton(Symbol.for("openclaw.pluginSourceCaptureMaintenance"), () => new AsyncLocalStorage());
//#endregion
//#region src/plugins/plugin-source-capture-native-loads.ts
/** The process-wide capture owner installs this observer once. */
function observePluginNativeLoads() {
	const paths = /* @__PURE__ */ new Set();
	const loadAddon = process.dlopen.bind(process);
	process.dlopen = (...args) => {
		try {
			const file = fsSync.realpathSync.native(args[1]);
			paths.add(process.platform === "win32" ? normalizeWindowsPathPreservingCase(file) : file);
		} catch {}
		return loadAddon(...args);
	};
	return paths;
}
//#endregion
//#region src/plugins/plugin-source-capture-directory.ts
const CAPTURE_GRACE_MS = 36e5;
const { instances, ownedRoots, nativeReferences, retiringNativeRoots, nativeLoadPaths, retainedRoots, sweeps, warningBackoff } = resolveGlobalSingleton(Symbol.for("openclaw.pluginSourceCaptureInstances"), () => {
	const observedNativePaths = observePluginNativeLoads();
	process.once("exit", () => {
		for (const [key, instance] of instances) try {
			const root = retireInstance(key, instance);
			if (root) removeInstanceSync(root, instance.pendingNative);
		} catch (error) {
			process.stderr.write(`Plugin source capture exit cleanup failed: ${String(error)}\n`);
		}
	});
	return {
		instances: /* @__PURE__ */ new Map(),
		ownedRoots: /* @__PURE__ */ new Set(),
		nativeReferences: /* @__PURE__ */ new Map(),
		retiringNativeRoots: /* @__PURE__ */ new Set(),
		nativeLoadPaths: observedNativePaths,
		retainedRoots: /* @__PURE__ */ new Set(),
		sweeps: /* @__PURE__ */ new Map(),
		warningBackoff: /* @__PURE__ */ new Map()
	};
});
function retireInstance(key, instance) {
	if (instance.root && retainLoadedPluginSourceCapture(instance.root)) {
		instance.references.clear();
		scheduleCaptureCleanup(key, instance);
		return;
	}
	instance.closing = true;
	let removalRoot = instance.root;
	try {
		instance.token?.(true);
	} catch (error) {
		if (!hasErrnoCode(error, "ENOENT")) throw error;
		instance.token?.();
		removalRoot = void 0;
	}
	if (instance.root) ownedRoots.delete(instance.root);
	instance.references.clear();
	instances.delete(key);
	instance.cleanupJob?.cancel();
	instance.detachScheduler?.();
	return removalRoot;
}
function warn(error) {
	process.emitWarning(`Plugin source capture cleanup: ${String(error)}`);
}
/** Reclamation owns an existing native token until its captured payload is gone. */
async function reclaimInstance(directory, originalDirectory, nativeMaintenance) {
	const ownerPath = path.join(directory, SQLITE_STAGING_TOKEN_FILES[0]);
	const family = SQLITE_STAGING_TOKEN_FILES.map((file) => fsSync.lstatSync(path.join(directory, file), { throwIfNoEntry: false }));
	const originalOwner = family[0];
	const captures = path.join(directory, "captures");
	const captured = fsSync.lstatSync(captures, { throwIfNoEntry: false });
	if (process.getuid && originalDirectory.uid !== process.getuid() || !originalOwner || family.some((file) => file && (!file.isFile() || file.nlink !== 1 || process.getuid && file.uid !== process.getuid())) || captured && !captured.isDirectory()) return;
	const unchanged = () => {
		const currentDirectory = fsSync.lstatSync(directory);
		const currentOwner = fsSync.lstatSync(ownerPath);
		return currentDirectory.dev === originalDirectory.dev && currentDirectory.ino === originalDirectory.ino && currentDirectory.isDirectory() && currentOwner.isFile() && currentOwner.nlink === 1 && currentOwner.dev === originalOwner.dev && currentOwner.ino === originalOwner.ino;
	};
	const release = acquireSqliteStagingToken(directory, "reclaim");
	let released = false;
	ownedRoots.add(directory);
	try {
		if (!unchanged()) return;
		const native = path.join(directory, "native");
		const nativeStat = fsSync.lstatSync(native, { throwIfNoEntry: false });
		await fsPromises.rm(captures, {
			recursive: true,
			force: true
		});
		let retainedNative = Boolean(nativeStat);
		if (nativeStat?.isDirectory() && nativeMaintenance) {
			for (const nativeEntry of await fsPromises.readdir(native, { withFileTypes: true })) {
				const nativeDirectory = path.join(native, nativeEntry.name);
				if (!nativeEntry.isDirectory()) continue;
				nativeMaintenance.assertCurrent();
				if (!unchanged()) return;
				const contained = (file) => file.startsWith(nativeDirectory + path.sep);
				if ([...nativeMaintenance.retainedPaths].some(contained) || [...nativeReferences.keys()].some(contained)) continue;
				retiringNativeRoots.add(nativeDirectory);
				try {
					await fsPromises.rm(nativeDirectory, {
						recursive: true,
						force: true
					});
					nativeMaintenance.removed.push(nativeDirectory);
				} finally {
					retiringNativeRoots.delete(nativeDirectory);
				}
			}
			retainedNative = (await fsPromises.readdir(native)).length > 0;
		}
		release(true);
		released = true;
		if (!retainedNative && unchanged()) {
			nativeMaintenance?.assertCurrent();
			await fsPromises.rm(directory, {
				recursive: true,
				force: true
			});
		}
	} finally {
		try {
			if (!released) release();
		} finally {
			ownedRoots.delete(directory);
		}
	}
}
/** Physical module lifetime outlives registration and CommonJS cache eviction. */
function retainLoadedPluginSourceCapture(directory) {
	if (![...nativeLoadPaths].some((file) => isPathInside$2(directory, file))) return false;
	const retained = [...ownedRoots].find((root) => isPathInside$2(root, directory)) ?? directory;
	if (![...retainedRoots].some((root) => isPathInside$2(root, retained) || isPathInside$2(retained, root))) warn(`retained-by-loaded-module: ${retained}; cleanup deferred until the next startup`);
	retainedRoots.add(retained);
	return true;
}
function removeInstanceSync(root, pendingNative = []) {
	if (retainLoadedPluginSourceCapture(root)) return;
	fsSync.rmSync(path.join(root, "captures"), {
		recursive: true,
		force: true
	});
	for (const directory of pendingNative) fsSync.rmSync(directory, {
		recursive: true,
		force: true
	});
	const native = path.join(root, "native");
	if (!fsSync.existsSync(native) || fsSync.readdirSync(native).length === 0) fsSync.rmSync(root, {
		recursive: true,
		force: true
	});
}
async function reclaimInstances(root, recordFailure, legacy = false, nativeMaintenance, fallbackPrefix) {
	let entries;
	try {
		entries = await fsPromises.readdir(root, { withFileTypes: true });
	} catch (error) {
		if (!hasErrnoCode(error, "ENOENT")) throw error;
		return;
	}
	if (entries.length === 0) return;
	const cutoff = Date.now() - CAPTURE_GRACE_MS;
	let legacyAllowed;
	const lstatIfPresent = (file) => fsPromises.lstat(file).catch((error) => {
		if (!hasErrnoCode(error, "ENOENT")) throw error;
	});
	for (const entry of entries) {
		if (!entry.isDirectory() || legacy && !isLegacyPluginSourceCaptureName(entry.name) || fallbackPrefix && !entry.name.startsWith(fallbackPrefix)) continue;
		const directory = path.join(root, entry.name);
		try {
			const stat = await fsPromises.lstat(directory);
			const changed = legacy ? Math.max(stat.mtimeMs, stat.ctimeMs, stat.birthtimeMs) : stat.mtimeMs;
			if (!stat.isDirectory() || changed > cutoff && !nativeMaintenance?.startup) continue;
			const canonical = await fsPromises.realpath(directory);
			if (ownedRoots.has(canonical) || retainLoadedPluginSourceCapture(canonical)) continue;
			const tokenPath = path.join(canonical, SQLITE_STAGING_TOKEN_FILES[0]);
			const nativeStat = await lstatIfPresent(path.join(canonical, "native"));
			const tokenStat = await lstatIfPresent(tokenPath);
			if (legacy && tokenStat) continue;
			if (!tokenStat) {
				if (fallbackPrefix || changed > cutoff) continue;
				if (nativeStat) continue;
				if (legacy) {
					if (legacyAllowed === void 0) {
						const { inspectOtherOpenClawProcesses } = await import("./openclaw-process-census-CjuTiGRe.mjs");
						const census = inspectOtherOpenClawProcesses();
						legacyAllowed = "error" in census || census.pids.length === 0;
					}
					if (!legacyAllowed) continue;
					if (process.getuid && (await fsPromises.lstat(canonical)).uid !== process.getuid()) continue;
				}
				const retired = path.join(root, `${legacy ? PLUGIN_SOURCE_CAPTURE_PREFIX : ""}${randomUUID()}`);
				await fsPromises.rename(canonical, retired);
				await fsPromises.rm(retired, {
					recursive: true,
					force: true
				});
				continue;
			}
			await reclaimInstance(canonical, stat, nativeMaintenance);
		} catch (error) {
			if (!hasErrnoCode(error, "ENOENT") && !isSqliteLockError(error)) recordFailure(error);
		}
	}
}
/** Coalesce active scans, but throttle diagnostics independently of cleanup retries. */
function sweepPluginSourceCaptureDirectories(stateDir) {
	const root = path.resolve(resolvePluginSourceCapturesDirectory(stateDir));
	let sweep = sweeps.get(root);
	if (!sweep) {
		let failures = 0;
		let firstFailure;
		const recordFailure = (error) => {
			if (failures++ === 0) firstFailure = error;
		};
		sweep = reclaimInstances(root, recordFailure).catch(recordFailure).then(() => reclaimInstances(tmpdir(), recordFailure, false, void 0, resolvePluginSourceCaptureFallbackPrefix(stateDir))).catch(recordFailure).then(async () => {
			const visited = /* @__PURE__ */ new Set();
			for (const candidate of [path.join(stateDir, "tmp"), tmpdir()]) try {
				const directory = await fsPromises.realpath(candidate);
				if (!visited.has(directory)) {
					visited.add(directory);
					await reclaimInstances(directory, recordFailure, true);
				}
			} catch (error) {
				if (!hasErrnoCode(error, "ENOENT")) recordFailure(error);
			}
		}).then(() => {
			if (failures === 0) {
				warningBackoff.delete(root);
				return;
			}
			const now = Date.now();
			const previous = warningBackoff.get(root);
			if (previous && now < previous.next) return;
			const delay = Math.min((previous?.delay ?? CAPTURE_GRACE_MS / 2) * 2, 24 * CAPTURE_GRACE_MS);
			if (!previous && warningBackoff.size >= 32) {
				const oldest = warningBackoff.keys().next().value;
				if (oldest !== void 0) warningBackoff.delete(oldest);
			}
			warningBackoff.set(root, {
				next: now + delay,
				delay
			});
			warn(`${failures} cleanup failure(s) in ${root}; will retry. First: ${String(firstFailure)}`);
		}).finally(() => sweeps.delete(root));
		sweeps.set(root, sweep);
	}
	return sweep;
}
function scheduleCaptureCleanup(key, instance) {
	const scheduler = [...instance.references].findLast((reference) => reference.scheduler && !reference.scheduler.signal.aborted)?.scheduler ?? void 0;
	if (instance.scheduler === scheduler) return;
	instance.detachScheduler?.();
	instance.cleanupJob?.cancel();
	instance.scheduler = scheduler;
	instance.cleanupJob = void 0;
	instance.detachScheduler = void 0;
	if (!scheduler) return;
	const rebind = () => scheduleCaptureCleanup(key, instance);
	scheduler.signal.addEventListener("abort", rebind, { once: true });
	instance.detachScheduler = () => scheduler.signal.removeEventListener("abort", rebind);
	instance.cleanupJob = runInPluginSourceCaptureContext(() => scheduler.schedule({
		id: `plugin-source-captures:${key}`,
		delayMs: CAPTURE_GRACE_MS,
		everyMs: CAPTURE_GRACE_MS,
		run: () => sweepPluginSourceCaptureDirectories(key)
	}));
}
resolveGlobalSingleton(Symbol.for("openclaw.pluginRuntimeCloseRetainedError"), () => class RetainedRuntimeError extends Error {
	constructor(cause) {
		super("Plugin runtime still owns resources; inspect cleanup failures before retrying Gateway close.", { cause });
		this.name = "PluginRuntimeCloseRetainedError";
	}
});
//#endregion
//#region src/plugins/plugin-metadata-lifecycle.ts
const pluginMetadataProcessMemoClears = /* @__PURE__ */ new Map();
resolveGlobalSingleton(Symbol.for("openclaw.gatewayPluginMetadataOwners"), () => /* @__PURE__ */ new Set());
/** Registers a metadata clear hook with the lifetime of its facts. */
function registerPluginMetadataProcessMemoLifecycleClear(clearProcessMemo, options = { owner: "process" }) {
	pluginMetadataProcessMemoClears.set(clearProcessMemo, options.owner);
}
//#endregion
//#region src/plugins/installed-plugin-index-row.ts
const INSTALLED_PLUGIN_INDEX_STATE_KEY = "plugins.installedIndex";
/** Shared inspection commands use the same existing-only, artifact-preserving reader. */
function readPluginMetadataStateRowSync(selector, databaseOptions, artifactPreservingReadOnly = false) {
	const row = readPluginMetadataStateRowsSync([selector === "installed-index" ? INSTALLED_PLUGIN_INDEX_STATE_KEY : "plugins.bundledDiscovery"], databaseOptions, artifactPreservingReadOnly)[0];
	return row ? { value_json: row.value_json } : void 0;
}
/** Acquire related metadata facts from the same prepared database bytes. */
function readPluginMetadataStateRowsSync(stateKeys, databaseOptions, artifactPreservingReadOnly = false) {
	const read = ({ db }) => {
		if (!tableExists(db, "config_machine_state")) return [];
		return executeSqliteQuerySync(db, getNodeSqliteKysely(db).selectFrom("config_machine_state").select(["state_key", "value_json"]).where("state_key", "in", stateKeys)).rows;
	};
	return (artifactPreservingReadOnly ? withExistingOpenClawStateDatabaseArtifactPreservingReadOnly(read, databaseOptions) : withExistingOpenClawStateDatabaseReadOnly(read, databaseOptions)) ?? [];
}
//#endregion
//#region src/plugins/bundled-discovery-state.ts
function parseBundledDiscoveryMode(value) {
	return value === "compat" || value === "allowlist" ? value : void 0;
}
function readBundledDiscoveryFact(read) {
	try {
		return read();
	} catch (error) {
		if (isStateDatabaseReadAdmissionInvalidatedError(error)) throw new PluginCacheFactInvalidatedError("Plugin discovery read admission changed during preparation; retry the operation.", { cause: error });
		throw error;
	}
}
function resolveBundledDiscoveryOptions(options) {
	return options.path || options.database || !hasActivePluginInstallRoots() ? options : {
		...options,
		env: {
			...options.env ?? process.env,
			OPENCLAW_STATE_DIR: resolveActivePluginInstallRoots(options.env).stateDir
		}
	};
}
function readBundledDiscoveryMode(options = {}, behavior = {}) {
	const resolvedOptions = resolveBundledDiscoveryOptions(options);
	return parseBundledDiscoveryMode(readBundledDiscoveryFact(() => readConfigMachineState("plugins.bundledDiscovery", resolvedOptions, behavior)));
}
const discoveryState = resolveGlobalSingleton(Symbol.for("openclaw.bundledDiscoveryMode"), () => ({
	generation: {},
	snapshotModes: /* @__PURE__ */ new WeakMap()
}));
registerPluginMetadataProcessMemoLifecycleClear(() => {
	clearBundledDiscoveryModeMemo();
});
function resolveBundledDiscoveryMemoKey(env) {
	const scopedEnv = hasActivePluginInstallRoots() ? {
		...env,
		OPENCLAW_STATE_DIR: resolveActivePluginInstallRoots(env).stateDir
	} : env;
	return resolveOpenClawStateSqlitePath(scopedEnv);
}
/**
* Callers loading a registry with an explicit env pass it so the mode comes
* from that env's state root; omitting it reads the process root. Pinned
* install roots win over both, matching readBundledDiscoveryMode.
*/
function readBundledDiscoveryModeMemoized(env = process.env, behavior = {}, readPreparedValue) {
	const options = env === process.env ? {} : { env };
	const snapshot = behavior.artifactPreservingReadOnly || isArtifactPreservingStateRead() ? readBundledDiscoveryFact(() => getActiveOpenClawStateDatabaseReadSnapshot(resolveBundledDiscoveryOptions(options))) : void 0;
	if (snapshot || behavior.artifactPreservingReadOnly) {
		const prepared = snapshot && discoveryState.snapshotModes.get(snapshot);
		if (prepared) return prepared.value;
		const value = readBundledDiscoveryMode(options, behavior);
		if (snapshot) discoveryState.snapshotModes.set(snapshot, { value });
		return value;
	}
	const key = resolveBundledDiscoveryMemoKey(env);
	if (discoveryState.memoized?.key !== key) {
		const owner = getPluginCache();
		const prepared = owner.preparedBundledDiscoveryModes.get(key);
		if (prepared && "value" in prepared && prepared.value.generation === discoveryState.generation) {
			getPluginCacheRetirementSignal(owner).throwIfAborted();
			discoveryState.memoized = {
				key,
				value: prepared.value.value
			};
		} else discoveryState.memoized = {
			key,
			value: readPreparedValue ? parseBundledDiscoveryMode(readBundledDiscoveryFact(() => readPreparedValue(key))) : readBundledDiscoveryMode(env === process.env ? {} : { env })
		};
	}
	return discoveryState.memoized.value;
}
/**
* Clears the memo after a machine-state write so same-process readers observe
* the new mode. Without this, doctor's migration could cache the pre-migration
* absent mode and rebuild plugin indexes against stale strict-gate decisions.
*/
function clearBundledDiscoveryModeMemo() {
	discoveryState.memoized = void 0;
	discoveryState.snapshotModes = /* @__PURE__ */ new WeakMap();
	discoveryState.generation = {};
	for (const cache of /* @__PURE__ */ new Set([getPluginCache(), getProcessPluginCache()])) cache.preparedBundledDiscoveryModes.clear();
}
//#endregion
//#region src/plugins/compat/deprecation-marking.ts
const MARKING_DATE = "2026-07-25";
const DEPRECATION_MARKING = {
	status: "removal-pending",
	introduced: MARKING_DATE,
	deprecated: MARKING_DATE,
	warningStarts: MARKING_DATE,
	removeAfter: "2026-10-01"
};
/** The elapsed window does not replace each family's required reader-migration proof. */
const DEPRECATION_MARKING_COMPAT_RECORDS = [
	{
		code: "plugin-sdk-channel-setup-input-fields",
		...DEPRECATION_MARKING,
		owner: "channel",
		replacement: "plugin-local setup input intersections that declare each owning channel field; retain each field until a new published-plugin artifact sweep finds no reader",
		docsPath: "/plugins/sdk-migration#published-channel-setup-compatibility",
		surfaces: [
			"ChannelSetupInput.privateKey",
			"ChannelSetupInput.secret",
			"ChannelSetupInput.botToken",
			"ChannelSetupInput.appToken",
			"ChannelSetupInput.signingSecret",
			"ChannelSetupInput.mode",
			"ChannelSetupInput.cliPath",
			"ChannelSetupInput.authDir",
			"ChannelSetupInput.httpUrl",
			"ChannelSetupInput.httpPort",
			"ChannelSetupInput.webhookPath",
			"ChannelSetupInput.webhookUrl",
			"ChannelSetupInput.userId",
			"ChannelSetupInput.accessToken",
			"ChannelSetupInput.password",
			"ChannelSetupInput.deviceName",
			"ChannelSetupInput.url",
			"ChannelSetupInput.baseUrl",
			"ChannelSetupInput.code",
			"ChannelSetupInput.groupChannels",
			"ChannelSetupInput.dmAllowlist",
			"ChannelSetupInput.autoDiscoverChannels"
		],
		diagnostics: ["TypeScript @deprecated annotations on the reader-backed ChannelSetupInput compatibility tier", "published-plugin artifact reader sweep required before field removal"],
		tests: ["src/plugin-sdk/channel-setup.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "ChannelSetupInput keeps its reader-backed channel fields through the dated migration window while plugins move them into plugin-local input types."
	},
	{
		code: "plugin-sdk-broad-runtime-barrels",
		...DEPRECATION_MARKING,
		owner: "sdk",
		replacement: "focused plugin SDK subpaths for each runtime capability; retain until bundled and published plugins no longer import any of the seven broad barrels",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"openclaw/plugin-sdk/agent-runtime",
			"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.useCache",
			"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.cacheOnly",
			"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.metadataSnapshot",
			"openclaw/plugin-sdk/agent-runtime loadModelCatalog",
			"openclaw/plugin-sdk/cli-runtime",
			"openclaw/plugin-sdk/conversation-runtime",
			"openclaw/plugin-sdk/hook-runtime",
			"openclaw/plugin-sdk/media-runtime",
			"openclaw/plugin-sdk/media-runtime buildAgentMediaPayload",
			"openclaw/plugin-sdk/plugin-runtime",
			"openclaw/plugin-sdk/security-runtime"
		],
		diagnostics: ["TypeScript @deprecated annotations on broad plugin SDK barrels", "plugin boundary report compatibility inventory"],
		tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "Broad agent, CLI, conversation, hook, media, plugin, and security runtime barrels remain available while bundled and external plugins migrate to focused subpaths."
	},
	{
		code: "plugin-sdk-provider-owned-helper-shims",
		...DEPRECATION_MARKING,
		owner: "provider",
		replacement: "provider-local auth, model, replay, OAuth, and stream helper APIs; retain until every helper is migrated in official providers and absent from published plugins",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"openclaw/plugin-sdk/provider-stream GOOGLE_THINKING_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream KILOCODE_THINKING_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream MOONSHOT_THINKING_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream MINIMAX_FAST_MODE_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream OPENAI_RESPONSES_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream OPENROUTER_THINKING_STREAM_HOOKS",
			"openclaw/plugin-sdk/provider-stream TOOL_STREAM_DEFAULT_ON_HOOKS",
			"openclaw/plugin-sdk/provider-stream-shared defaultToolStreamExtraParams",
			"openclaw/plugin-sdk/provider-stream-shared stripTrailingAnthropicAssistantPrefillWhenThinking",
			"openclaw/plugin-sdk/provider-stream-shared createAnthropicThinkingPrefillPayloadWrapper",
			"openclaw/plugin-sdk/provider-stream-shared OpenAICompatibleThinkingLevel",
			"openclaw/plugin-sdk/provider-stream-shared isOpenAICompatibleThinkingEnabled",
			"openclaw/plugin-sdk/provider-stream-shared DeepSeekV4ThinkingLevel",
			"openclaw/plugin-sdk/provider-stream-shared DeepSeekV4ReasoningEffort",
			"openclaw/plugin-sdk/provider-stream-shared createDeepSeekV4OpenAICompatibleThinkingWrapper",
			"openclaw/plugin-sdk/provider-stream-shared createThinkingOnlyFinalTextWrapper",
			"openclaw/plugin-sdk/provider-stream-shared createGoogleThinkingPayloadWrapper",
			"openclaw/plugin-sdk/provider-stream-shared createGoogleThinkingStreamWrapper",
			"openclaw/plugin-sdk/provider-model-shared isProxyReasoningUnsupportedModelHint",
			"openclaw/plugin-sdk/provider-model-shared OPENAI_COMPATIBLE_REPLAY_HOOKS",
			"openclaw/plugin-sdk/provider-model-shared ANTHROPIC_BY_MODEL_REPLAY_HOOKS",
			"openclaw/plugin-sdk/provider-model-shared NATIVE_ANTHROPIC_REPLAY_HOOKS",
			"openclaw/plugin-sdk/provider-model-shared PASSTHROUGH_GEMINI_REPLAY_HOOKS",
			"openclaw/plugin-sdk/provider-auth DEFAULT_COPILOT_API_BASE_URL",
			"openclaw/plugin-sdk/provider-auth deriveCopilotApiBaseUrlFromToken",
			"openclaw/plugin-sdk/provider-auth resolveCopilotApiToken",
			"openclaw/plugin-sdk/provider-auth-copilot-cache CachedCopilotToken",
			"openclaw/plugin-sdk/oauth-utils toFormUrlEncoded",
			"openclaw/plugin-sdk/oauth-utils generatePkceVerifierChallenge",
			"openclaw/plugin-sdk/provider-oauth-runtime OAuthProvider",
			"openclaw/plugin-sdk/provider-oauth-runtime OAuthProviderInfo"
		],
		diagnostics: ["TypeScript @deprecated annotations naming provider-local replacements", "plugin boundary report compatibility inventory"],
		tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "Provider-specific auth, model, replay, OAuth, and stream shortcuts remain as deprecated SDK shims while providers move to their local APIs."
	},
	{
		code: "message-presentation-legacy-bridges",
		...DEPRECATION_MARKING,
		owner: "channel",
		replacement: "MessagePresentation values and channel presentation renderers; retain until reply producers and official channel packages no longer emit or read legacy interactive replies",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"InteractiveReplyButton.value",
			"InteractiveReplyButton.url",
			"InteractiveReplyButton.webApp",
			"InteractiveReplyButton.web_app",
			"InteractiveReplyOption.value",
			"InteractiveReplyButton",
			"InteractiveReplyOption",
			"InteractiveReplyBlock",
			"InteractiveReply",
			"normalizeInteractiveReply",
			"hasInteractiveReplyBlocks",
			"presentationToInteractiveReply",
			"presentationToInteractiveControlsReply",
			"interactiveReplyToPresentation",
			"resolveInteractiveTextFallback",
			"src/auto-reply ReplyPayload.interactive",
			"openclaw/plugin-sdk/reply-payload ReplyPayload.interactive",
			"reduceInteractiveReply",
			"@openclaw/discord buildDiscordInteractiveComponents",
			"@openclaw/slack buildSlackInteractiveBlocks",
			"@openclaw/telegram buildTelegramInteractiveButtons"
		],
		diagnostics: ["TypeScript @deprecated annotations naming MessagePresentation replacements", "plugin boundary report compatibility inventory"],
		tests: [
			"src/interactive/payload.test.ts",
			"src/plugin-sdk/reply-payload.test.ts",
			"src/plugins/compat/registry.test.ts"
		],
		releaseNote: "Legacy interactive reply values and channel-specific rendering bridges remain available while producers migrate to MessagePresentation."
	},
	{
		code: "plugin-sdk-focused-compat-aliases",
		...DEPRECATION_MARKING,
		owner: "sdk",
		replacement: "the focused replacement named by each TypeScript @deprecated annotation; retain until every enumerated alias has zero bundled and published readers",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"openclaw/plugin-sdk/acp-runtime __testing",
			"openclaw/plugin-sdk/approval-reaction-runtime",
			"openclaw/plugin-sdk/channel-inbound BuildChannelTurnContextParams",
			"openclaw/plugin-sdk/channel-inbound BuiltChannelTurnContext",
			"openclaw/plugin-sdk/channel-inbound buildChannelTurnContext",
			"openclaw/plugin-sdk/channel-inbound finalizeChannelInboundContext",
			"openclaw/plugin-sdk/channel-inbound filterChannelTurnSupplementalContext",
			"openclaw/plugin-sdk/channel-send-result ChannelSendRawResult",
			"openclaw/plugin-sdk/command-auth",
			"openclaw/plugin-sdk/command-auth ResolveSenderCommandAuthorizationParams",
			"openclaw/plugin-sdk/command-auth resolveCommandAuthorizedFromAuthorizers",
			"openclaw/plugin-sdk/command-auth CommandAuthorizationRuntime",
			"openclaw/plugin-sdk/command-auth ResolveSenderCommandAuthorizationWithRuntimeParams",
			"openclaw/plugin-sdk/command-auth resolveDirectDmAuthorizationOutcome",
			"openclaw/plugin-sdk/command-auth resolveSenderCommandAuthorizationWithRuntime",
			"openclaw/plugin-sdk/command-auth resolveSenderCommandAuthorization",
			"openclaw/plugin-sdk/keyed-async-queue KeyedAsyncQueue.getTailMapForTesting",
			"openclaw/plugin-sdk/persistent-dedupe PersistentDedupeLegacyPathOptions.lockOptions",
			"openclaw/plugin-sdk/retry-runtime createTelegramRetryRunner",
			"openclaw/plugin-sdk/ssrf-policy SsrfPolicyOptions.allowPrivateNetwork",
			"openclaw/plugin-sdk/ssrf-policy ssrfPolicyFromAllowPrivateNetwork",
			"openclaw/plugin-sdk/tts-runtime TtsSynthesisStreamResult",
			"openclaw/plugin-sdk/tts-runtime TtsRuntimeFacade._test"
		],
		diagnostics: ["TypeScript @deprecated annotations naming focused replacements", "plugin boundary report compatibility inventory"],
		tests: [
			"src/plugin-sdk/channel-inbound.test.ts",
			"src/plugin-sdk/command-auth.test.ts",
			"src/plugin-sdk/ssrf-policy.test.ts",
			"src/plugins/compat/registry.test.ts"
		],
		releaseNote: "Focused SDK compatibility aliases remain available through a dated window while callers adopt their annotated replacements."
	},
	{
		code: "agent-harness-terminal-result-aliases",
		...DEPRECATION_MARKING,
		owner: "agent-runtime",
		replacement: "AgentHarnessAttemptResult.terminal and AgentHarnessDeliveryDefaults.visibleReplies; retain until harness migration verifies that legacy terminal fields and sourceVisibleReplies are unread",
		docsPath: "/plugins/sdk-agent-harness",
		surfaces: [
			"AgentHarnessAttemptResult.aborted",
			"AgentHarnessAttemptResult.externalAbort",
			"AgentHarnessAttemptResult.timedOut",
			"AgentHarnessAttemptResult.idleTimedOut",
			"AgentHarnessAttemptResult.timedOutDuringCompaction",
			"AgentHarnessAttemptResult.timedOutDuringToolExecution",
			"AgentHarnessAttemptResult.timedOutByRunBudget",
			"AgentHarnessAttemptResult.promptError",
			"AgentHarnessAttemptResult.promptErrorSource",
			"AgentHarnessDeliveryDefaults.sourceVisibleReplies"
		],
		diagnostics: ["TypeScript @deprecated annotations on agent harness result and delivery defaults", "plugin boundary report compatibility inventory"],
		tests: ["src/agents/harness/settled-turn-finalization-result.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "Agent harness result booleans and sourceVisibleReplies remain available while harnesses migrate to terminal outcomes and visibleReplies."
	},
	{
		code: "official-plugin-export-aliases",
		...DEPRECATION_MARKING,
		owner: "channel",
		replacement: "the canonical testing export, MessagePresentation renderers, and host-owned timeout/runtime behavior; retain until minimum supported official plugin packages no longer import these aliases",
		docsPath: "/plugins/compatibility#current-compatibility-areas",
		surfaces: [
			"@openclaw/google-meet __testing",
			"@openclaw/discord buildDiscordInteractiveComponents",
			"@openclaw/discord normalizeDiscordListenerTimeoutMs",
			"@openclaw/discord normalizeDiscordInboundWorkerTimeoutMs",
			"@openclaw/discord isAbortError",
			"@openclaw/discord runDiscordTaskWithTimeout",
			"@openclaw/slack buildSlackInteractiveBlocks"
		],
		diagnostics: ["TypeScript @deprecated annotations on published official-plugin exports", "plugin boundary report compatibility inventory"],
		tests: ["extensions/google-meet/index.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "Published Google Meet testing, channel presentation, and Discord timeout aliases remain available while consumers move to their canonical exports and host-owned behavior."
	},
	{
		code: "memory-host-compatibility-aliases",
		...DEPRECATION_MARKING,
		owner: "sdk",
		replacement: "canonical memory cache/FTS tables and getRuntimeConfig or caller-provided config; retain until memory integrations are verified to use canonical tables and prepared runtime config without these aliases",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"@openclaw/memory-host-sdk ensureMemoryIndexSchema.embeddingCacheTable",
			"@openclaw/memory-host-sdk ensureMemoryIndexSchema.ftsTable",
			"@openclaw/memory-host-sdk/runtime-core loadConfig",
			"@openclaw/memory-host-sdk/host/openclaw-runtime loadConfig"
		],
		diagnostics: ["TypeScript @deprecated annotations on memory-host SDK compatibility fields", "plugin boundary report memory-host SDK summary"],
		tests: ["packages/memory-host-sdk/src/host/memory-schema.test.ts", "src/plugins/compat/registry.test.ts"],
		releaseNote: "Memory-host cache-table overrides and runtime config reload aliases remain available while callers migrate to canonical tables and prepared config."
	},
	{
		code: "plugin-runtime-api-compat-aliases",
		...DEPRECATION_MARKING,
		owner: "plugin-execution",
		replacement: "the namespaced plugin API and focused runtime methods named per surface; retain until all enumerated flat API and runtime aliases have no readers",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"OpenClawPluginApi.registerSessionExtension",
			"OpenClawPluginApi.enqueueNextTurnInjection",
			"OpenClawPluginApi.registerControlUiDescriptor",
			"OpenClawPluginApi.registerRuntimeLifecycle",
			"OpenClawPluginApi.registerAgentEventSubscription",
			"OpenClawPluginApi.emitAgentEvent",
			"OpenClawPluginApi.setRunContext",
			"OpenClawPluginApi.getRunContext",
			"OpenClawPluginApi.clearRunContext",
			"OpenClawPluginApi.registerSessionSchedulerJob",
			"OpenClawPluginApi.registerSessionAction",
			"OpenClawPluginApi.sendSessionAttachment",
			"OpenClawPluginApi.scheduleSessionTurn",
			"OpenClawPluginApi.unscheduleSessionTurnsByTag",
			"PluginHookContext.senderExternalId",
			"PluginAttachmentChannelHints.telegram",
			"PluginAttachmentChannelHints.slack",
			"AgentPromptSurfaceKind pi_main",
			"PluginRuntime.channel.reply.createReplyDispatcherWithTyping",
			"PluginRuntime.channel.reply.resolveHumanDelayConfig",
			"PluginRuntime.channel.reply.dispatchReplyFromConfig",
			"PluginRuntime.channel.reply.finalizeInboundContext",
			"PluginRuntime.channel.media.fetchRemoteMedia",
			"PluginRuntime.channel.session.resolveStorePath",
			"PluginRuntime.channel.session.recordInboundSession",
			"PluginRuntime.channel.inbound.runPreparedReply",
			"PluginRuntime.channel.turn",
			"PluginRuntime.system.requestHeartbeatNow"
		],
		diagnostics: ["TypeScript @deprecated annotations on plugin API and runtime aliases", "plugin boundary report compatibility inventory"],
		tests: [
			"src/plugins/captured-registration.test.ts",
			"src/plugins/runtime/index.test.ts",
			"src/plugins/compat/registry.test.ts"
		],
		releaseNote: "Flat plugin registration and broad runtime aliases remain available while plugins migrate to namespaced APIs and focused runtime methods."
	},
	{
		code: "plugin-provider-manifest-compat-aliases",
		...DEPRECATION_MARKING,
		owner: "provider",
		replacement: "manifest-owned plugin kind/setup metadata and model catalog registration; retain until providers no longer publish runtime kind or legacy catalog hooks",
		docsPath: "/plugins/sdk-migration#compatibility-policy",
		surfaces: [
			"DefinePluginEntryOptions.kind",
			"SingleProviderPluginOptions.kind",
			"OpenClawPluginDefinition.kind",
			"PluginPackageChannel.cliAddOptions",
			"ProviderPlugin.catalog",
			"ProviderPlugin.staticCatalog",
			"ProviderPlugin.suppressBuiltInModel",
			"ProviderPlugin.augmentModelCatalog",
			"ProviderBuiltInModelSuppressionContext"
		],
		diagnostics: ["TypeScript @deprecated annotations on plugin manifest and provider catalog aliases", "plugin boundary report compatibility inventory"],
		tests: [
			"src/plugins/contracts/package-manifest.contract.test.ts",
			"src/plugins/contracts/provider-catalog-deprecation.contract.test.ts",
			"src/plugins/compat/registry.test.ts"
		],
		releaseNote: "Runtime plugin kind/setup metadata and provider catalog hooks remain available while plugins migrate ownership into manifests and catalog registrations."
	}
];
//#endregion
//#region src/plugins/compat/plugin-sdk-subpath-records.ts
const PLUGIN_SDK_SUBPATH_SEEDS = [
	{
		code: "plugin-sdk-channel-streaming-subpath",
		subpath: "channel-streaming",
		status: "removed",
		owner: "channel",
		replacement: "`openclaw/plugin-sdk/channel-outbound`",
		releaseNote: "The deprecated `channel-streaming` Plugin SDK subpath was removed; plugins now import channel streaming helpers from `channel-outbound`."
	},
	{
		code: "plugin-sdk-config-runtime-subpath",
		subpath: "config-runtime",
		status: "removal-pending",
		owner: "config",
		removeAfter: "2026-10-01",
		replacement: "`api.pluginConfig`, `openclaw/plugin-sdk/config-mutation`, `openclaw/plugin-sdk/runtime-config-snapshot`, and `openclaw/plugin-sdk/config-contracts`; retain until supported external plugin migration is verified"
	},
	{
		code: "plugin-sdk-inbound-reply-dispatch-subpath",
		subpath: "inbound-reply-dispatch",
		owner: "channel",
		removalGate: "next-plugin-sdk-major",
		replacement: "`openclaw/plugin-sdk/channel-inbound` and `openclaw/plugin-sdk/channel-outbound`"
	},
	{
		code: "plugin-sdk-channel-reply-pipeline-subpath",
		subpath: "channel-reply-pipeline",
		status: "removal-pending",
		owner: "channel",
		removeAfter: "2026-10-01",
		replacement: "`openclaw/plugin-sdk/channel-outbound`; retain until supported external plugin migration is verified"
	},
	{
		code: "plugin-sdk-infra-runtime-subpath",
		subpath: "infra-runtime",
		status: "removal-pending",
		owner: "sdk",
		removeAfter: "2026-10-01",
		replacement: "focused subpaths including `openclaw/plugin-sdk/delivery-queue-runtime`, `openclaw/plugin-sdk/diagnostic-runtime`, `openclaw/plugin-sdk/error-runtime`, `openclaw/plugin-sdk/exec-approvals-runtime`, `openclaw/plugin-sdk/fetch-runtime`, and `openclaw/plugin-sdk/ssrf-runtime`; retain until supported external plugin migration is verified and system-event snapshot inspection and consumption have a modern public replacement"
	},
	{
		code: "plugin-sdk-text-runtime-subpath",
		subpath: "text-runtime",
		status: "removed",
		owner: "sdk",
		replacement: "`openclaw/plugin-sdk/logging-core`, `openclaw/plugin-sdk/text-chunking`, `openclaw/plugin-sdk/text-utility-runtime`, and `openclaw/plugin-sdk/string-coerce-runtime`",
		releaseNote: "The deprecated `text-runtime` Plugin SDK facade was removed; plugins now import logging, chunking, text utility, and string coercion helpers from their focused subpaths."
	},
	{
		code: "plugin-sdk-channel-secret-runtime-subpath",
		subpath: "channel-secret-runtime",
		status: "removed",
		owner: "channel",
		replacement: "`openclaw/plugin-sdk/channel-secret-basic-runtime` and `openclaw/plugin-sdk/channel-secret-tts-runtime`",
		releaseNote: "The deprecated `channel-secret-runtime` Plugin SDK subpath was removed; plugins now use the focused basic and TTS secret-runtime subpaths."
	},
	{
		code: "plugin-sdk-agent-config-primitives-subpath",
		subpath: "agent-config-primitives",
		status: "removed",
		owner: "config",
		replacement: "`openclaw/plugin-sdk/channel-config-schema`",
		releaseNote: "The deprecated `agent-config-primitives` Plugin SDK subpath was removed; plugins now use maintained config-schema primitives."
	},
	{
		code: "plugin-sdk-matrix-subpath",
		subpath: "matrix",
		status: "removed",
		owner: "channel",
		replacement: "`openclaw/plugin-sdk/run-command`",
		releaseNote: "The deprecated `matrix` Plugin SDK facade was removed; command execution now uses the generic `run-command` subpath."
	},
	{
		code: "plugin-sdk-channel-logging-subpath",
		subpath: "channel-logging",
		status: "removed",
		owner: "channel",
		replacement: "`openclaw/plugin-sdk/channel-inbound` and `openclaw/plugin-sdk/channel-outbound`",
		releaseNote: "The deprecated `channel-logging` Plugin SDK subpath was removed; channel logging helpers now come from the inbound and outbound channel surfaces."
	},
	{
		code: "plugin-sdk-channel-lifecycle-subpath",
		subpath: "channel-lifecycle",
		status: "removal-pending",
		owner: "channel",
		removeAfter: "2026-10-01",
		replacement: "`openclaw/plugin-sdk/channel-outbound`; retain until supported external plugin migration is verified"
	},
	{
		code: "plugin-sdk-channel-message-subpath",
		subpath: "channel-message",
		status: "removal-pending",
		owner: "channel",
		removeAfter: "2026-10-01",
		replacement: "`openclaw/plugin-sdk/channel-outbound` and `openclaw/plugin-sdk/channel-inbound`; retain until supported external plugin migration is verified"
	},
	{
		code: "plugin-sdk-group-access-subpath",
		subpath: "group-access",
		status: "removed",
		owner: "channel",
		replacement: "`openclaw/plugin-sdk/channel-ingress-runtime`",
		releaseNote: "The deprecated `group-access` Plugin SDK subpath was removed; plugins now resolve message admission through `channel-ingress-runtime`."
	},
	{
		code: "plugin-sdk-zod-subpath",
		subpath: "zod",
		status: "removed",
		owner: "sdk",
		replacement: "the direct `zod` package import",
		releaseNote: "The deprecated `zod` Plugin SDK re-export was removed; plugins now import `zod` directly."
	}
];
function buildPluginSdkSubpathRecord(seed) {
	const shared = {
		code: seed.code,
		owner: seed.owner,
		introduced: "2026-07-06",
		replacement: seed.replacement,
		docsPath: "/plugins/sdk-migration",
		surfaces: [`openclaw/plugin-sdk/${seed.subpath}`],
		tests: ["src/plugins/compat/registry.test.ts"]
	};
	if ("status" in seed && seed.status === "removed") return {
		...shared,
		status: seed.status,
		diagnostics: ["plugin SDK compatibility registry and migration guide"],
		releaseNote: seed.releaseNote
	};
	return {
		...shared,
		status: "status" in seed ? seed.status : "deprecated",
		deprecated: "2026-07-06",
		warningStarts: "2026-07-06",
		removeAfter: "removeAfter" in seed ? seed.removeAfter : void 0,
		removalGate: "removalGate" in seed ? seed.removalGate : void 0,
		diagnostics: ["repository deprecated API usage guard for core and bundled plugins; no external runtime import warning"]
	};
}
const PLUGIN_SDK_SUBPATH_RECORDS = PLUGIN_SDK_SUBPATH_SEEDS.map(buildPluginSdkSubpathRecord);
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS = [
	{
		subpath: "media-understanding",
		status: "removal-pending",
		removeAfter: "2026-09-30",
		replacement: "`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`; retain the public subpath through the 2026-09-30 window while official plugin consumers migrate",
		docsPath: "/plugins/architecture"
	},
	{
		subpath: "memory-host-core",
		status: "removal-pending",
		removeAfter: "2026-09-30",
		replacement: "host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade through the 2026-09-30 window and until a focused public-artifact read seam exists",
		docsPath: "/plugins/architecture-internals#context-engine-plugins"
	},
	{
		subpath: "plugin-config-runtime",
		status: "removal-pending",
		removeAfter: "2026-12-01",
		replacement: "`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths; retain the public subpath through the 2026-12-01 window while official plugin consumers migrate",
		docsPath: "/plugins/sdk-runtime"
	},
	{
		subpath: "tool-plugin",
		status: "deprecated",
		replacement: "retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`; `getToolPluginMetadata` currently reads metadata only from an already-executed entry",
		docsPath: "/plugins/tool-plugins"
	}
];
function buildPublicSdkSubpathRecord({ subpath, ...compat }) {
	return {
		code: `plugin-sdk-${subpath}-public-demotion`,
		owner: "sdk",
		introduced: "2026-07-15",
		deprecated: "2026-07-15",
		warningStarts: "2026-07-15",
		...compat,
		surfaces: [`openclaw/plugin-sdk/${subpath}`],
		diagnostics: ["registry-backed public SDK demotion window; no external runtime import warning"],
		tests: ["src/plugins/compat/registry.test.ts"]
	};
}
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS.map(buildPublicSdkSubpathRecord);
//#endregion
//#region src/plugins/compat/registry-records.ts
const ACTIVATION_HINT_METADATA = {
	status: "active",
	owner: "plugin-execution",
	introduced: "2026-04-24",
	docsPath: "/plugins/manifest",
	diagnostics: ["activation plan compat reason"],
	tests: ["src/plugins/activation-planner.test.ts"]
};
[
	...PLUGIN_SDK_SUBPATH_RECORDS,
	...BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS,
	...DEPRECATION_MARKING_COMPAT_RECORDS,
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA },
	{ ...ACTIVATION_HINT_METADATA }
];
//#endregion
//#region src/plugins/plugin-policy-id.ts
/**
* Canonicalizes a plugin id for comparison against `plugins.allow`, `plugins.deny`, and
* `plugins.entries`, which are lowercase-normalized when config is normalized. A manifest declares
* its id in whatever case its author chose, so policy must compare this derived key rather than the
* declared id. The declared id itself stays untouched: the loader matches it against the plugin's
* runtime export id, and rewriting it would break plugins whose export matches a mixed-case manifest.
*/
function normalizePluginPolicyId(id) {
	return normalizeOptionalLowercaseString(id) ?? "";
}
//#endregion
//#region src/plugins/config-activation-shared.ts
const PLUGIN_ACTIVATION_REASON_BY_CAUSE = {
	"enabled-in-config": "enabled in config",
	"bundled-channel-enabled-in-config": "channel enabled in config",
	"selected-memory-slot": "selected memory slot",
	"selected-context-engine-slot": "selected context engine slot",
	"selected-in-allowlist": "selected in allowlist",
	"plugins-disabled": "plugins disabled",
	"blocked-by-denylist": "blocked by denylist",
	"disabled-in-config": "disabled in config",
	"channel-disabled-in-config": "channel disabled in config",
	"workspace-disabled-by-default": "workspace plugin (disabled by default)",
	"not-in-allowlist": "not in allowlist",
	"enabled-by-effective-config": "enabled by effective config",
	"bundled-channel-configured": "channel configured",
	"bundled-default-enablement": "bundled default enablement",
	"bundled-disabled-by-default": "bundled (disabled by default)"
};
function toPluginActivationState(decision) {
	return {
		enabled: decision.enabled,
		activated: decision.activated,
		explicitlyEnabled: decision.explicitlyEnabled,
		source: decision.source,
		reason: decision.reason || (decision.cause ? PLUGIN_ACTIVATION_REASON_BY_CAUSE[decision.cause] : void 0)
	};
}
function resolveExplicitPluginSelectionShared(params) {
	const policyId = normalizePluginPolicyId(params.id);
	if (params.config.entries[policyId]?.enabled === true) return {
		explicitlyEnabled: true,
		cause: "enabled-in-config"
	};
	if (params.origin === "bundled" && params.resolveChannelConfigEnablement(params.rootConfig, params.id, params.channelIds) === true) return {
		explicitlyEnabled: true,
		cause: "bundled-channel-enabled-in-config"
	};
	if (params.config.slots.memory === params.id) return {
		explicitlyEnabled: true,
		cause: "selected-memory-slot"
	};
	if (params.config.slots.contextEngine === params.id) return {
		explicitlyEnabled: true,
		cause: "selected-context-engine-slot"
	};
	if (params.origin !== "bundled" && params.config.allow.includes(policyId)) return {
		explicitlyEnabled: true,
		cause: "selected-in-allowlist"
	};
	return { explicitlyEnabled: false };
}
function resolvePluginActivationDecisionShared(params) {
	const activationSource = params.activationSource ?? {
		plugins: params.config,
		rootConfig: params.rootConfig
	};
	const explicitSelection = resolveExplicitPluginSelectionShared({
		id: params.id,
		origin: params.origin,
		config: activationSource.plugins,
		rootConfig: activationSource.rootConfig,
		channelIds: params.channelIds,
		resolveChannelConfigEnablement: params.resolveChannelConfigEnablement
	});
	const decision = (source, details = {}) => ({
		enabled: source !== "disabled",
		activated: source !== "disabled",
		explicitlyEnabled: explicitSelection.explicitlyEnabled,
		source,
		...details
	});
	if (!params.config.enabled) return decision("disabled", { cause: "plugins-disabled" });
	const policyId = normalizePluginPolicyId(params.id);
	if (params.config.deny.includes(policyId)) return decision("disabled", { cause: "blocked-by-denylist" });
	const entry = params.config.entries[policyId];
	if (entry?.enabled === false) return decision("disabled", { cause: "disabled-in-config" });
	if (params.resolveChannelConfigEnablement(activationSource.rootConfig ?? params.rootConfig, params.id, params.channelIds) === false) return decision("disabled", { cause: "channel-disabled-in-config" });
	const explicitlyAllowed = params.config.allow.includes(policyId);
	if (params.origin === "workspace" && !explicitlyAllowed && entry?.enabled !== true && explicitSelection.cause !== "selected-context-engine-slot") return decision("disabled", { cause: "workspace-disabled-by-default" });
	if (params.config.slots.memory === params.id) return decision("explicit", {
		explicitlyEnabled: true,
		cause: "selected-memory-slot"
	});
	if (params.config.slots.contextEngine === params.id) return decision("explicit", {
		explicitlyEnabled: true,
		cause: "selected-context-engine-slot"
	});
	if (params.allowBundledChannelExplicitBypassesAllowlist === true && explicitSelection.cause === "bundled-channel-enabled-in-config") return decision("explicit", {
		explicitlyEnabled: true,
		cause: explicitSelection.cause
	});
	if (params.config.allow.length > 0 && !explicitlyAllowed) return decision("disabled", { cause: "not-in-allowlist" });
	if (explicitSelection.explicitlyEnabled) return decision("explicit", {
		explicitlyEnabled: true,
		cause: explicitSelection.cause
	});
	if (params.autoEnabledReason) return decision("auto", {
		explicitlyEnabled: false,
		reason: params.autoEnabledReason
	});
	if (entry?.enabled === true) return decision("auto", {
		explicitlyEnabled: false,
		cause: "enabled-by-effective-config"
	});
	if (params.origin === "bundled" && params.resolveChannelConfigEnablement(params.rootConfig, params.id, params.channelIds) === true) return decision("auto", {
		explicitlyEnabled: false,
		cause: "bundled-channel-configured"
	});
	if (params.origin === "bundled" && params.enabledByDefault === true) return decision("default", {
		explicitlyEnabled: false,
		cause: "bundled-default-enablement"
	});
	if (params.origin === "bundled") return decision("disabled", {
		explicitlyEnabled: false,
		cause: "bundled-disabled-by-default"
	});
	return decision("default");
}
//#endregion
//#region src/plugins/path-safety.ts
/** Plugin-local re-export of shared path safety helpers for plugin install/runtime code. */
/** Resolves matching physical spellings when Windows presents one tree through different aliases. */
function resolvePhysicalPathInsideRootSync(rootPath, targetPath) {
	if (process.platform !== "win32") return;
	try {
		const root = fsSync.statSync(rootPath, { bigint: true });
		if (!root.isDirectory() || root.ino === 0n) return;
		let current = path.resolve(targetPath);
		while (true) {
			const candidate = fsSync.statSync(current, { bigint: true });
			if (candidate.dev === root.dev && candidate.ino === root.ino) {
				const physicalRoot = fsSync.lstatSync(current).isSymbolicLink() ? path.resolve(rootPath) : current;
				return {
					rootPath: physicalRoot,
					targetPath: path.resolve(physicalRoot, path.relative(current, targetPath)),
					rootIdentity: Object.freeze({
						dev: root.dev,
						ino: root.ino
					})
				};
			}
			const parent = path.dirname(current);
			if (parent === current) return;
			current = parent;
		}
	} catch {
		return;
	}
}
function isPathInside$1(rootPath, targetPath) {
	return isPathInside(rootPath, targetPath) || resolvePhysicalPathInsideRootSync(rootPath, targetPath) !== void 0;
}
function createIdentityBoundRootFileFs(rootPath, expected) {
	const lstatSync = ((...args) => {
		const stat = Reflect.apply(fsSync.lstatSync, fsSync, args);
		if (args[0] === rootPath) {
			const dev = typeof stat.dev === "bigint" ? stat.dev : BigInt(stat.dev);
			const ino = typeof stat.ino === "bigint" ? stat.ino : BigInt(stat.ino);
			if (!stat.isDirectory() || dev !== expected.dev || ino !== expected.ino) throw new FsSafeError("path-mismatch", "plugin root identity changed during alias reconciliation");
		}
		return stat;
	});
	return {
		closeSync: fsSync.closeSync,
		constants: fsSync.constants,
		fstatSync: fsSync.fstatSync,
		lstatSync,
		openSync: fsSync.openSync,
		readFileSync: fsSync.readFileSync,
		realpathSync: fsSync.realpathSync
	};
}
/** Opens a plugin artifact after reconciling Windows root aliases. */
function openPluginRootFileSync(params) {
	const admittedRoot = params.rootRealPath ?? params.rootPath;
	const physical = isPathInside(admittedRoot, params.filePath) ? void 0 : resolvePhysicalPathInsideRootSync(admittedRoot, params.filePath);
	return openRootFileSync({
		absolutePath: physical?.targetPath ?? params.filePath,
		rootPath: physical?.rootPath ?? params.rootPath,
		rootRealPath: physical?.rootPath ?? params.rootRealPath,
		boundaryLabel: params.boundaryLabel ?? "plugin root",
		rejectHardlinks: params.rejectHardlinks,
		maxBytes: params.maxBytes,
		skipLexicalRootCheck: physical ? true : void 0,
		ioFs: physical ? createIdentityBoundRootFileFs(physical.rootPath, physical.rootIdentity) : void 0
	});
}
//#endregion
//#region src/plugins/plugin-cache-files.ts
const DEFAULT_PLUGIN_METADATA_MAX_BYTES = 16777216;
function entryKey(relativePath, rejectHardlinks) {
	return JSON.stringify([path.normalize(relativePath), rejectHardlinks]);
}
function enforceFileSize(entry, maxBytes, reason = "validation", filePath) {
	if (entry.ok && maxBytes !== void 0 && Math.max(entry.signature.size, entry.contents.length) > maxBytes) return {
		ok: false,
		failure: {
			ok: false,
			reason,
			error: new FsSafeError("too-large", `File exceeds ${maxBytes} bytes: ${filePath ?? entry.path}`)
		}
	};
	return entry;
}
function pathFacts(targetPath) {
	const absolute = path.resolve(targetPath);
	const root = getPluginCacheRoot(path.dirname(absolute));
	const key = path.basename(absolute);
	let facts = root.paths.get(key);
	if (!facts) {
		facts = {};
		root.paths.set(key, facts);
	}
	return facts;
}
function pluginCacheExistsSync(targetPath) {
	const facts = pathFacts(targetPath);
	return facts.exists ??= fsSync.existsSync(targetPath);
}
function resolveRealpath(targetPath) {
	if (path.resolve(targetPath) === targetPath && pluginCacheRealpathSync(targetPath, true) === targetPath) return targetPath;
	return fsSync.realpathSync(targetPath);
}
function pluginCacheRealpathSync(targetPath, native = false) {
	const facts = pathFacts(targetPath);
	const key = native ? "nativeRealpath" : "realpath";
	if (facts[key] === void 0) try {
		facts[key] = native ? fsSync.realpathSync.native(targetPath) : resolveRealpath(targetPath);
		pathFacts(facts[key])[key] = facts[key];
	} catch {
		facts[key] = null;
	}
	return facts[key];
}
/** A trusted owner that changes permissions replaces its own observed stat. */
function refreshPluginCacheStat(targetPath) {
	pathFacts(targetPath).stat = void 0;
	return pluginCacheStatSync(targetPath);
}
function pluginCacheStatSync(targetPath, throwOnError = false) {
	const facts = pathFacts(targetPath);
	if (facts.stat === void 0) {
		facts.statError = void 0;
		try {
			facts.stat = fsSync.statSync(targetPath);
			facts.exists = true;
		} catch (error) {
			materializePluginCacheError(error);
			facts.statError = error;
			facts.stat = null;
		}
	}
	if (facts.stat === null && throwOnError) throw facts.statError;
	return facts.stat;
}
/** Final symlink checks must retain lstat facts separately from followed target stats. */
function pluginCacheLstatSync(targetPath) {
	const facts = pathFacts(targetPath);
	if (facts.lstat === void 0) try {
		facts.lstat = fsSync.lstatSync(targetPath);
	} catch {
		facts.lstat = null;
	}
	return facts.lstat;
}
function readPluginCacheDirectory(targetPath) {
	const root = getPluginCacheRoot(targetPath);
	if (!root.directory) try {
		root.directory = {
			ok: true,
			entries: fsSync.readdirSync(targetPath, { withFileTypes: true })
		};
	} catch (error) {
		materializePluginCacheError(error);
		root.directory = {
			ok: false,
			error
		};
	}
	if (!root.directory.ok) throw root.directory.error;
	return root.directory.entries;
}
/** Discovery validation is immutable metadata; actual imports validate their own file identity. */
function checkPluginCacheEntry(params) {
	let root = getPluginCacheRoot(params.rootDir);
	const key = entryKey(params.relativePath, params.rejectHardlinks);
	const cached = root.checkedEntries.get(key);
	if (cached) return cached;
	const absolutePath = path.resolve(params.rootDir, params.relativePath);
	let checked;
	if (!pluginCacheExistsSync(absolutePath)) try {
		const resolved = resolveRootPathSync({
			absolutePath,
			rootPath: params.rootDir,
			rootCanonicalPath: params.rootRealPath,
			boundaryLabel: "plugin package directory"
		});
		root = bindPluginCacheRoot(params.rootDir, resolved.rootCanonicalPath);
		checked = {
			ok: true,
			path: resolved.canonicalPath,
			rootRealPath: resolved.rootCanonicalPath,
			exists: false
		};
	} catch (error) {
		checked = {
			ok: false,
			reason: "validation",
			error
		};
	}
	else {
		const opened = openPluginRootFileSync({
			filePath: absolutePath,
			rootPath: params.rootDir,
			rootRealPath: params.rootRealPath,
			boundaryLabel: "plugin package directory",
			rejectHardlinks: params.rejectHardlinks
		});
		if (!opened.ok) checked = opened;
		else {
			fsSync.closeSync(opened.fd);
			root = bindPluginCacheRoot(params.rootDir, opened.rootRealPath);
			Object.assign(pathFacts(opened.path), {
				exists: true,
				stat: opened.stat
			});
			checked = {
				ok: true,
				path: opened.path,
				rootRealPath: opened.rootRealPath,
				exists: true
			};
		}
	}
	if (!checked.ok) materializePluginCacheError(checked.error);
	root.checkedEntries.set(key, checked);
	return checked;
}
/** Reads metadata once under its original boundary policy, including absence and invalid files. */
function readPluginCacheFile(params) {
	const lexicalRoot = path.resolve(params.rootDir);
	let root = getPluginCacheRoot(lexicalRoot);
	const maxBytes = params.maxBytes === null ? void 0 : params.maxBytes ?? DEFAULT_PLUGIN_METADATA_MAX_BYTES;
	const key = entryKey(params.relativePath, params.rejectHardlinks);
	const limitKey = JSON.stringify([key, maxBytes]);
	const strict = params.rejectHardlinks ? void 0 : root.files.get(entryKey(params.relativePath, true));
	const cached = root.files.get(key) ?? root.files.get(limitKey) ?? (strict?.ok ? strict : void 0);
	if (cached) return enforceFileSize(cached, maxBytes);
	const requestedPath = path.resolve(lexicalRoot, params.relativePath);
	const checked = root.checkedEntries.get(key);
	if (pathFacts(requestedPath).exists === false || checked && (!checked.ok || !checked.exists)) {
		const entry = {
			ok: false,
			failure: checked && !checked.ok ? checked : {
				ok: false,
				reason: "path"
			}
		};
		root.files.set(key, entry);
		return entry;
	}
	const canonicalRoot = params.rootRealPath ?? pluginCacheRealpathSync(lexicalRoot) ?? lexicalRoot;
	const canonicalCached = getPluginCacheRoot(canonicalRoot).files.get(key);
	if (canonicalCached?.ok) {
		bindPluginCacheRoot(lexicalRoot, canonicalRoot);
		return enforceFileSize(canonicalCached, maxBytes);
	}
	const absolutePath = path.resolve(canonicalRoot, params.relativePath);
	const opened = openPluginRootFileSync({
		filePath: absolutePath,
		rootRealPath: canonicalRoot,
		rootPath: canonicalRoot,
		boundaryLabel: "plugin root",
		rejectHardlinks: params.rejectHardlinks,
		maxBytes
	});
	let entry;
	if (!opened.ok) {
		entry = {
			ok: false,
			failure: opened
		};
		if (opened.reason === "path" && opened.error && typeof opened.error === "object" && "code" in opened.error && opened.error.code === "ENOENT") {
			pathFacts(requestedPath).exists = false;
			pathFacts(absolutePath).exists = false;
		}
	} else {
		root = bindPluginCacheRoot(lexicalRoot, opened.rootRealPath);
		try {
			const contents = maxBytes === void 0 ? fsSync.readFileSync(opened.fd) : readFileDescriptorBoundedSync$1(opened.fd, maxBytes);
			entry = {
				ok: true,
				path: opened.path,
				rootRealPath: opened.rootRealPath,
				contents,
				hash: crypto.createHash("sha256").update(contents).digest("hex"),
				signature: {
					size: opened.stat.size,
					mtimeMs: opened.stat.mtimeMs,
					ctimeMs: opened.stat.ctimeMs
				}
			};
			Object.assign(pathFacts(absolutePath), {
				exists: true,
				stat: opened.stat
			});
			root.checkedEntries.set(key, {
				ok: true,
				path: opened.path,
				rootRealPath: opened.rootRealPath,
				exists: true
			});
		} catch (error) {
			entry = {
				ok: false,
				failure: {
					ok: false,
					reason: "io",
					error
				},
				failurePhase: "read"
			};
		} finally {
			fsSync.closeSync(opened.fd);
		}
	}
	if (!entry.ok) materializePluginCacheError(entry.failure.error);
	root.files.set(entry.ok ? key : limitKey, entry);
	return entry;
}
/** Catalog files retain the regular-file policy, which rejects final symlinks but allows hardlinks. */
function readPluginCacheRegularFile(params) {
	const absolutePath = path.resolve(params.filePath);
	const lexicalRoot = path.dirname(absolutePath);
	const canonicalRoot = pluginCacheRealpathSync(lexicalRoot) ?? lexicalRoot;
	const root = bindPluginCacheRoot(lexicalRoot, canonicalRoot);
	const filename = path.basename(absolutePath);
	const key = JSON.stringify(["regular", filename]);
	const limitKey = JSON.stringify([
		"regular",
		filename,
		params.maxBytes
	]);
	let entry = root.files.get(key) ?? root.files.get(limitKey);
	if (!entry && pathFacts(absolutePath).exists === false) {
		entry = {
			ok: false,
			failure: {
				ok: false,
				reason: "path"
			}
		};
		root.files.set(key, entry);
	}
	if (!entry) try {
		const { buffer: contents, stat } = readRegularFileSync({
			filePath: absolutePath,
			maxBytes: params.maxBytes
		});
		entry = {
			ok: true,
			path: path.join(canonicalRoot, filename),
			rootRealPath: canonicalRoot,
			contents,
			hash: crypto.createHash("sha256").update(contents).digest("hex"),
			signature: {
				size: stat.size,
				mtimeMs: stat.mtimeMs,
				ctimeMs: stat.ctimeMs
			}
		};
		Object.assign(pathFacts(absolutePath), {
			exists: true,
			stat
		});
		root.files.set(key, entry);
	} catch (error) {
		materializePluginCacheError(error);
		entry = {
			ok: false,
			failure: {
				ok: false,
				reason: "io",
				error
			}
		};
		root.files.set(error instanceof FsSafeError && error.code === "too-large" ? limitKey : key, entry);
		if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") pathFacts(absolutePath).exists = false;
	}
	return enforceFileSize(entry, params.maxBytes, "io", absolutePath);
}
function readPluginCacheJsonFile(filePath, options = {}) {
	const file = readPluginCacheRegularFile({
		filePath,
		...options
	});
	return file.ok ? parsePluginCacheJson(file) : {
		ok: false,
		error: file.failure.error
	};
}
function parsePluginCacheJson(file, options = {}) {
	const key = options.json5 ? "json5" : "json";
	if (!file[key]) try {
		const source = file.contents.toString("utf8");
		file[key] = {
			ok: true,
			value: options.json5 ? parseJsonWithJson5Fallback(source) : JSON.parse(source)
		};
	} catch (error) {
		materializePluginCacheError(error);
		file[key] = {
			ok: false,
			error
		};
	}
	return file[key];
}
//#endregion
//#region src/plugins/package-entrypoints.ts
/** True when a package entrypoint needs built JavaScript candidates. */
function isTypeScriptPackageEntry(entryPath) {
	return [
		".ts",
		".tsx",
		".mts",
		".cts"
	].includes(path.extname(entryPath).toLowerCase());
}
/** Lists built runtime entry candidates for a TypeScript package entrypoint. */
function listBuiltRuntimeEntryCandidates(entryPath) {
	if (!isTypeScriptPackageEntry(entryPath)) return [];
	const normalized = entryPath.replace(/\\/g, "/");
	const withoutExtension = normalized.replace(/\.[^.]+$/u, "");
	const normalizedRelative = withoutExtension.replace(/^\.\//u, "");
	const distWithoutExtension = normalizedRelative.startsWith("src/") ? `./dist/${normalizedRelative.slice(4)}` : `./dist/${normalizedRelative}`;
	const sourceExtension = path.extname(normalized).toLowerCase();
	const outputExtensions = sourceExtension === ".mts" ? [
		".mjs",
		".js",
		".cjs"
	] : sourceExtension === ".cts" ? [
		".cjs",
		".js",
		".mjs"
	] : [
		".js",
		".mjs",
		".cjs"
	];
	return [
		distWithoutExtension,
		...normalizedRelative.startsWith("src/") ? [`./dist/${normalizedRelative}`] : [],
		withoutExtension
	].flatMap((basePath) => outputExtensions.map((extension) => `${basePath}${extension}`));
}
//#endregion
//#region src/plugins/package-manifest.ts
const DEFAULT_PLUGIN_ENTRY_CANDIDATES = [
	"index.ts",
	"index.js",
	"index.mjs",
	"index.cjs"
];
function getPackageManifestMetadata(manifest) {
	return manifest?.[MANIFEST_KEY];
}
function resolvePackageExtensionEntries(manifest) {
	const rawOpenClaw = manifest?.[MANIFEST_KEY];
	if (rawOpenClaw === void 0 || rawOpenClaw === null) return {
		status: "missing",
		entries: []
	};
	if (!isRecord(rawOpenClaw)) return {
		status: "invalid",
		entries: [],
		error: "package.json openclaw must be an object"
	};
	const raw = rawOpenClaw.extensions;
	if (raw === void 0 || raw === null) return {
		status: "missing",
		entries: []
	};
	if (!Array.isArray(raw)) return {
		status: "invalid",
		entries: [],
		error: "package.json openclaw.extensions must be an array"
	};
	const entries = [];
	for (const [index, entry] of raw.entries()) {
		const normalized = normalizeOptionalString(entry);
		if (!normalized) return {
			status: "invalid",
			entries: [],
			error: `package.json openclaw.extensions[${index}] must be a non-empty string`
		};
		entries.push(normalized);
	}
	if (entries.length === 0) return {
		status: "empty",
		entries: []
	};
	return {
		status: "ok",
		entries
	};
}
//#endregion
//#region src/plugins/plugin-runtime-artifact-selection.ts
/** Built hosts default only checkout plugins to compiled execution, not installed packages. */
function resolvePluginRuntimeArtifactPreference(preferBuiltPluginArtifacts) {
	if (preferBuiltPluginArtifacts !== void 0) return preferBuiltPluginArtifacts ? "all" : "source";
	return /\.[cm]?js$/.test(new URL(import.meta.url).pathname) ? "bundled" : "source";
}
//#endregion
//#region src/plugins/bundled-dir.ts
/** Resolves the bundled plugin directory for source checkouts, dist builds, and tests. */
const DISABLED_BUNDLED_PLUGINS_DIR = path.join(os.tmpdir(), "openclaw-empty-bundled-plugins");
const TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV = "OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR";
/** Returns true when env disables bundled plugin discovery. */
function areBundledPluginsDisabled(env = process.env) {
	const raw = normalizeOptionalLowercaseString(env.OPENCLAW_DISABLE_BUNDLED_PLUGINS);
	return raw === "1" || raw === "true";
}
function resolveDisabledBundledPluginsDir() {
	if (!pluginCacheExistsSync(DISABLED_BUNDLED_PLUGINS_DIR)) {
		fsSync.mkdirSync(DISABLED_BUNDLED_PLUGINS_DIR, { recursive: true });
		refreshPluginCacheStat(DISABLED_BUNDLED_PLUGINS_DIR);
	}
	return DISABLED_BUNDLED_PLUGINS_DIR;
}
function isSourceCheckoutRoot(packageRoot) {
	return pluginCacheExistsSync(path.join(packageRoot, "pnpm-workspace.yaml")) && pluginCacheExistsSync(path.join(packageRoot, "src")) && pluginCacheExistsSync(path.join(packageRoot, "extensions"));
}
function shouldTrustTestBundledPluginsDirOverride(env) {
	const separateEnv = env !== process.env;
	if (!isTruthyEnvValue(env[TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV]) && !(separateEnv && isTruthyEnvValue(process.env[TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV]))) return false;
	return isVitestRuntimeEnv(env) || separateEnv && isVitestRuntimeEnv(process.env);
}
function hasUsableBundledPluginTree(pluginsDir) {
	if (!pluginCacheExistsSync(pluginsDir)) return false;
	try {
		return readPluginCacheDirectory(pluginsDir).some((entry) => {
			if (!entry.isDirectory()) return false;
			const pluginDir = path.join(pluginsDir, entry.name);
			return pluginCacheExistsSync(path.join(pluginDir, "package.json")) || pluginCacheExistsSync(path.join(pluginDir, "openclaw.plugin.json"));
		});
	} catch {
		return false;
	}
}
function safeRealpathSync(targetPath) {
	return pluginCacheRealpathSync(targetPath, true);
}
function trustedBundledPluginRootsForPackageRoot(packageRoot) {
	const roots = [path.join(packageRoot, "dist", "extensions"), path.join(packageRoot, "dist-runtime", "extensions")];
	if (isSourceCheckoutRoot(packageRoot)) roots.push(path.join(packageRoot, "extensions"));
	return roots;
}
function resolvePackageRootsForBundledPlugins() {
	const argvRoot = resolveOpenClawPackageRootSync({ argv1: process.argv[1] });
	const moduleRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
	return uniqueStrings([argvRoot, moduleRoot].filter((entry) => Boolean(entry)));
}
function resolveSourceCheckoutDependencyDiagnostic(env = process.env) {
	if (areBundledPluginsDisabled(env)) return null;
	for (const packageRoot of resolvePackageRootsForBundledPlugins()) {
		if (!isSourceCheckoutRoot(packageRoot)) continue;
		const extensionsDir = path.join(packageRoot, "extensions");
		if (!isPluginInPackageBundledRoots({
			rootDir: extensionsDir,
			packageRoot
		}) || !hasUsableBundledPluginTree(extensionsDir)) continue;
		if (pluginCacheExistsSync(path.join(packageRoot, "node_modules", ".pnpm"))) continue;
		return {
			source: packageRoot,
			message: "OpenClaw source checkout detected without pnpm workspace dependencies; run `pnpm install` from the repo root so bundled plugins can load package-local dependencies."
		};
	}
	return null;
}
function resolveTrustedExistingOverride(resolvedOverride) {
	const realOverride = safeRealpathSync(resolvedOverride);
	if (!realOverride) return null;
	const modulePackageRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
	if (!modulePackageRoot || !isPluginInPackageBundledRoots({
		rootDir: realOverride,
		packageRoot: modulePackageRoot
	})) return null;
	if (!hasUsableBundledPluginTree(realOverride)) return null;
	return realOverride;
}
/** Checks physical containment in a package's source or compiled plugin trees. */
function isPluginInPackageBundledRoots(params) {
	const realPluginRoot = safeRealpathSync(params.rootDir);
	const realPackageRoot = safeRealpathSync(params.packageRoot);
	if (!realPluginRoot || !realPackageRoot) return false;
	return trustedBundledPluginRootsForPackageRoot(params.packageRoot).map((trustedRoot) => safeRealpathSync(trustedRoot)).some((trustedRoot) => trustedRoot !== null && isPathInside$2(realPackageRoot, trustedRoot) && isPathInside$2(trustedRoot, realPluginRoot));
}
/** Recognizes compiled bundle ownership only; this never confers plugin trust. */
function isForeignBundledPluginRoot(rootDir, env = process.env) {
	const realRootDir = safeRealpathSync(rootDir);
	if (!realRootDir) return false;
	const extensionsDir = path.dirname(realRootDir);
	const compiledDir = path.dirname(extensionsDir);
	if (path.basename(extensionsDir) !== "extensions" || !["dist", "dist-runtime"].includes(path.basename(compiledDir))) return false;
	const packageRoot = path.dirname(compiledDir);
	if (resolveOpenClawPackageRootSync({ cwd: packageRoot }) !== packageRoot || !isPluginInPackageBundledRoots({
		rootDir: realRootDir,
		packageRoot
	})) return false;
	const activeBundledDir = resolveBundledPluginsDir(env);
	const realActiveDir = activeBundledDir ? safeRealpathSync(activeBundledDir) : null;
	if (realActiveDir && isPathInside$2(realActiveDir, realRootDir)) return false;
	const runningRoots = resolvePackageRootsForBundledPlugins();
	return runningRoots.length > 0 && !runningRoots.some((runningRoot) => isPluginInPackageBundledRoots({
		rootDir: realRootDir,
		packageRoot: runningRoot
	}));
}
function resolveBundledDirFromPackageRoot(packageRoot, preference = "bundled") {
	const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
	const runtimeExtensionsDir = path.join(packageRoot, "dist-runtime", "extensions");
	if (isSourceCheckoutRoot(packageRoot)) {
		const sourceExtensionsDir = path.join(packageRoot, "extensions");
		return (preference === "source" ? [sourceExtensionsDir] : [
			builtExtensionsDir,
			runtimeExtensionsDir,
			sourceExtensionsDir
		]).find((rootDir) => isPluginInPackageBundledRoots({
			rootDir,
			packageRoot
		}) && hasUsableBundledPluginTree(rootDir));
	}
	return pluginCacheExistsSync(builtExtensionsDir) ? [runtimeExtensionsDir, builtExtensionsDir].find((rootDir) => isPluginInPackageBundledRoots({
		rootDir,
		packageRoot
	})) : void 0;
}
function resolveBundledPluginsDirUncached(env) {
	if (areBundledPluginsDisabled(env)) return resolveDisabledBundledPluginsDir();
	const override = env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim();
	let rejectedExistingOverride = null;
	if (override) {
		const resolvedOverride = resolveUserPath(override, env);
		if (pluginCacheExistsSync(resolvedOverride)) {
			if (shouldTrustTestBundledPluginsDirOverride(env)) return path.resolve(resolvedOverride);
			const trustedOverride = resolveTrustedExistingOverride(resolvedOverride);
			if (trustedOverride) return trustedOverride;
			rejectedExistingOverride = resolvedOverride;
		}
	}
	try {
		const argvRoot = resolveOpenClawPackageRootSync({ argv1: process.argv[1] });
		const safeArgvRoot = Boolean(argvRoot && rejectedExistingOverride && isPluginInPackageBundledRoots({
			rootDir: rejectedExistingOverride,
			packageRoot: argvRoot
		})) ? null : argvRoot;
		const moduleRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
		const packageRoots = uniqueStrings([safeArgvRoot, moduleRoot].filter((entry) => Boolean(entry)));
		for (const packageRoot of packageRoots) {
			const bundledDir = resolveBundledDirFromPackageRoot(packageRoot, resolvePluginRuntimeArtifactPreference());
			if (bundledDir) return bundledDir;
		}
	} catch {}
	try {
		const execDir = path.dirname(process.execPath);
		const siblingBuilt = path.join(execDir, "dist", "extensions");
		if (pluginCacheExistsSync(siblingBuilt)) return siblingBuilt;
		const sibling = path.join(execDir, "extensions");
		if (pluginCacheExistsSync(sibling)) return sibling;
	} catch {}
	try {
		let cursor = path.dirname(fileURLToPath(import.meta.url));
		for (let i = 0; i < 6; i += 1) {
			const candidate = path.join(cursor, "extensions");
			if (pluginCacheExistsSync(candidate)) return candidate;
			const parent = path.dirname(cursor);
			if (parent === cursor) break;
			cursor = parent;
		}
	} catch {}
}
function resolveBundledPluginsDir(env = process.env) {
	const disabled = areBundledPluginsDisabled(env);
	const override = disabled ? void 0 : env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim();
	const resolvedOverride = override ? resolveUserPath(override, env) : void 0;
	const trustOverride = shouldTrustTestBundledPluginsDirOverride(env);
	const argv1 = process.argv[1];
	const execPath = process.execPath;
	const cwd = tryProcessCwd();
	const metadata = getPluginCache().metadata;
	const cached = metadata.bundledPluginsDir;
	if (cached && cached.moduleUrl === import.meta.url && cached.disabled === disabled && cached.resolvedOverride === resolvedOverride && cached.trustOverride === trustOverride && cached.argv1 === argv1 && cached.execPath === execPath && cached.cwd === cwd) return cached.value;
	const value = resolveBundledPluginsDirUncached(env);
	metadata.bundledPluginsDir = {
		moduleUrl: import.meta.url,
		disabled,
		resolvedOverride,
		trustOverride,
		argv1,
		execPath,
		cwd,
		value
	};
	return value;
}
//#endregion
//#region src/channels/bundled-channel-catalog-read.ts
/**
* Bundled channel catalog reader.
*
* Loads channel metadata from generated package catalogs and bundled plugin package manifests.
*/
const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = path.join("dist", "channel-catalog.json");
function listPackageRoots() {
	return uniqueStrings([resolveOpenClawPackageRootSync({ cwd: process.cwd() }), resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url })].filter((entry) => Boolean(entry)));
}
function readBundledExtensionCatalogEntriesSync(pluginsDir) {
	if (!pluginsDir) return [];
	try {
		return readPluginCacheDirectory(pluginsDir).filter((entry) => entry.isDirectory()).flatMap((entry) => {
			const parsed = readPluginCacheJsonFile(path.join(pluginsDir, entry.name, "package.json"));
			return parsed.ok && isRecord(parsed.value) ? [parsed.value] : [];
		});
	} catch {
		return [];
	}
}
function readOfficialCatalogFileSync() {
	const bundledExternalEntries = BUNDLED_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_ENTRIES.filter((entry) => typeof entry === "object" && entry !== null);
	for (const packageRoot of listPackageRoots()) {
		const payload = readPluginCacheJsonFile(path.join(packageRoot, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH));
		if (payload.ok && isRecord(payload.value)) {
			const entries = Array.isArray(payload.value.entries) ? payload.value.entries.filter((entry) => isRecord(entry)) : [];
			return [...bundledExternalEntries, ...entries];
		}
	}
	return bundledExternalEntries;
}
function isChannelCatalogEntryLike(entry) {
	return "openclaw" in entry;
}
function toBundledChannelEntry(entry) {
	const channel = isChannelCatalogEntryLike(entry) ? entry.openclaw?.channel : entry;
	const id = normalizeOptionalLowercaseString(channel?.id);
	if (!id || !channel) return null;
	return {
		id,
		channel,
		aliases: Array.isArray(channel.aliases) ? channel.aliases.map((alias) => normalizeOptionalLowercaseString(alias)).filter((alias) => Boolean(alias)) : [],
		order: typeof channel.order === "number" && Number.isFinite(channel.order) ? channel.order : Number.MAX_SAFE_INTEGER
	};
}
/**
* Lists bundled channel catalog entries from package manifests and generated catalog files.
*/
function listBundledChannelCatalogEntries() {
	const pluginsDir = resolveBundledPluginsDir();
	const catalogs = getPluginCache().metadata.bundledChannelCatalogs;
	const key = JSON.stringify([process.cwd(), pluginsDir]);
	const cached = catalogs.get(key);
	if (cached) return cached;
	const entries = /* @__PURE__ */ new Map();
	for (const entry of readBundledExtensionCatalogEntriesSync(pluginsDir)) {
		const channelEntry = toBundledChannelEntry(entry);
		if (channelEntry) entries.set(channelEntry.id, channelEntry);
	}
	for (const entry of readOfficialCatalogFileSync()) {
		const channelEntry = toBundledChannelEntry(entry);
		if (channelEntry) entries.set(channelEntry.id, entries.get(channelEntry.id) ?? channelEntry);
	}
	const catalog = Array.from(entries.values()).toSorted((left, right) => left.order - right.order || left.id.localeCompare(right.id));
	catalogs.set(key, catalog);
	return catalog;
}
//#endregion
//#region src/channels/bundled-channel-ids.generated.ts
const GENERATED_BUNDLED_CHANNEL_IDS = [
	{
		channelId: "a2a",
		order: 75,
		label: "A2A"
	},
	{
		channelId: "buzz",
		order: 56,
		label: "Buzz"
	},
	{
		channelId: "clickclack",
		order: 85,
		label: "ClickClack"
	},
	{
		channelId: "discord",
		label: "Discord"
	},
	{
		channelId: "feishu",
		aliases: ["lark"],
		order: 35,
		label: "Feishu"
	},
	{
		channelId: "googlechat",
		aliases: ["gchat", "google-chat"],
		order: 55,
		label: "Google Chat"
	},
	{
		channelId: "imessage",
		aliases: ["imsg"],
		label: "iMessage"
	},
	{
		channelId: "irc",
		aliases: ["internet-relay-chat"],
		label: "IRC"
	},
	{
		channelId: "line",
		order: 75,
		label: "LINE"
	},
	{
		channelId: "matrix",
		order: 70,
		label: "Matrix"
	},
	{
		channelId: "mattermost",
		order: 65,
		label: "Mattermost"
	},
	{
		channelId: "msteams",
		aliases: ["teams"],
		order: 60,
		label: "Microsoft Teams"
	},
	{
		channelId: "nextcloud-talk",
		aliases: ["nc", "nc-talk"],
		order: 65,
		label: "Nextcloud Talk"
	},
	{
		channelId: "nostr",
		order: 55,
		label: "Nostr"
	},
	{
		channelId: "qa-channel",
		order: 999,
		configurable: false,
		label: "QA Channel"
	},
	{
		channelId: "raft",
		order: 72,
		label: "Raft"
	},
	{
		channelId: "reef",
		label: "Reef"
	},
	{
		channelId: "signal",
		label: "Signal"
	},
	{
		channelId: "slack",
		label: "Slack"
	},
	{
		channelId: "sms",
		order: 88,
		label: "SMS"
	},
	{
		channelId: "synology-chat",
		order: 90,
		label: "Synology Chat"
	},
	{
		channelId: "telegram",
		label: "Telegram"
	},
	{
		channelId: "tlon",
		order: 90,
		label: "Tlon"
	},
	{
		channelId: "twitch",
		aliases: ["twitch-chat"],
		label: "Twitch"
	},
	{
		channelId: "whatsapp",
		label: "WhatsApp"
	},
	{
		channelId: "zalo",
		aliases: ["zl"],
		order: 80,
		label: "Zalo"
	},
	{
		channelId: "zalouser",
		aliases: ["zlu"],
		order: 85,
		label: "Zalo Personal"
	}
];
//#endregion
//#region src/channels/ids.ts
/**
* Built-in chat channel ids and aliases.
*
* Derives canonical ids from generated bundled channel metadata with runtime catalog fallback.
*/
function listBundledChatChannelEntries() {
	return GENERATED_BUNDLED_CHANNEL_IDS.filter((entry) => entry.configurable !== false).map((entry) => ({
		id: normalizeOptionalLowercaseString(entry.channelId) ?? entry.channelId,
		aliases: entry.aliases ?? [],
		label: entry.label?.trim() || void 0,
		order: entry.order ?? Number.MAX_SAFE_INTEGER
	})).toSorted((left, right) => left.order - right.order || left.id.localeCompare(right.id, "en", { sensitivity: "base" }));
}
const BUNDLED_CHAT_CHANNEL_ENTRIES = Object.freeze(listBundledChatChannelEntries());
const CHAT_CHANNEL_ID_SET = new Set(BUNDLED_CHAT_CHANNEL_ENTRIES.map((entry) => entry.id));
Object.freeze(BUNDLED_CHAT_CHANNEL_ENTRIES.map((entry) => entry.id));
/**
* Maps configured built-in channel aliases to canonical chat channel ids.
*/
const CHAT_CHANNEL_ALIASES = Object.freeze(Object.fromEntries(BUNDLED_CHAT_CHANNEL_ENTRIES.flatMap((entry) => entry.aliases.map((alias) => [alias, entry.id]))));
function normalizeRuntimeBundledChatChannelId(normalized) {
	for (const entry of listBundledChannelCatalogEntries()) if (entry.id === normalized || entry.aliases.includes(normalized)) return entry.id;
	return null;
}
/**
* Normalizes a raw chat channel id or alias to a known canonical built-in channel id.
*/
function normalizeChatChannelId(raw) {
	const normalized = normalizeOptionalLowercaseString(raw);
	if (!normalized) return null;
	const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
	return CHAT_CHANNEL_ID_SET.has(resolved) ? resolved : normalizeRuntimeBundledChatChannelId(normalized);
}
//#endregion
//#region src/plugins/default-enablement.ts
/** True when a plugin should be enabled by default for a platform. */
function isPluginEnabledByDefaultForPlatform(plugin, platform = process.platform) {
	if (plugin.enabledByDefault === true) return true;
	return plugin.enabledByDefaultOnPlatforms?.includes(platform) === true;
}
//#endregion
//#region src/plugins/slots.ts
/** Applies mutually exclusive plugin slot selection for memory and context-engine plugins. */
const DEFAULT_SLOT_BY_KEY = {
	memory: "memory-core",
	contextEngine: "legacy"
};
Object.keys(DEFAULT_SLOT_BY_KEY);
/** Returns the implicit plugin id that owns a slot before config overrides it. */
function defaultSlotIdForKey(slotKey) {
	return DEFAULT_SLOT_BY_KEY[slotKey];
}
/** Raw `plugins.slots[key]`: `none` turns the slot off, blank leaves it unset. */
function normalizeSlotValue(value) {
	const trimmed = normalizeOptionalString(value);
	if (!trimmed) return;
	if (normalizeOptionalLowercaseString(trimmed) === "none") return null;
	return trimmed;
}
function resolveSlotSelection(slotKey, value) {
	const normalized = normalizeSlotValue(value);
	if (normalized === void 0) return {
		kind: "default",
		pluginId: defaultSlotIdForKey(slotKey)
	};
	return normalized === null ? { kind: "off" } : {
		kind: "pinned",
		pluginId: normalized
	};
}
//#endregion
//#region src/plugins/config-normalization-shared.ts
/** Default plugin id normalizer for already-canonical ids. */
const identityNormalizePluginId = (id) => id.trim();
function normalizePluginConfigList(value, normalizePluginId) {
	if (!Array.isArray(value)) return [];
	return value.map((entry) => typeof entry === "string" ? normalizePluginId(entry) : "").filter(Boolean);
}
function normalizeHookTimeoutMs(value) {
	return asSafeIntegerInRange(value, {
		min: 1,
		max: 6e5
	});
}
function normalizeHookTimeouts(value) {
	if (!isRecord(value)) return;
	const normalized = {};
	for (const [hookName, timeoutMs] of Object.entries(value)) {
		const normalizedTimeoutMs = normalizeHookTimeoutMs(timeoutMs);
		if (normalizedTimeoutMs !== void 0) normalized[hookName] = normalizedTimeoutMs;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizePluginHooks(value) {
	if (!isRecord(value)) return;
	const hooks = {};
	for (const key of ["allowPromptInjection", "allowConversationAccess"]) if (typeof value[key] === "boolean") hooks[key] = value[key];
	const timeoutMs = normalizeHookTimeoutMs(value.timeoutMs);
	const timeouts = normalizeHookTimeouts(value.timeouts);
	if (timeoutMs !== void 0) hooks.timeoutMs = timeoutMs;
	if (timeouts !== void 0) hooks.timeouts = timeouts;
	return Object.keys(hooks).length > 0 ? hooks : void 0;
}
function normalizePluginModelConfig(value, kind) {
	if (!isRecord(value)) return;
	const config = {};
	if (typeof value.allowModelOverride === "boolean") config.allowModelOverride = value.allowModelOverride;
	for (const [key, configuredKey] of [["allowedModels", "hasAllowedModelsConfig"], ["allowedCompletionModels", "hasAllowedCompletionModelsConfig"]]) {
		if (key === "allowedCompletionModels" && kind !== "llm") continue;
		const models = normalizeArrayBackedTrimmedStringList(value[key]);
		if (models) {
			config[configuredKey] = true;
			if (models.length > 0) config[key] = models;
		}
	}
	if (kind === "llm") {
		for (const key of ["allowAuthProfileOverride", "allowAgentIdOverride"]) if (typeof value[key] === "boolean") config[key] = value[key];
	}
	return Object.keys(config).length > 0 ? config : void 0;
}
function normalizePluginEntries(entries, normalizePluginId) {
	if (!isRecord(entries)) return {};
	const normalized = {};
	for (const [key, value] of Object.entries(entries)) {
		const normalizedKey = normalizePluginId(key);
		if (!normalizedKey) continue;
		if (!isRecord(value)) {
			normalized[normalizedKey] = {};
			continue;
		}
		const entry = value;
		normalized[normalizedKey] = {
			...normalized[normalizedKey],
			enabled: typeof entry.enabled === "boolean" ? entry.enabled : normalized[normalizedKey]?.enabled,
			hooks: normalizePluginHooks(entry.hooks) ?? normalized[normalizedKey]?.hooks,
			subagent: normalizePluginModelConfig(entry.subagent, "subagent") ?? normalized[normalizedKey]?.subagent,
			llm: normalizePluginModelConfig(entry.llm, "llm") ?? normalized[normalizedKey]?.llm,
			config: "config" in entry ? entry.config : normalized[normalizedKey]?.config
		};
	}
	return normalized;
}
/** Normalizes plugin config while allowing callers to resolve aliases first. */
function normalizePluginsConfigWithResolverCore(config, normalizePluginId = identityNormalizePluginId) {
	const memorySlot = resolveSlotSelection("memory", config?.slots?.memory);
	return {
		enabled: config?.enabled ?? true,
		allow: normalizePluginConfigList(config?.allow, normalizePluginId),
		deny: normalizePluginConfigList(config?.deny, normalizePluginId),
		loadPaths: normalizePluginConfigList(config?.load?.paths, identityNormalizePluginId),
		slots: {
			memory: memorySlot.kind === "off" ? null : memorySlot.pluginId,
			contextEngine: normalizeSlotValue(config?.slots?.contextEngine)
		},
		entries: normalizePluginEntries(config?.entries, normalizePluginId)
	};
}
/**
* Enables an owner for any enabled channel; disables it only when all channels are off.
* Unspecified channels leave the plugin's own activation policy in control.
*/
function resolveChannelConfigEnablement(cfg, pluginId, channelIds = []) {
	const channels = cfg?.channels;
	if (!channels) return;
	const enablement = (channelIds.length ? channelIds.map((channelId) => normalizeChatChannelId(channelId) ?? channelId) : [normalizeChatChannelId(pluginId)]).map((channelId) => {
		const entry = channelId ? channels[channelId] : void 0;
		return isRecord(entry) ? entry.enabled : void 0;
	});
	if (enablement.includes(true)) return true;
	return enablement.every((enabled) => enabled === false) ? false : void 0;
}
//#endregion
//#region src/plugins/config-state.ts
/** Normalizes plugin config and resolves effective enablement, slots, and activation sources. */
const BUILT_IN_PLUGIN_ALIAS_FALLBACKS = [
	["google-gemini-cli", "google"],
	["minimax-portal", "minimax"],
	["minimax-portal-auth", "minimax"]
];
const BUILT_IN_PLUGIN_ALIAS_LOOKUP = new Map([...BUILT_IN_PLUGIN_ALIAS_FALLBACKS, ...BUILT_IN_PLUGIN_ALIAS_FALLBACKS.map(([, pluginId]) => [pluginId, pluginId])]);
/** Normalizes user/config plugin ids into the canonical lowercase key form. */
function normalizePluginId(id) {
	const normalized = normalizeOptionalLowercaseString(id) ?? "";
	return BUILT_IN_PLUGIN_ALIAS_LOOKUP.get(normalized) ?? normalized;
}
const normalizePluginsConfig = (config) => {
	return normalizePluginsConfigWithResolverCore(config, normalizePluginId);
};
function resolveEffectivePluginActivationState(params) {
	return toPluginActivationState(resolvePluginActivationDecisionShared({
		...params,
		allowBundledChannelExplicitBypassesAllowlist: true,
		resolveChannelConfigEnablement
	}));
}
//#endregion
//#region src/plugins/installed-plugin-index-hash.ts
function hashString(value) {
	return crypto.createHash("sha256").update(value).digest("hex");
}
/** Hashes JSON-serializable data with SHA-256. */
function hashJson(value) {
	return hashString(JSON.stringify(value));
}
/** Hashes JSON-like data independently of object property insertion order. */
function hashStableJson(value) {
	return hashString(stableStringify(value));
}
//#endregion
//#region src/plugins/installed-plugin-index-policy.ts
/** Hashes config policy inputs that can change installed plugin activation. */
function resolveInstalledPluginIndexPolicyHash(config, env, behavior = {}) {
	const normalized = normalizePluginsConfig(config?.plugins);
	const channelPolicy = {};
	const channels = config?.channels;
	if (channels && typeof channels === "object" && !Array.isArray(channels)) {
		for (const [channelId, value] of Object.entries(channels)) if (value && typeof value === "object" && !Array.isArray(value)) {
			const enabled = value.enabled;
			if (typeof enabled === "boolean") channelPolicy[channelId] = enabled;
		}
	}
	return hashJson({
		plugins: {
			enabled: normalized.enabled,
			allow: normalized.allow,
			deny: normalized.deny,
			bundledDiscovery: readBundledDiscoveryModeMemoized(env, behavior) ?? null,
			slots: normalized.slots,
			entries: Object.fromEntries(Object.entries(normalized.entries).flatMap(([pluginId, entry]) => typeof entry.enabled === "boolean" ? [[pluginId, entry.enabled]] : []).toSorted(([left], [right]) => left.localeCompare(right)))
		},
		channels: Object.fromEntries(Object.entries(channelPolicy).toSorted(([left], [right]) => left.localeCompare(right)))
	});
}
//#endregion
//#region src/plugins/bundled-load-path-aliases.ts
const PACKAGED_BUNDLED_ROOTS = [path.join("dist", "extensions"), path.join("dist-runtime", "extensions")];
/** Normalizes bundled lookup paths without preserving trailing separators. */
function normalizeBundledLookupPath(targetPath) {
	const normalized = path.normalize(targetPath);
	const root = path.parse(normalized).root;
	let trimmed = normalized;
	while (trimmed.length > root.length && (trimmed.endsWith(path.sep) || trimmed.endsWith("/"))) trimmed = trimmed.slice(0, -1);
	return trimmed;
}
function findPackagedBundledRoot(localPath) {
	const normalized = normalizeBundledLookupPath(localPath);
	for (const packagedRoot of PACKAGED_BUNDLED_ROOTS) {
		const marker = `${path.sep}${packagedRoot}`;
		const markerIndex = normalized.lastIndexOf(marker);
		if (markerIndex === -1) continue;
		const markerEnd = markerIndex + marker.length;
		if (normalized.length !== markerEnd && normalized[markerEnd] !== path.sep) continue;
		return {
			packageRoot: normalized.slice(0, markerIndex),
			bundledRoot: normalized.slice(0, markerEnd)
		};
	}
	return null;
}
/** Builds the legacy extensions root for a packaged bundled plugin root. */
function buildLegacyBundledRootPath(localPath) {
	const packaged = findPackagedBundledRoot(localPath);
	return packaged ? path.join(packaged.packageRoot, "extensions") : null;
}
//#endregion
//#region src/plugins/bundled-source-overlays.ts
/** Parses Linux mountinfo content into absolute mount points. */
function parseLinuxMountInfoMountPoints(mountInfo) {
	const mountPoints = /* @__PURE__ */ new Set();
	for (const line of mountInfo.split(/\r?\n/u)) {
		const trimmed = line.trim();
		if (!trimmed) continue;
		const mountPoint = trimmed.split(" ")[4];
		if (!mountPoint) continue;
		mountPoints.add(path.resolve(decodeMountInfoPath(mountPoint)));
	}
	return mountPoints;
}
function readLinuxMountPoints() {
	const metadata = getPluginCache().metadata;
	if (!metadata.discoveryMountPoints) try {
		metadata.discoveryMountPoints = parseLinuxMountInfoMountPoints(fsSync.readFileSync("/proc/self/mountinfo", "utf8"));
	} catch {
		metadata.discoveryMountPoints = /* @__PURE__ */ new Set();
	}
	return metadata.discoveryMountPoints;
}
function isFilesystemMountPoint(targetPath) {
	const target = pluginCacheStatSync(targetPath);
	const parent = pluginCacheStatSync(path.dirname(targetPath));
	return Boolean(target && parent && (target.dev !== parent.dev || target.ino === parent.ino));
}
function sourceOverlaysDisabled(env) {
	const raw = normalizeOptionalLowercaseString(env.OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS);
	return raw === "1" || raw === "true";
}
/** True when a path appears to be a mounted bundled source overlay. */
function isBundledSourceOverlayPath(params) {
	const resolved = path.resolve(params.sourcePath);
	return (params.mountPoints ?? readLinuxMountPoints()).has(resolved) || isFilesystemMountPoint(resolved);
}
/** Lists source overlay directories that shadow packaged bundled plugin dirs. */
function listBundledSourceOverlayDirs(params) {
	if (sourceOverlaysDisabled(params.env ?? process.env) || !params.bundledRoot) return [];
	const legacyRoot = buildLegacyBundledRootPath(params.bundledRoot);
	if (!legacyRoot || !pluginCacheExistsSync(legacyRoot)) return [];
	let entries;
	try {
		entries = readPluginCacheDirectory(legacyRoot);
	} catch {
		return [];
	}
	const mountPoints = params.mountPoints ?? readLinuxMountPoints();
	const legacyRootMounted = isBundledSourceOverlayPath({
		sourcePath: legacyRoot,
		mountPoints
	});
	const overlayDirs = [];
	for (const entry of entries) {
		if (!entry.isDirectory()) continue;
		const sourceDir = path.join(legacyRoot, entry.name);
		if (!pluginCacheExistsSync(path.join(params.bundledRoot, entry.name))) continue;
		if (!legacyRootMounted && !isBundledSourceOverlayPath({
			sourcePath: sourceDir,
			mountPoints
		})) continue;
		overlayDirs.push(sourceDir);
	}
	return overlayDirs.toSorted((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/plugins/candidate-install-owner.ts
const PLUGIN_CANDIDATE_INSTALL_OWNER = Symbol.for("openclaw.pluginCandidateInstallOwner");
function recordPluginCandidateInstallOwner(candidate, installOwner, ambiguous = false) {
	if (!installOwner && !ambiguous) return candidate;
	Object.defineProperty(candidate, PLUGIN_CANDIDATE_INSTALL_OWNER, {
		configurable: true,
		enumerable: true,
		value: ambiguous ? { ambiguous: true } : { installOwner }
	});
	return candidate;
}
function readPluginCandidateInstallOwner(candidate) {
	return candidate[PLUGIN_CANDIDATE_INSTALL_OWNER];
}
function resolvePluginCandidateInstallOwner(candidate) {
	return readPluginCandidateInstallOwner(candidate)?.installOwner;
}
function isPluginCandidateInstallOwnerAmbiguous(candidate) {
	return readPluginCandidateInstallOwner(candidate)?.ambiguous === true;
}
//#endregion
//#region packages/llm-core/src/model-data.ts
const MODEL_DATA_APIS = [
	"openai-completions",
	"openai-responses",
	"openai-chatgpt-responses",
	"anthropic-messages",
	"google-generative-ai",
	"google-interactions",
	"google-vertex",
	"github-copilot",
	"bedrock-converse-stream",
	"ollama",
	"pi-messages",
	"azure-openai-responses"
];
const MODEL_DATA_THINKING_FORMATS = [
	"openai",
	"openrouter",
	"deepseek",
	"together",
	"qwen",
	"qwen-chat-template",
	"zai"
];
const MODEL_DATA_THINKING_LEVELS = [
	"off",
	"minimal",
	"low",
	"medium",
	"high",
	"xhigh",
	"max"
];
//#endregion
//#region packages/model-catalog-core/src/model-catalog-types.ts
/** Supported API protocols for model catalog entries. */
const MODEL_CATALOG_APIS = [...MODEL_DATA_APIS];
/** Supported model thinking/reasoning wire formats. */
const MODEL_CATALOG_THINKING_FORMATS = [...MODEL_DATA_THINKING_FORMATS];
/** Narrow a string to a supported model catalog thinking format. */
function isModelCatalogThinkingFormat(value) {
	return MODEL_CATALOG_THINKING_FORMATS.includes(value);
}
/** Model-level thinking settings carried by provider catalog metadata. */
const MODEL_CATALOG_THINKING_LEVELS = [...MODEL_DATA_THINKING_LEVELS];
//#endregion
//#region packages/model-catalog-core/src/model-catalog-context-windows.ts
function normalizePositiveInteger$1(value) {
	return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
}
function normalizeModelCatalogContextWindows(value) {
	if (!Array.isArray(value)) return;
	const seen = /* @__PURE__ */ new Set();
	const contextWindows = value.slice(0, 16).flatMap((entry) => {
		if (!isRecord(entry)) return [];
		const id = normalizeOptionalString(entry.id) ?? "";
		const label = normalizeOptionalString(entry.label) ?? "";
		const contextWindow = normalizePositiveInteger$1(entry.contextWindow);
		if (!id || !label || contextWindow === void 0 || seen.has(id)) return [];
		seen.add(id);
		return [{
			id,
			label,
			contextWindow
		}];
	});
	return contextWindows.length > 0 ? contextWindows.toSorted((left, right) => left.contextWindow - right.contextWindow || left.id.localeCompare(right.id)) : void 0;
}
function normalizeModelCatalogContextWindowSelection(value) {
	const contextWindows = normalizeModelCatalogContextWindows(value.contextWindows);
	const contextWindowDefault = normalizeOptionalString(value.contextWindowDefault);
	return contextWindowDefault && contextWindows?.some((option) => option.id === contextWindowDefault) ? {
		contextWindows,
		contextWindowDefault
	} : {};
}
//#endregion
//#region packages/model-catalog-core/src/model-catalog-normalize.ts
const MODEL_CATALOG_INPUTS = /* @__PURE__ */ new Set([
	"text",
	"image",
	"document"
]);
const MODEL_CATALOG_DISCOVERY_MODES = /* @__PURE__ */ new Set([
	"static",
	"refreshable",
	"runtime"
]);
const MODEL_CATALOG_STATUSES = /* @__PURE__ */ new Set([
	"available",
	"preview",
	"deprecated",
	"disabled"
]);
const MODEL_CATALOG_API_SET = new Set(MODEL_CATALOG_APIS);
/** Reject object keys that can mutate prototypes when copied into records. */
function isBlockedObjectKey(key) {
	return key === "__proto__" || key === "prototype" || key === "constructor";
}
function normalizeModelCatalogThinkingLevelMap(value) {
	if (!isRecord(value)) return;
	const normalized = {};
	for (const level of MODEL_CATALOG_THINKING_LEVELS) {
		const mapped = value[level];
		if (mapped === null) {
			normalized[level] = null;
			continue;
		}
		const normalizedValue = normalizeOptionalString(mapped);
		if (normalizedValue !== void 0) normalized[level] = normalizedValue;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeSafeRecordKey(value) {
	const key = normalizeOptionalString(value) ?? "";
	return key && !isBlockedObjectKey(key) ? key : "";
}
function normalizeOwnedProviderSet(providers) {
	const normalized = /* @__PURE__ */ new Set();
	for (const provider of providers) {
		const providerId = normalizeProviderId(provider);
		if (providerId) normalized.add(providerId);
	}
	return normalized;
}
function normalizeStringMap(value) {
	if (!isRecord(value)) return;
	const normalized = {};
	for (const [rawKey, rawValue] of Object.entries(value)) {
		const key = normalizeSafeRecordKey(rawKey);
		const mapValue = normalizeOptionalString(rawValue) ?? "";
		if (key && mapValue) normalized[key] = mapValue;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeModelCatalogApi(value) {
	const api = normalizeOptionalString(value) ?? "";
	return MODEL_CATALOG_API_SET.has(api) ? api : void 0;
}
function normalizeModelCatalogInputs(value) {
	const inputs = normalizeTrimmedStringList(value).filter((input) => MODEL_CATALOG_INPUTS.has(input));
	return inputs.length > 0 ? inputs : void 0;
}
function normalizePositiveInteger(value) {
	return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
}
function normalizeModelCatalogTieredCost(value) {
	if (!Array.isArray(value)) return;
	const normalized = [];
	for (const entry of value) {
		if (!isRecord(entry) || !Array.isArray(entry.range)) continue;
		const input = asNonNegativeFiniteNumber(entry.input);
		const output = asNonNegativeFiniteNumber(entry.output);
		const cacheRead = asNonNegativeFiniteNumber(entry.cacheRead);
		const cacheWrite = asNonNegativeFiniteNumber(entry.cacheWrite);
		if (input === void 0 || output === void 0 || cacheRead === void 0 || cacheWrite === void 0 || entry.range.length < 1 || entry.range.length > 2) continue;
		const rangeValues = entry.range.map((rangeValue) => asNonNegativeFiniteNumber(rangeValue));
		if (rangeValues.some((rangeValue) => rangeValue === void 0)) continue;
		normalized.push({
			input,
			output,
			cacheRead,
			cacheWrite,
			range: rangeValues.length === 1 ? [rangeValues[0]] : [rangeValues[0], rangeValues[1]]
		});
	}
	return normalized.length > 0 ? normalized : void 0;
}
function normalizeModelCatalogCost(value) {
	if (!isRecord(value)) return;
	const cost = {};
	for (const field of [
		"input",
		"output",
		"cacheRead",
		"cacheWrite"
	]) {
		const normalized = asNonNegativeFiniteNumber(value[field]);
		if (normalized !== void 0) cost[field] = normalized;
	}
	const tieredPricing = normalizeModelCatalogTieredCost(value.tieredPricing);
	if (tieredPricing) cost.tieredPricing = tieredPricing;
	return Object.keys(cost).length > 0 ? cost : void 0;
}
function normalizeOpenRouterPrice(value) {
	if (!isRecord(value)) return;
	const maxPrice = {};
	for (const field of [
		"prompt",
		"completion",
		"image",
		"audio",
		"request"
	]) {
		const candidate = value[field];
		const normalized = normalizeOptionalString(candidate) ?? asFiniteNumber(candidate);
		if (normalized !== void 0) maxPrice[field] = normalized;
	}
	return Object.keys(maxPrice).length > 0 ? maxPrice : void 0;
}
function normalizeOpenRouterMetricPreference(value) {
	const numeric = asFiniteNumber(value);
	if (numeric !== void 0) return numeric;
	if (!isRecord(value)) return;
	const normalized = {};
	for (const field of [
		"p50",
		"p75",
		"p90",
		"p99"
	]) {
		const cutoff = asFiniteNumber(value[field]);
		if (cutoff !== void 0) normalized[field] = cutoff;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeOpenRouterSort(value) {
	const sort = normalizeOptionalString(value);
	if (sort) return sort;
	if (!isRecord(value)) return;
	const by = normalizeOptionalString(value.by);
	const partition = value.partition === null ? null : normalizeOptionalString(value.partition) ?? void 0;
	const normalized = {
		...by ? { by } : {},
		...partition !== void 0 ? { partition } : {}
	};
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeOpenRouterRouting(value) {
	if (!isRecord(value)) return;
	const routing = {
		...typeof value.allow_fallbacks === "boolean" ? { allow_fallbacks: value.allow_fallbacks } : {},
		...typeof value.require_parameters === "boolean" ? { require_parameters: value.require_parameters } : {},
		...value.data_collection === "deny" || value.data_collection === "allow" ? { data_collection: value.data_collection } : {},
		...typeof value.zdr === "boolean" ? { zdr: value.zdr } : {},
		...typeof value.enforce_distillable_text === "boolean" ? { enforce_distillable_text: value.enforce_distillable_text } : {}
	};
	for (const field of [
		"order",
		"only",
		"ignore",
		"quantizations"
	]) {
		const normalized = normalizeOptionalTrimmedStringList(value[field]);
		if (normalized) routing[field] = normalized;
	}
	const sort = normalizeOpenRouterSort(value.sort);
	if (sort) routing.sort = sort;
	const maxPrice = normalizeOpenRouterPrice(value.max_price);
	if (maxPrice) routing.max_price = maxPrice;
	for (const field of ["preferred_min_throughput", "preferred_max_latency"]) {
		const normalized = normalizeOpenRouterMetricPreference(value[field]);
		if (normalized !== void 0) routing[field] = normalized;
	}
	return Object.keys(routing).length > 0 ? routing : void 0;
}
function normalizeVercelGatewayRouting(value) {
	if (!isRecord(value)) return;
	const routing = {};
	for (const field of ["only", "order"]) {
		const normalized = normalizeOptionalTrimmedStringList(value[field]);
		if (normalized) routing[field] = normalized;
	}
	return Object.keys(routing).length > 0 ? routing : void 0;
}
function normalizeModelCatalogCompat(value) {
	if (!isRecord(value)) return;
	const compat = {};
	for (const field of [
		"supportsStore",
		"supportsPromptCacheKey",
		"supportsDeveloperRole",
		"supportsReasoningEffort",
		"supportsTemperature",
		"supportsInstructions",
		"supportsUsageInStreaming",
		"supportsTools",
		"supportsStrictMode",
		"supportsJsonSchemaResponseFormat",
		"requiresStringContent",
		"strictMessageKeys",
		"requiresToolResultName",
		"requiresAssistantAfterToolResult",
		"requiresThinkingAsText",
		"requiresReasoningContentOnAssistantMessages",
		"zaiToolStream",
		"sendSessionAffinityHeaders",
		"sendSessionIdHeader",
		"supportsEagerToolInputStreaming",
		"supportsLongCacheRetention",
		"supportsResponsesContinuation",
		"requiresOpenAiAnthropicToolPayload"
	]) if (typeof value[field] === "boolean") compat[field] = value[field];
	for (const field of ["toolSchemaProfile", "toolCallArgumentsEncoding"]) {
		const normalized = normalizeOptionalString(value[field]) ?? "";
		if (normalized) compat[field] = normalized;
	}
	for (const field of [
		"visibleReasoningDetailTypes",
		"supportedReasoningEfforts",
		"unsupportedToolSchemaKeywords"
	]) {
		const normalized = normalizeTrimmedStringList(value[field]);
		if (normalized.length > 0 || field === "supportedReasoningEfforts" && Array.isArray(value[field])) compat[field] = normalized;
	}
	if (isRecord(value.reasoningEffortMap)) {
		const reasoningEffortMap = Object.fromEntries(Object.entries(value.reasoningEffortMap).flatMap(([rawKey, rawMapped]) => {
			const key = rawKey.trim();
			const mapped = typeof rawMapped === "string" ? rawMapped.trim() : "";
			return key && mapped ? [[key, mapped]] : [];
		}));
		if (Object.keys(reasoningEffortMap).length > 0) compat.reasoningEffortMap = reasoningEffortMap;
	}
	const codeMode = normalizeOptionalString(value.codeMode) ?? "";
	if (codeMode === "preferred" || codeMode === "capable") compat.codeMode = codeMode;
	const maxTokensField = normalizeOptionalString(value.maxTokensField) ?? "";
	if (maxTokensField === "max_completion_tokens" || maxTokensField === "max_tokens") compat.maxTokensField = maxTokensField;
	const thinkingFormat = normalizeOptionalString(value.thinkingFormat) ?? "";
	if (isModelCatalogThinkingFormat(thinkingFormat)) compat.thinkingFormat = thinkingFormat;
	if (value.cacheControlFormat === "anthropic") compat.cacheControlFormat = "anthropic";
	const openRouterRouting = normalizeOpenRouterRouting(value.openRouterRouting);
	if (openRouterRouting) compat.openRouterRouting = openRouterRouting;
	const vercelGatewayRouting = normalizeVercelGatewayRouting(value.vercelGatewayRouting);
	if (vercelGatewayRouting) compat.vercelGatewayRouting = vercelGatewayRouting;
	return Object.keys(compat).length > 0 ? compat : void 0;
}
function normalizeModelCatalogStatus(value) {
	const status = normalizeOptionalString(value) ?? "";
	return MODEL_CATALOG_STATUSES.has(status) ? status : void 0;
}
function normalizeModelCatalogImageTokenMode(value) {
	const tokenMode = normalizeOptionalString(value) ?? "";
	if (tokenMode === "tile" || tokenMode === "detail" || tokenMode === "provider") return tokenMode;
}
function normalizeModelCatalogMediaInput(value) {
	if (!isRecord(value) || !isRecord(value.image)) return;
	const normalizedImage = {};
	for (const field of [
		"maxBytes",
		"maxPixels",
		"maxSidePx",
		"preferredSidePx"
	]) {
		const normalized = normalizePositiveInteger(value.image[field]);
		if (normalized !== void 0) normalizedImage[field] = normalized;
	}
	const tokenMode = normalizeModelCatalogImageTokenMode(value.image.tokenMode);
	if (tokenMode) normalizedImage.tokenMode = tokenMode;
	return Object.keys(normalizedImage).length > 0 ? { image: normalizedImage } : void 0;
}
function normalizeModelCatalogModel(value) {
	if (!isRecord(value)) return;
	const id = normalizeOptionalString(value.id) ?? "";
	if (!id) return;
	const name = normalizeOptionalString(value.name) ?? "";
	const api = normalizeModelCatalogApi(value.api);
	const baseUrl = normalizeOptionalString(value.baseUrl) ?? "";
	const headers = normalizeStringMap(value.headers);
	const input = normalizeModelCatalogInputs(value.input);
	const reasoning = typeof value.reasoning === "boolean" ? value.reasoning : void 0;
	const contextWindow = asPositiveFiniteNumber(value.contextWindow);
	const contextWindowSelection = normalizeModelCatalogContextWindowSelection(value);
	const contextTokens = normalizePositiveInteger(value.contextTokens);
	const maxTokens = asPositiveFiniteNumber(value.maxTokens);
	const thinkingLevelMap = normalizeModelCatalogThinkingLevelMap(value.thinkingLevelMap);
	const cost = normalizeModelCatalogCost(value.cost);
	const compat = normalizeModelCatalogCompat(value.compat);
	const mediaInput = normalizeModelCatalogMediaInput(value.mediaInput);
	const status = normalizeModelCatalogStatus(value.status);
	const statusReason = normalizeOptionalString(value.statusReason) ?? "";
	const replaces = normalizeTrimmedStringList(value.replaces);
	const replacedBy = normalizeOptionalString(value.replacedBy) ?? "";
	const tags = normalizeTrimmedStringList(value.tags);
	return {
		id,
		...name ? { name } : {},
		...api ? { api } : {},
		...baseUrl ? { baseUrl } : {},
		...headers ? { headers } : {},
		...input ? { input } : {},
		...reasoning !== void 0 ? { reasoning } : {},
		...contextWindow !== void 0 ? { contextWindow } : {},
		...contextWindowSelection,
		...contextTokens !== void 0 ? { contextTokens } : {},
		...maxTokens !== void 0 ? { maxTokens } : {},
		...thinkingLevelMap ? { thinkingLevelMap } : {},
		...cost ? { cost } : {},
		...compat ? { compat } : {},
		...mediaInput ? { mediaInput } : {},
		...status ? { status } : {},
		...statusReason ? { statusReason } : {},
		...replaces.length > 0 ? { replaces } : {},
		...replacedBy ? { replacedBy } : {},
		...tags.length > 0 ? { tags } : {}
	};
}
function normalizeModelCatalogProvider(value) {
	if (!isRecord(value)) return;
	const models = Array.isArray(value.models) ? value.models.map((entry) => normalizeModelCatalogModel(entry)).filter((entry) => Boolean(entry)) : [];
	if (models.length === 0) return;
	const baseUrl = normalizeOptionalString(value.baseUrl) ?? "";
	const api = normalizeModelCatalogApi(value.api);
	const headers = normalizeStringMap(value.headers);
	const defaultModel = normalizeOptionalString(value.defaultModel) ?? "";
	const defaultUtilityModel = normalizeOptionalString(value.defaultUtilityModel) ?? "";
	const recommended = Array.isArray(value.recommendedModels) ? value.recommendedModels.map(normalizeOptionalString) : [];
	const recommendedModels = recommended.length > 0 && new Set(recommended).size === recommended.length && recommended.every((id) => Boolean(id) && models.some((model) => model.id === id)) ? recommended : [];
	return {
		...baseUrl ? { baseUrl } : {},
		...api ? { api } : {},
		...headers ? { headers } : {},
		...defaultModel ? { defaultModel } : {},
		...defaultUtilityModel ? { defaultUtilityModel } : {},
		...recommendedModels.length > 0 ? { recommendedModels } : {},
		models
	};
}
function normalizeModelCatalogProviders(value, ownedProviders) {
	if (!isRecord(value)) return;
	const providers = {};
	for (const [rawProviderId, rawProvider] of Object.entries(value)) {
		const providerId = normalizeProviderId(rawProviderId);
		if (!providerId || !ownedProviders.has(providerId)) continue;
		const provider = normalizeModelCatalogProvider(rawProvider);
		if (provider) providers[providerId] = provider;
	}
	return Object.keys(providers).length > 0 ? providers : void 0;
}
function normalizeModelCatalogAliases(value, ownedProviders) {
	if (!isRecord(value)) return;
	const aliases = {};
	for (const [rawAlias, rawTarget] of Object.entries(value)) {
		const alias = normalizeProviderId(rawAlias);
		if (!alias || !isRecord(rawTarget)) continue;
		const provider = normalizeProviderId(normalizeOptionalString(rawTarget.provider) ?? "");
		if (!provider || !ownedProviders.has(provider)) continue;
		const api = normalizeModelCatalogApi(rawTarget.api);
		const baseUrl = normalizeOptionalString(rawTarget.baseUrl) ?? "";
		aliases[alias] = {
			provider,
			...api ? { api } : {},
			...baseUrl ? { baseUrl } : {}
		};
	}
	return Object.keys(aliases).length > 0 ? aliases : void 0;
}
function normalizeModelCatalogSuppressions(value) {
	if (!Array.isArray(value)) return;
	const suppressions = [];
	for (const entry of value) {
		if (!isRecord(entry)) continue;
		const provider = normalizeProviderId(normalizeOptionalString(entry.provider) ?? "");
		const model = normalizeOptionalString(entry.model) ?? "";
		if (!provider || !model) continue;
		const reason = normalizeOptionalString(entry.reason) ?? "";
		const replacedBy = isRecord(entry.retirement) ? normalizeOptionalString(entry.retirement.replacedBy) : void 0;
		const retirement = isRecord(entry.retirement) && (entry.retirement.replacedBy === void 0 || replacedBy) ? replacedBy ? { replacedBy } : {} : void 0;
		const rawWhen = isRecord(entry.when) ? entry.when : void 0;
		const baseUrlHosts = normalizeTrimmedStringList(rawWhen?.baseUrlHosts).map((host) => host.toLowerCase());
		const providerConfigApiIn = normalizeTrimmedStringList(rawWhen?.providerConfigApiIn).map((api) => api.toLowerCase());
		const when = baseUrlHosts.length > 0 || providerConfigApiIn.length > 0 ? {
			...baseUrlHosts.length > 0 ? { baseUrlHosts } : {},
			...providerConfigApiIn.length > 0 ? { providerConfigApiIn } : {}
		} : void 0;
		if (retirement && entry.when !== void 0 && (!rawWhen || !when || rawWhen.baseUrlHosts !== void 0 && baseUrlHosts.length === 0 || rawWhen.providerConfigApiIn !== void 0 && providerConfigApiIn.length === 0)) continue;
		suppressions.push({
			provider,
			model,
			...reason ? { reason } : {},
			...retirement ? { retirement } : {},
			...when ? { when } : {}
		});
	}
	return suppressions.length > 0 ? suppressions : void 0;
}
function normalizeModelCatalogDiscovery(value, ownedProviders) {
	if (!isRecord(value)) return;
	const discovery = {};
	for (const [rawProviderId, rawMode] of Object.entries(value)) {
		const providerId = normalizeProviderId(rawProviderId);
		const mode = normalizeOptionalString(rawMode) ?? "";
		if (providerId && ownedProviders.has(providerId) && MODEL_CATALOG_DISCOVERY_MODES.has(mode)) discovery[providerId] = mode;
	}
	return Object.keys(discovery).length > 0 ? discovery : void 0;
}
/** Normalize a raw model catalog object for the set of providers owned by a plugin/manifest. */
function normalizeModelCatalog(value, params) {
	if (!isRecord(value)) return;
	const ownedProviders = normalizeOwnedProviderSet(params.ownedProviders);
	const modelsDev = Object.fromEntries(Object.entries(normalizeStringMap(value.modelsDev) ?? {}).flatMap(([rawProviderId, sourceId]) => {
		const providerId = normalizeProviderId(rawProviderId);
		return ownedProviders.has(providerId) && !isBlockedObjectKey(providerId) ? [[providerId, sourceId]] : [];
	}));
	const providers = normalizeModelCatalogProviders(value.providers, ownedProviders);
	const aliases = normalizeModelCatalogAliases(value.aliases, ownedProviders);
	const suppressions = normalizeModelCatalogSuppressions(value.suppressions);
	const discovery = normalizeModelCatalogDiscovery(value.discovery, ownedProviders);
	const runtimeAugment = value.runtimeAugment === true;
	const catalog = {
		...Object.keys(modelsDev).length > 0 ? { modelsDev } : {},
		...providers ? { providers } : {},
		...aliases ? { aliases } : {},
		...suppressions ? { suppressions } : {},
		...discovery ? { discovery } : {},
		...runtimeAugment ? { runtimeAugment } : {}
	};
	return Object.keys(catalog).length > 0 ? catalog : void 0;
}
//#endregion
//#region packages/gateway-protocol/src/plugin-ui-capabilities.ts
/** Static UI contribution kinds, in canonical presentation order. */
const PLUGIN_UI_CAPABILITIES = [
	"page",
	"navigation",
	"panel",
	"action",
	"accessory",
	"widget",
	"replacement",
	"link-reader"
];
/** Omission is unspecified; an empty declaration explicitly advertises no UI. */
function validatePluginUiCapabilities(value) {
	if (value === void 0) return { ok: true };
	if (!Array.isArray(value)) return {
		ok: false,
		error: "must be an array"
	};
	for (const entry of value) if (!PLUGIN_UI_CAPABILITIES.some((candidate) => candidate === entry)) return {
		ok: false,
		error: `contains unknown UI capability ${JSON.stringify(entry)}`
	};
	return {
		ok: true,
		capabilities: PLUGIN_UI_CAPABILITIES.filter((capability) => value.includes(capability))
	};
}
//#endregion
//#region packages/plugin-package-contract/src/categories.ts
/** Active browse taxonomy, ordered with the core configuration surfaces first. */
const PLUGIN_CATEGORY_SLUGS = [
	"channels",
	"models",
	"agent-runtimes",
	"memory",
	"context",
	"voice",
	"web",
	"computer-use",
	"media",
	"security",
	"integrations",
	"developer-tools",
	"infrastructure",
	"documents-files",
	"inbox-collaboration",
	"productivity",
	"scheduling",
	"finance-payments",
	"sales-marketing",
	"data-analytics",
	"agent-orchestration",
	"research",
	"other"
];
/** Published declarations remain readable while authors adopt the active taxonomy. */
const LEGACY_PLUGIN_CATEGORY_SLUGS = [
	"tools",
	"runtime",
	"gateway"
];
const ACCEPTED_PLUGIN_CATEGORY_SLUGS = [...PLUGIN_CATEGORY_SLUGS, ...LEGACY_PLUGIN_CATEGORY_SLUGS];
/** Validate optional ordered package-owned plugin categories. */
function validatePluginCategories(value) {
	if (value === void 0) return { ok: true };
	if (!Array.isArray(value)) return {
		ok: false,
		error: "must be an array"
	};
	if (value.length < 1 || value.length > 3) return {
		ok: false,
		error: "must contain between 1 and 3 entries"
	};
	const categories = [];
	for (const entry of value) {
		const category = ACCEPTED_PLUGIN_CATEGORY_SLUGS.find((candidate) => candidate === entry);
		if (!category) return {
			ok: false,
			error: `contains unknown category ${JSON.stringify(entry)}`
		};
		if (categories.includes(category)) return {
			ok: false,
			error: "must not contain duplicates"
		};
		categories.push(category);
	}
	return {
		ok: true,
		categories
	};
}
//#endregion
//#region src/plugins/doctor-session-route-state-owner-types.ts
function isDoctorSessionRouteStateOwner(value) {
	if (!value || typeof value !== "object") return false;
	const candidate = value;
	return typeof candidate.id === "string" && typeof candidate.label === "string" && candidate.id.trim().length > 0 && candidate.label.trim().length > 0 && (candidate.providerIds === void 0 || normalizeTrimmedStringList(candidate.providerIds).length > 0) && (candidate.runtimeIds === void 0 || normalizeTrimmedStringList(candidate.runtimeIds).length > 0) && (candidate.cliSessionKeys === void 0 || normalizeTrimmedStringList(candidate.cliSessionKeys).length > 0) && (candidate.authProfilePrefixes === void 0 || normalizeTrimmedStringList(candidate.authProfilePrefixes).length > 0);
}
function coerceDoctorSessionRouteStateOwners(value) {
	if (!Array.isArray(value)) return [];
	return value.filter(isDoctorSessionRouteStateOwner).map((owner) => ({
		id: owner.id.trim(),
		label: owner.label.trim(),
		providerIds: normalizeTrimmedStringList(owner.providerIds),
		runtimeIds: normalizeTrimmedStringList(owner.runtimeIds),
		cliSessionKeys: normalizeTrimmedStringList(owner.cliSessionKeys),
		authProfilePrefixes: normalizeTrimmedStringList(owner.authProfilePrefixes)
	}));
}
const PLUGIN_MANIFEST_CONTRACT_KEYS = [
	"codeModeExecutors",
	"embeddedExtensionFactories",
	"agentToolResultMiddleware",
	"trustedToolPolicies",
	"externalAuthProviders",
	"decisionProviders",
	"embeddingProviders",
	"speechProviders",
	"realtimeTranscriptionProviders",
	"realtimeVoiceProviders",
	"mediaUnderstandingProviders",
	"transcriptSourceProviders",
	"documentExtractors",
	"imageGenerationProviders",
	"videoGenerationProviders",
	"musicGenerationProviders",
	"webContentExtractors",
	"webFetchProviders",
	"webSearchProviders",
	"workerProviders",
	"usageProviders",
	"migrationProviders",
	"gatewayMethodDispatch",
	"tools"
];
//#endregion
//#region src/plugins/manifest-capability-normalizers.ts
function normalizeDecisionCapabilities(value) {
	if (!isRecord(value) || !Array.isArray(value.questionTypes) || value.questionTypes.length === 0 || value.questionTypes.length > 3 || !value.questionTypes.every((kind) => kind === "boolean" || kind === "choice" || kind === "score")) return;
	const capabilities = { questionTypes: [...new Set(value.questionTypes)] };
	for (const key of [
		"maxQuestions",
		"maxChoiceAlternatives",
		"maxScoreLevels",
		"maxInputTokens"
	]) {
		const limit = value[key];
		if (typeof limit === "number" && Number.isSafeInteger(limit) && limit > 0) capabilities[key] = limit;
	}
	if (value.inputTokenScope === "encoded-question" || value.inputTokenScope === "state-plus-each-criterion") capabilities.inputTokenScope = value.inputTokenScope;
	if (typeof value.requiresBooleanCriteria === "boolean") capabilities.requiresBooleanCriteria = value.requiresBooleanCriteria;
	if (value.confidence === "provider-specific" || value.confidence === "none") capabilities.confidence = value.confidence;
	return capabilities;
}
/** Normalize provider-owned model descriptors without importing provider code. */
function normalizeManifestDecisionModels(value, providers) {
	const seen = /* @__PURE__ */ new Set();
	return normalizeManifestObjectList(value, (entry) => {
		const provider = normalizeOptionalString(entry.provider);
		const id = normalizeOptionalString(entry.id);
		const name = normalizeOptionalString(entry.name);
		if (!provider || !id || !name || !providers?.includes(provider)) return;
		const ref = `${provider}/${id}`;
		if (seen.has(ref)) return;
		const capabilities = normalizeDecisionCapabilities(entry.capabilities);
		seen.add(ref);
		return {
			provider,
			id,
			name,
			...capabilities ? { capabilities } : {}
		};
	});
}
/** Endpoint restrictions constrain a provider alias without changing stored credential identity. */
function normalizeManifestProviderAuthAliases(value) {
	return normalizeManifestRecord(value, (entry) => {
		if (typeof entry === "string") return normalizeOptionalString(entry);
		if (isRecord(entry)) {
			const provider = normalizeOptionalString(entry.provider);
			const baseUrls = normalizeTrimmedStringList(entry.baseUrls);
			if (provider && baseUrls.length > 0) return {
				provider,
				baseUrls
			};
		}
	});
}
function isPluginToolProfile(profile) {
	return profile === "minimal" || profile === "coding" || profile === "messaging" || profile === "full";
}
function normalizeStringListRecord(value) {
	return normalizeManifestRecord(value, normalizeOptionalTrimmedStringList);
}
function normalizeManifestStringRecord(value) {
	return normalizeManifestRecord(value, normalizeOptionalString);
}
function normalizeManifestMcpServers(value) {
	return normalizeNamedMetadataRecord(value, (server) => ({ ...server }));
}
function normalizeManifestRecord(value, normalizeEntry) {
	if (!isRecord(value)) return;
	const normalized = Object.create(null);
	for (const [rawId, rawEntry] of Object.entries(value)) {
		const id = normalizeOptionalString(rawId) ?? "";
		const entry = !id || isBlockedObjectKey$1(id) ? void 0 : normalizeEntry(rawEntry, id);
		if (entry) normalized[id] = entry;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeNamedMetadataRecord(value, normalizeEntry) {
	return normalizeManifestRecord(value, (entry, id) => isRecord(entry) ? normalizeEntry(entry, id) : void 0);
}
function normalizeManifestObjectList(value, normalizeEntry) {
	if (!Array.isArray(value)) return;
	const normalized = [];
	for (const entry of value) {
		const result = isRecord(entry) ? normalizeEntry(entry) : void 0;
		if (result !== void 0) normalized.push(result);
	}
	return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestTranscriptSources(value, ownedProviders = []) {
	const locatorKeys = [
		"accountId",
		"guildId",
		"channelId",
		"meetingUrl"
	];
	return normalizeNamedMetadataRecord(value, (entry, id) => {
		if (!ownedProviders.includes(id)) return;
		const name = normalizeOptionalString(entry.name);
		const raw = entry.autoStart;
		const autoStart = isRecord(raw) && Object.entries(raw).every(([key, mode]) => locatorKeys.some((locator) => locator === key) && (mode === "optional" || mode === "required")) ? Object.fromEntries(Object.entries(raw)) : void 0;
		return name || autoStart ? {
			...name ? { name } : {},
			...autoStart ? { autoStart } : {}
		} : void 0;
	});
}
function isMediaUnderstandingCapability(value) {
	return value === "image" || value === "audio" || value === "video";
}
function normalizeMediaUnderstandingRecord(value, normalizeValue) {
	if (!isRecord(value)) return;
	const normalized = {};
	for (const [rawKey, rawValue] of Object.entries(value)) {
		if (!isMediaUnderstandingCapability(rawKey)) continue;
		const entry = normalizeValue(rawValue);
		if (entry !== void 0) normalized[rawKey] = entry;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeMediaUnderstandingCapabilities(value) {
	const values = normalizeTrimmedStringList(value).filter(isMediaUnderstandingCapability);
	return values.length > 0 ? values : void 0;
}
function normalizeMediaUnderstandingNativeDocumentInputs(value) {
	const values = normalizeTrimmedStringList(value).filter((entry) => entry === "pdf");
	return values.length > 0 ? values : void 0;
}
function normalizeMediaUnderstandingDocumentModels(value) {
	if (!isRecord(value)) return;
	const pdfRaw = value.pdf;
	if (!isRecord(pdfRaw)) return;
	const textExtraction = normalizeOptionalString(pdfRaw.textExtraction);
	const image = pdfRaw.image === false ? false : normalizeOptionalString(pdfRaw.image);
	const pdf = {
		...textExtraction ? { textExtraction } : {},
		...image !== void 0 ? { image } : {}
	};
	return Object.keys(pdf).length > 0 ? { pdf } : void 0;
}
function normalizeMediaUnderstandingProviderMetadata(value) {
	return normalizeNamedMetadataRecord(value, (rawMetadata) => {
		const capabilities = normalizeMediaUnderstandingCapabilities(rawMetadata.capabilities);
		const defaultModels = normalizeMediaUnderstandingRecord(rawMetadata.defaultModels, normalizeOptionalString);
		const autoPriority = normalizeMediaUnderstandingRecord(rawMetadata.autoPriority, (priority) => typeof priority === "number" && Number.isFinite(priority) ? priority : void 0);
		const nativeDocumentInputs = normalizeMediaUnderstandingNativeDocumentInputs(rawMetadata.nativeDocumentInputs);
		const documentModels = normalizeMediaUnderstandingDocumentModels(rawMetadata.documentModels);
		const metadata = {
			...capabilities ? { capabilities } : {},
			...defaultModels ? { defaultModels } : {},
			...autoPriority ? { autoPriority } : {},
			...nativeDocumentInputs ? { nativeDocumentInputs } : {},
			...documentModels ? { documentModels } : {}
		};
		return Object.keys(metadata).length > 0 ? metadata : void 0;
	});
}
function normalizeProviderBaseUrlGuard(value) {
	if (!isRecord(value)) return;
	const provider = normalizeOptionalString(value.provider);
	const allowedBaseUrls = normalizeTrimmedStringList(value.allowedBaseUrls);
	if (!provider || allowedBaseUrls.length === 0) return;
	const defaultBaseUrl = normalizeOptionalString(value.defaultBaseUrl);
	return {
		provider,
		...defaultBaseUrl ? { defaultBaseUrl } : {},
		allowedBaseUrls
	};
}
function normalizeCapabilityProviderAuthSignals(value) {
	return normalizeManifestObjectList(value, (rawSignal) => {
		const provider = normalizeOptionalString(rawSignal.provider);
		if (!provider) return;
		const providerBaseUrl = normalizeProviderBaseUrlGuard(rawSignal.providerBaseUrl);
		return {
			provider,
			...providerBaseUrl ? { providerBaseUrl } : {}
		};
	});
}
function normalizeCapabilityProviderModeConfigSignal(value) {
	if (!isRecord(value)) return;
	const pathResult = normalizeOptionalString(value.path);
	const defaultValue = normalizeOptionalString(value.default);
	const allowed = normalizeTrimmedStringList(value.allowed);
	const disallowed = normalizeTrimmedStringList(value.disallowed);
	const signal = {
		...pathResult ? { path: pathResult } : {},
		...defaultValue ? { default: defaultValue } : {},
		...allowed.length > 0 ? { allowed } : {},
		...disallowed.length > 0 ? { disallowed } : {}
	};
	return Object.keys(signal).length > 0 ? signal : void 0;
}
function normalizeCapabilityProviderConfigSignals(value) {
	return normalizeManifestObjectList(value, (rawSignal) => {
		const rootPath = normalizeOptionalString(rawSignal.rootPath);
		if (!rootPath) return;
		const overlayPath = normalizeOptionalString(rawSignal.overlayPath);
		const overlayMapPath = normalizeOptionalString(rawSignal.overlayMapPath);
		const required = normalizeTrimmedStringList(rawSignal.required);
		const requiredAny = normalizeTrimmedStringList(rawSignal.requiredAny);
		const mode = normalizeCapabilityProviderModeConfigSignal(rawSignal.mode);
		const signal = {
			rootPath,
			...overlayPath ? { overlayPath } : {},
			...overlayMapPath ? { overlayMapPath } : {},
			...required.length > 0 ? { required } : {},
			...requiredAny.length > 0 ? { requiredAny } : {},
			...mode ? { mode } : {}
		};
		return required.length > 0 || requiredAny.length > 0 || mode ? signal : void 0;
	});
}
function normalizeCapabilityProviderMetadataEntry(rawMetadata) {
	const aliases = normalizeTrimmedStringList(rawMetadata.aliases);
	const authProviders = normalizeTrimmedStringList(rawMetadata.authProviders);
	const authSignals = normalizeCapabilityProviderAuthSignals(rawMetadata.authSignals);
	const configSignals = normalizeCapabilityProviderConfigSignals(rawMetadata.configSignals);
	const referenceAudioInputs = rawMetadata.referenceAudioInputs === true ? true : void 0;
	const metadata = {
		...aliases.length > 0 ? { aliases } : {},
		...authProviders.length > 0 ? { authProviders } : {},
		...authSignals ? { authSignals } : {},
		...configSignals ? { configSignals } : {},
		...referenceAudioInputs ? { referenceAudioInputs } : {}
	};
	return Object.keys(metadata).length > 0 ? metadata : void 0;
}
function normalizeCapabilityProviderMetadata(value) {
	return normalizeNamedMetadataRecord(value, normalizeCapabilityProviderMetadataEntry);
}
function normalizePluginToolMetadata(value) {
	return normalizeNamedMetadataRecord(value, (rawMetadata) => {
		const providerMetadata = normalizeCapabilityProviderMetadataEntry(rawMetadata);
		const profiles = normalizeTrimmedStringList(rawMetadata.profiles).filter(isPluginToolProfile);
		const metadata = {
			...providerMetadata,
			...rawMetadata.optional === true ? { optional: true } : {},
			...profiles.length > 0 ? { profiles } : {},
			...rawMetadata.replaySafe === true ? { replaySafe: true } : {},
			...rawMetadata.sideEffecting === true ? { sideEffecting: true } : {}
		};
		return Object.keys(metadata).length > 0 ? metadata : void 0;
	});
}
function normalizeManifestCatalog(value) {
	if (!isRecord(value)) return;
	const featured = typeof value.featured === "boolean" ? value.featured : void 0;
	const order = typeof value.order === "number" && Number.isFinite(value.order) ? value.order : void 0;
	if (featured === void 0 && order === void 0) return;
	return {
		...featured !== void 0 ? { featured } : {},
		...order !== void 0 ? { order } : {}
	};
}
function normalizeManifestContracts(value) {
	if (!isRecord(value)) return;
	const contracts = {};
	for (const key of PLUGIN_MANIFEST_CONTRACT_KEYS) {
		const entries = normalizeTrimmedStringList(value[key]);
		if (entries.length > 0) contracts[key] = entries;
	}
	return Object.keys(contracts).length > 0 ? contracts : void 0;
}
function isManifestConfigLiteral(value) {
	return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
function normalizeManifestDangerousConfigFlags(value) {
	return normalizeManifestObjectList(value, (entry) => {
		const pathValue = normalizeOptionalString(entry.path) ?? "";
		return pathValue && isManifestConfigLiteral(entry.equals) ? {
			path: pathValue,
			equals: entry.equals
		} : void 0;
	});
}
function normalizeManifestSecretInputPaths(value) {
	return normalizeManifestObjectList(value, (entry) => {
		const pathLocal = normalizeOptionalString(entry.path) ?? "";
		if (!pathLocal) return;
		const expected = entry.expected === "string" ? entry.expected : void 0;
		const ownerKind = entry.ownerKind === "capability" || entry.ownerKind === "route" ? entry.ownerKind : void 0;
		return {
			path: pathLocal,
			...expected ? { expected } : {},
			...ownerKind ? { ownerKind } : {}
		};
	});
}
function normalizeManifestConfigContracts(value) {
	if (!isRecord(value)) return;
	const compatibilityMigrationPaths = normalizeTrimmedStringList(value.compatibilityMigrationPaths);
	const compatibilityRuntimePaths = normalizeTrimmedStringList(value.compatibilityRuntimePaths);
	const rawSecretInputs = isRecord(value.secretInputs) ? value.secretInputs : void 0;
	const dangerousFlags = normalizeManifestDangerousConfigFlags(value.dangerousFlags);
	const secretInputPaths = rawSecretInputs ? normalizeManifestSecretInputPaths(rawSecretInputs.paths) : void 0;
	const secretInputs = secretInputPaths ? {
		...typeof rawSecretInputs?.bundledDefaultEnabled === "boolean" ? { bundledDefaultEnabled: rawSecretInputs.bundledDefaultEnabled } : {},
		paths: secretInputPaths
	} : void 0;
	const configContracts = {
		...compatibilityMigrationPaths.length > 0 ? { compatibilityMigrationPaths } : {},
		...compatibilityRuntimePaths.length > 0 ? { compatibilityRuntimePaths } : {},
		...dangerousFlags ? { dangerousFlags } : {},
		...secretInputs ? { secretInputs } : {}
	};
	return Object.keys(configContracts).length > 0 ? configContracts : void 0;
}
//#endregion
//#region src/plugins/manifest-command-aliases.ts
/** Normalizes manifest-declared CLI command aliases. */
/** Normalizes manifest command alias records, omitting invalid entries. */
function normalizeManifestCommandAliases(value) {
	if (!Array.isArray(value)) return;
	const normalized = [];
	for (const entry of value) {
		if (typeof entry === "string") {
			const name = normalizeOptionalString(entry) ?? "";
			if (name) normalized.push({ name });
			continue;
		}
		if (!isRecord(entry)) continue;
		const name = normalizeOptionalString(entry.name) ?? "";
		if (!name) continue;
		const kind = entry.kind === "runtime-slash" ? entry.kind : void 0;
		const cliCommand = normalizeOptionalString(entry.cliCommand) ?? "";
		normalized.push({
			name,
			...kind ? { kind } : {},
			...cliCommand ? { cliCommand } : {}
		});
	}
	return normalized.length > 0 ? normalized : void 0;
}
//#endregion
//#region src/plugins/manifest-config-groups.ts
/** Invalid presentation metadata leaves the plugin's complete flat settings form available. */
function normalizeConfigGroups(raw, schema) {
	if (!Array.isArray(raw) || raw.length === 0 || !isRecord(schema.properties)) return;
	const ids = /* @__PURE__ */ new Set();
	const assigned = /* @__PURE__ */ new Set();
	const groups = [];
	for (const entry of raw) {
		if (!isRecord(entry)) return;
		const id = typeof entry.id === "string" ? entry.id.trim() : "";
		const title = typeof entry.title === "string" ? entry.title.trim() : "";
		if (!id || !title || ids.has(id) || entry.order !== void 0 && !Number.isInteger(entry.order) || !Array.isArray(entry.properties) || entry.properties.length === 0) return;
		const properties = [];
		for (const property of entry.properties) {
			if (typeof property !== "string" || !property || !Object.hasOwn(schema.properties, property) || assigned.has(property)) return;
			assigned.add(property);
			properties.push(property);
		}
		ids.add(id);
		groups.push({
			id,
			title,
			...typeof entry.order === "number" ? { order: entry.order } : {},
			properties
		});
	}
	return groups;
}
//#endregion
//#region packages/model-catalog-core/src/model-catalog-pricing.ts
const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
const MODELS_DEV_CATALOG_URL = "https://models.opencode.ai/api.json";
const MODEL_PRICING_SOURCES = [
	{
		id: "openCode",
		label: "OpenCode",
		url: MODELS_DEV_CATALOG_URL,
		authoritative: true
	},
	{
		id: "venice",
		label: "Venice",
		url: "https://api.venice.ai/api/v1/models",
		authoritative: true
	},
	{
		id: "chutes",
		label: "Chutes",
		url: "https://llm.chutes.ai/v1/models",
		authoritative: true
	},
	{
		id: "cerebras",
		label: "Cerebras",
		url: "https://api.cerebras.ai/public/v1/models",
		authoritative: true
	},
	{
		id: "deepinfra",
		label: "DeepInfra",
		url: "https://api.deepinfra.com/models/list",
		authoritative: true
	},
	{
		id: "modelsDev",
		label: "models.dev",
		url: MODELS_DEV_CATALOG_URL,
		authoritative: false
	},
	{
		id: "openRouter",
		label: "OpenRouter",
		url: OPENROUTER_MODELS_URL,
		authoritative: false
	},
	{
		id: "liteLLM",
		label: "LiteLLM",
		url: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
		authoritative: false
	}
];
/** Normalize source policy without deciding which plugin owns the provider. */
function normalizeModelPricingProvider(value) {
	const record = asOptionalRecord(value);
	if (!record) return;
	const policy = typeof record.external === "boolean" ? { external: record.external } : {};
	for (const { id: sourceId } of MODEL_PRICING_SOURCES) {
		const raw = record[sourceId];
		if (raw === false) {
			policy[sourceId] = false;
			continue;
		}
		const row = asOptionalRecord(raw);
		const provider = normalizeProviderId(normalizeOptionalString(row?.provider) ?? "");
		const modelIdTransforms = normalizeTrimmedStringList(row?.modelIdTransforms).filter((entry) => entry === "version-dots");
		const source = {
			...provider ? { provider } : {},
			...row?.passthroughProviderModel === true ? { passthroughProviderModel: true } : {},
			...modelIdTransforms.length > 0 ? { modelIdTransforms } : {}
		};
		if (Object.keys(source).length > 0) policy[sourceId] = source;
	}
	return Object.keys(policy).length > 0 ? policy : void 0;
}
//#endregion
//#region src/plugins/plugin-provider-request-policy.ts
/** Manifest parsing and prepared metadata use the same provider request policy. */
function normalizeManifestProviderRequestProvider(value) {
	if (!isRecord(value)) return;
	const family = normalizeOptionalString(value.family);
	const compatibilityFamily = normalizeOptionalString(value.compatibilityFamily) === "moonshot" ? "moonshot" : void 0;
	const supportsStreamingUsage = isRecord(value.openAICompletions) ? value.openAICompletions.supportsStreamingUsage : void 0;
	const openAICompletions = typeof supportsStreamingUsage === "boolean" ? { supportsStreamingUsage } : void 0;
	const providerRequest = {
		...family ? { family } : {},
		...compatibilityFamily ? { compatibilityFamily } : {},
		...openAICompletions ? { openAICompletions } : {}
	};
	return Object.keys(providerRequest).length > 0 ? providerRequest : void 0;
}
//#endregion
//#region src/plugins/manifest-model-provider-normalizers.ts
const MAX_SECRET_PROVIDER_EXEC_ARGS = 128;
const MAX_SECRET_PROVIDER_EXEC_ARG_BYTES = 1024;
const MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS = 12e4;
const MAX_SECRET_PROVIDER_EXEC_OUTPUT_BYTES = 20971520;
const MAX_SECRET_PROVIDER_EXEC_PASS_ENV = 128;
const SECRET_PROVIDER_NODE_COMMAND_PLACEHOLDER = "${node}";
function normalizeManifestModelSupport(value) {
	if (!isRecord(value)) return;
	const modelPrefixes = normalizeTrimmedStringList(value.modelPrefixes);
	const modelPatterns = normalizeTrimmedStringList(value.modelPatterns);
	const modelSupport = {
		...modelPrefixes.length > 0 ? { modelPrefixes } : {},
		...modelPatterns.length > 0 ? { modelPatterns } : {}
	};
	return Object.keys(modelSupport).length > 0 ? modelSupport : void 0;
}
function normalizeOwnedProviderMap(value, ownedProvidersRaw, normalizePolicy) {
	if (!isRecord(value) || !isRecord(value.providers)) return;
	const ownedProviders = new Set([...ownedProvidersRaw].map((provider) => normalizeProviderId(provider)).filter(Boolean));
	const providers = {};
	for (const [rawProviderId, rawPolicy] of Object.entries(value.providers)) {
		const providerId = normalizeProviderId(rawProviderId);
		const policy = providerId && ownedProviders.has(providerId) ? normalizePolicy(rawPolicy) : null;
		if (providerId && policy) providers[providerId] = policy;
	}
	return Object.keys(providers).length > 0 ? providers : void 0;
}
function normalizeManifestModelPricing(value, params) {
	const providers = normalizeOwnedProviderMap(value, params.ownedProviders, normalizeModelPricingProvider);
	return providers ? { providers } : void 0;
}
function normalizeManifestModelIdPrefixRules(value) {
	return normalizeManifestObjectList(value, (rawRule) => {
		const modelPrefix = normalizeOptionalString(rawRule.modelPrefix);
		const prefix = normalizeOptionalString(rawRule.prefix);
		return modelPrefix && prefix ? {
			modelPrefix,
			prefix
		} : void 0;
	});
}
function normalizeManifestModelIdNormalizationProvider(value) {
	if (!isRecord(value)) return;
	const aliases = {};
	if (isRecord(value.aliases)) for (const [rawAlias, rawCanonical] of Object.entries(value.aliases)) {
		const alias = normalizeProviderId(rawAlias);
		const canonical = normalizeOptionalString(rawCanonical);
		if (alias && canonical) aliases[alias] = canonical;
	}
	const stripPrefixes = normalizeTrimmedStringList(value.stripPrefixes);
	const prefixWhenBare = normalizeOptionalString(value.prefixWhenBare);
	const prefixWhenBareAfterAliasStartsWith = normalizeManifestModelIdPrefixRules(value.prefixWhenBareAfterAliasStartsWith);
	const normalization = {
		...Object.keys(aliases).length > 0 ? { aliases } : {},
		...stripPrefixes.length > 0 ? { stripPrefixes } : {},
		...prefixWhenBare ? { prefixWhenBare } : {},
		...prefixWhenBareAfterAliasStartsWith ? { prefixWhenBareAfterAliasStartsWith } : {}
	};
	return Object.keys(normalization).length > 0 ? normalization : void 0;
}
function normalizeManifestModelIdNormalization(value, params) {
	const providers = normalizeOwnedProviderMap(value, params.ownedProviders, normalizeManifestModelIdNormalizationProvider);
	return providers ? { providers } : void 0;
}
function normalizeManifestProviderEndpoints(value) {
	return normalizeManifestObjectList(value, (rawEndpoint) => {
		const endpointClass = normalizeOptionalString(rawEndpoint.endpointClass);
		if (!endpointClass) return;
		const hosts = normalizeTrimmedStringList(rawEndpoint.hosts).map((host) => host.toLowerCase());
		const hostSuffixes = normalizeTrimmedStringList(rawEndpoint.hostSuffixes).map((host) => host.toLowerCase());
		const baseUrls = normalizeTrimmedStringList(rawEndpoint.baseUrls);
		const googleVertexRegion = normalizeOptionalString(rawEndpoint.googleVertexRegion);
		const googleVertexRegionHostSuffix = normalizeOptionalString(rawEndpoint.googleVertexRegionHostSuffix)?.toLowerCase();
		if (hosts.length === 0 && hostSuffixes.length === 0 && baseUrls.length === 0) return;
		return {
			endpointClass,
			...hosts.length > 0 ? { hosts } : {},
			...hostSuffixes.length > 0 ? { hostSuffixes } : {},
			...baseUrls.length > 0 ? { baseUrls } : {},
			...googleVertexRegion ? { googleVertexRegion } : {},
			...googleVertexRegionHostSuffix ? { googleVertexRegionHostSuffix } : {}
		};
	});
}
function normalizeManifestProviderRequest(value, params) {
	const providers = normalizeOwnedProviderMap(value, params.ownedProviders, normalizeManifestProviderRequestProvider);
	return providers ? { providers } : void 0;
}
function normalizeManifestPositiveInteger(value, max) {
	return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= max ? value : void 0;
}
function normalizeManifestSecretProviderIntegrations(value) {
	return normalizeNamedMetadataRecord(value, (rawIntegration) => {
		const command = normalizeOptionalString(rawIntegration.command);
		if (rawIntegration.source !== "exec" || command !== SECRET_PROVIDER_NODE_COMMAND_PLACEHOLDER) return;
		const providerAlias = normalizeOptionalString(rawIntegration.providerAlias);
		const displayName = normalizeOptionalString(rawIntegration.displayName);
		const description = normalizeOptionalString(rawIntegration.description);
		const args = [];
		if (Array.isArray(rawIntegration.args)) for (const entry of rawIntegration.args) {
			if (typeof entry !== "string" || entry.length > MAX_SECRET_PROVIDER_EXEC_ARG_BYTES) continue;
			args.push(entry);
			if (args.length >= MAX_SECRET_PROVIDER_EXEC_ARGS) break;
		}
		const timeoutMs = normalizeManifestPositiveInteger(rawIntegration.timeoutMs, MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS);
		const noOutputTimeoutMs = normalizeManifestPositiveInteger(rawIntegration.noOutputTimeoutMs, MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS);
		const maxOutputBytes = normalizeManifestPositiveInteger(rawIntegration.maxOutputBytes, MAX_SECRET_PROVIDER_EXEC_OUTPUT_BYTES);
		const env = normalizeManifestStringRecord(rawIntegration.env);
		const passEnv = normalizeTrimmedStringList(rawIntegration.passEnv).filter((entry) => ENV_SECRET_REF_ID_RE$1.test(entry)).slice(0, MAX_SECRET_PROVIDER_EXEC_PASS_ENV);
		return {
			...providerAlias ? { providerAlias } : {},
			...displayName ? { displayName } : {},
			...description ? { description } : {},
			source: "exec",
			command,
			...args.length > 0 ? { args } : {},
			...timeoutMs !== void 0 ? { timeoutMs } : {},
			...noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs } : {},
			...maxOutputBytes !== void 0 ? { maxOutputBytes } : {},
			...typeof rawIntegration.jsonOnly === "boolean" ? { jsonOnly: rawIntegration.jsonOnly } : {},
			...env ? { env } : {},
			...passEnv.length > 0 ? { passEnv } : {}
		};
	});
}
//#endregion
//#region src/plugins/manifest-platforms.ts
const MANIFEST_PLATFORMS = /* @__PURE__ */ new Set([
	"aix",
	"android",
	"darwin",
	"freebsd",
	"haiku",
	"linux",
	"openbsd",
	"sunos",
	"win32",
	"cygwin",
	"netbsd"
]);
function normalizeManifestPlatforms(value) {
	return normalizeTrimmedStringList(value).filter((platform) => MANIFEST_PLATFORMS.has(platform));
}
//#endregion
//#region src/cli/program/command-descriptor-utils.ts
const SAFE_COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
/** Normalize and validate a command descriptor name for safe Commander registration. */
function normalizeCommandDescriptorName(name) {
	const normalized = name.trim();
	return SAFE_COMMAND_NAME_PATTERN.test(normalized) ? normalized : null;
}
/** Strip unsafe terminal content from descriptor descriptions. */
function sanitizeCommandDescriptorDescription(description) {
	return sanitizeForLog(description).trim();
}
//#endregion
//#region src/plugins/setup-presentation-url.ts
function normalizeSetupPresentationHttpsUrl(value) {
	const normalized = normalizeOptionalString(value);
	if (!normalized) return;
	try {
		const url = new URL(normalized);
		const canonical = url.toString();
		return url.protocol === "https:" && url.hostname && !url.username && !url.password && canonical.length <= 2048 ? canonical : void 0;
	} catch {
		return;
	}
}
//#endregion
//#region src/plugins/manifest-setup-normalizers.ts
function normalizeManifestActivation(value) {
	if (!isRecord(value)) return;
	const onProviders = normalizeTrimmedStringList(value.onProviders);
	const onAgentHarnesses = normalizeTrimmedStringList(value.onAgentHarnesses);
	const onCommands = normalizeTrimmedStringList(value.onCommands);
	const onChannels = normalizeTrimmedStringList(value.onChannels);
	const onRoutes = normalizeTrimmedStringList(value.onRoutes);
	const onConfigPaths = normalizeTrimmedStringList(value.onConfigPaths);
	const onStartup = typeof value.onStartup === "boolean" ? value.onStartup : void 0;
	const onCapabilities = normalizeTrimmedStringList(value.onCapabilities).filter((capability) => capability === "provider" || capability === "channel" || capability === "tool" || capability === "hook");
	const activation = {
		...onStartup !== void 0 ? { onStartup } : {},
		...onProviders.length > 0 ? { onProviders } : {},
		...onAgentHarnesses.length > 0 ? { onAgentHarnesses } : {},
		...onCommands.length > 0 ? { onCommands } : {},
		...onChannels.length > 0 ? { onChannels } : {},
		...onRoutes.length > 0 ? { onRoutes } : {},
		...onConfigPaths.length > 0 ? { onConfigPaths } : {},
		...onCapabilities.length > 0 ? { onCapabilities } : {}
	};
	return Object.keys(activation).length > 0 ? activation : void 0;
}
function normalizeChannelAccountKeyPolicies(value, channels) {
	if (!isRecord(value)) return;
	const policies = Object.create(null);
	for (const channel of channels) {
		if (isBlockedObjectKey$1(channel) || !Object.hasOwn(value, channel)) continue;
		const entry = value[channel];
		const field = isRecord(entry) ? normalizeOptionalString(entry.canonicalAliasesRequireOwnField) : void 0;
		if (field && !isBlockedObjectKey$1(field)) policies[channel] = { canonicalAliasesRequireOwnField: field };
	}
	return Object.keys(policies).length ? policies : void 0;
}
function normalizeManifestCliCommands(value) {
	if (!Array.isArray(value)) return;
	const seen = /* @__PURE__ */ new Set();
	return normalizeManifestObjectList(value, (entry) => {
		if (typeof entry.name !== "string" || typeof entry.description !== "string") return;
		const name = normalizeCommandDescriptorName(entry.name);
		const description = sanitizeCommandDescriptorDescription(entry.description);
		if (!name || !description || typeof entry.hasSubcommands !== "boolean" || seen.has(name)) return;
		seen.add(name);
		return {
			name,
			description,
			hasSubcommands: entry.hasSubcommands
		};
	}) ?? [];
}
function normalizeManifestSetupProviders(value) {
	return normalizeManifestObjectList(value, (entry) => {
		const id = normalizeOptionalString(entry.id) ?? "";
		if (!id) return;
		const authMethods = normalizeTrimmedStringList(entry.authMethods);
		const envVars = normalizeTrimmedStringList(entry.envVars);
		const authEvidence = normalizeManifestSetupProviderAuthEvidence(entry.authEvidence);
		return {
			id,
			...authMethods.length > 0 ? { authMethods } : {},
			...envVars.length > 0 ? { envVars } : {},
			...authEvidence ? { authEvidence } : {}
		};
	});
}
function normalizeManifestSetupProviderAuthEvidence(value) {
	return normalizeManifestObjectList(value, (entry) => {
		if (entry.type !== "local-file-with-env") return;
		const credentialMarker = normalizeOptionalString(entry.credentialMarker);
		if (!credentialMarker) return;
		const fileEnvVar = normalizeOptionalString(entry.fileEnvVar);
		const fallbackPaths = normalizeTrimmedStringList(entry.fallbackPaths);
		if (!fileEnvVar && fallbackPaths.length === 0) return;
		const requiresAnyEnv = normalizeTrimmedStringList(entry.requiresAnyEnv);
		const requiresAllEnv = normalizeTrimmedStringList(entry.requiresAllEnv);
		const source = normalizeOptionalString(entry.source);
		return {
			type: "local-file-with-env",
			...fileEnvVar ? { fileEnvVar } : {},
			...fallbackPaths.length > 0 ? { fallbackPaths } : {},
			...requiresAnyEnv.length > 0 ? { requiresAnyEnv } : {},
			...requiresAllEnv.length > 0 ? { requiresAllEnv } : {},
			credentialMarker,
			...source ? { source } : {}
		};
	});
}
function normalizeManifestSetup(value) {
	if (!isRecord(value)) return;
	const providers = normalizeManifestSetupProviders(value.providers);
	const cliBackends = normalizeTrimmedStringList(value.cliBackends);
	const configMigrations = normalizeTrimmedStringList(value.configMigrations);
	const nativeSessionCatalog = isRecord(value.nativeSessionCatalog) ? {
		label: normalizeOptionalString(value.nativeSessionCatalog.label) ?? "",
		description: normalizeOptionalString(value.nativeSessionCatalog.description),
		nodeCommands: normalizeTrimmedStringList(value.nativeSessionCatalog.nodeCommands),
		legacyDefaultEnabled: value.nativeSessionCatalog.legacyDefaultEnabled === true
	} : void 0;
	const requiresRuntime = typeof value.requiresRuntime === "boolean" ? value.requiresRuntime : void 0;
	const setup = {
		...providers ? { providers } : {},
		...cliBackends.length > 0 ? { cliBackends } : {},
		...configMigrations.length > 0 ? { configMigrations } : {},
		...nativeSessionCatalog?.label ? { nativeSessionCatalog: {
			label: nativeSessionCatalog.label,
			...nativeSessionCatalog.legacyDefaultEnabled ? { legacyDefaultEnabled: true } : {},
			...nativeSessionCatalog.nodeCommands.length > 0 ? { nodeCommands: nativeSessionCatalog.nodeCommands } : {},
			...nativeSessionCatalog.description ? { description: nativeSessionCatalog.description } : {}
		} } : {},
		...requiresRuntime !== void 0 ? { requiresRuntime } : {}
	};
	return Object.keys(setup).length > 0 ? setup : void 0;
}
function normalizeManifestQaRunners(value) {
	return normalizeManifestObjectList(value, (entry) => {
		const commandName = normalizeOptionalString(entry.commandName) ?? "";
		if (!commandName) return;
		const description = normalizeOptionalString(entry.description) ?? "";
		return {
			commandName,
			...description ? { description } : {}
		};
	});
}
function normalizeDashboardCapabilityBase(value, field, index) {
	if (!isRecord(value)) return `${field}[${index}] must be an object`;
	const id = normalizeOptionalString(value.id);
	const method = normalizeOptionalString(value.method);
	const description = normalizeOptionalString(value.description);
	if (!id || !/^[a-z0-9][a-z0-9._-]*$/u.test(id)) return `${field}[${index}].id must be a lowercase capability id`;
	if (!method) return `${field}[${index}].method must be a non-empty string`;
	if (!description) return `${field}[${index}].description must be a non-empty string`;
	return {
		id,
		method,
		description
	};
}
function normalizeManifestDashboard(value) {
	if (value === void 0) return { ok: true };
	if (!isRecord(value)) return {
		ok: false,
		error: "dashboard must be an object"
	};
	if (value.dataBindings !== void 0 && !Array.isArray(value.dataBindings)) return {
		ok: false,
		error: "dashboard.dataBindings must be an array"
	};
	if (value.actionVerbs !== void 0 && !Array.isArray(value.actionVerbs)) return {
		ok: false,
		error: "dashboard.actionVerbs must be an array"
	};
	const dataBindings = [];
	for (const [index, entry] of (value.dataBindings ?? []).entries()) {
		const normalized = normalizeDashboardCapabilityBase(entry, "dashboard.dataBindings", index);
		if (typeof normalized === "string") return {
			ok: false,
			error: normalized
		};
		dataBindings.push(normalized);
	}
	const actionVerbs = [];
	for (const [index, entry] of (value.actionVerbs ?? []).entries()) {
		const normalized = normalizeDashboardCapabilityBase(entry, "dashboard.actionVerbs", index);
		if (typeof normalized === "string") return {
			ok: false,
			error: normalized
		};
		const rawParamShape = isRecord(entry) ? entry.paramShape : void 0;
		if (rawParamShape !== void 0 && !isRecord(rawParamShape)) return {
			ok: false,
			error: `dashboard.actionVerbs[${index}].paramShape must be a JSON Schema object`
		};
		actionVerbs.push({
			...normalized,
			...rawParamShape ? { paramShape: rawParamShape } : {}
		});
	}
	if (dataBindings.length === 0 && actionVerbs.length === 0) return { ok: true };
	return {
		ok: true,
		dashboard: {
			...dataBindings.length > 0 ? { dataBindings } : {},
			...actionVerbs.length > 0 ? { actionVerbs } : {}
		}
	};
}
function normalizeManifestControlUi(value) {
	if (value === void 0) return ok(void 0);
	if (!isRecord(value) || Object.keys(value).some((key) => key !== "entry" && key !== "styles")) return err("controlUi must contain only entry and optional styles");
	const entry = typeof value.entry === "string" ? value.entry.replace(/^\.\//u, "") : "";
	if (entry.length > 512 || !/^dist\/(?:[\w-][\w.-]*\/)+[\w-][\w.-]*\.m?js$/u.test(entry)) return err("controlUi.entry must be a JavaScript file in a dedicated dist subdirectory");
	if (value.styles !== void 0 && (!Array.isArray(value.styles) || value.styles.length > 16)) return err("controlUi.styles must be an array of at most 16 stylesheets");
	const assetPrefix = entry.slice(0, entry.lastIndexOf("/") + 1);
	const styles = [];
	for (const rawStyle of value.styles ?? []) {
		const style = typeof rawStyle === "string" ? rawStyle.replace(/^\.\//u, "") : "";
		if (style.length > 512 || !style.startsWith(assetPrefix) || !/^(?:[\w-][\w.-]*\/)+[\w-][\w.-]*\.css$/u.test(style)) return err("controlUi.styles must contain CSS files under the entry's asset directory");
		if (!styles.includes(style)) styles.push(style);
	}
	return ok({
		entry,
		...styles.length > 0 ? { styles } : {}
	});
}
function normalizeProviderChannelLogin(value) {
	if (!isRecord(value) || Object.keys(value).some((key) => key !== "aliases")) return;
	if (value.aliases !== void 0 && (!Array.isArray(value.aliases) || value.aliases.some((alias) => typeof alias !== "string" || !alias.trim()))) return;
	const aliases = normalizeUniqueTrimmedStringList(value.aliases);
	return aliases.length > 0 ? { aliases } : {};
}
function normalizeProviderAuthChoices(value) {
	return normalizeManifestObjectList(value, (entry) => {
		const provider = normalizeOptionalString(entry.provider) ?? "";
		const method = normalizeOptionalString(entry.method) ?? "";
		const choiceId = normalizeOptionalString(entry.choiceId) ?? "";
		if (!provider || !method || !choiceId) return;
		const choiceLabel = normalizeOptionalString(entry.choiceLabel) ?? "";
		const choiceHint = normalizeOptionalString(entry.choiceHint) ?? "";
		const icon = normalizeSetupPresentationHttpsUrl(entry.icon);
		const website = normalizeSetupPresentationHttpsUrl(entry.website);
		const docsUrl = normalizeSetupPresentationHttpsUrl(entry.docsUrl);
		const assistantPriority = typeof entry.assistantPriority === "number" && Number.isFinite(entry.assistantPriority) ? entry.assistantPriority : void 0;
		const assistantVisibility = entry.assistantVisibility === "manual-only" || entry.assistantVisibility === "visible" || entry.assistantVisibility === "detected-only" ? entry.assistantVisibility : void 0;
		const deprecatedChoiceIds = normalizeTrimmedStringList(entry.deprecatedChoiceIds);
		const groupId = normalizeOptionalString(entry.groupId) ?? "";
		const groupLabel = normalizeOptionalString(entry.groupLabel) ?? "";
		const groupHint = normalizeOptionalString(entry.groupHint) ?? "";
		const optionKey = normalizeOptionalString(entry.optionKey) ?? "";
		const cliFlag = normalizeOptionalString(entry.cliFlag) ?? "";
		const cliOption = normalizeOptionalString(entry.cliOption) ?? "";
		const cliDescription = normalizeOptionalString(entry.cliDescription) ?? "";
		const appGuidedActionLabel = normalizeOptionalString(entry.appGuidedActionLabel) ?? "";
		const appGuidedAuth = entry.appGuidedAuth === "oauth" || entry.appGuidedAuth === "device-code" ? entry.appGuidedAuth : void 0;
		const onboardingScopes = normalizeTrimmedStringList(entry.onboardingScopes).filter((scope) => scope === "text-inference" || scope === "image-generation" || scope === "music-generation");
		const channelLogin = normalizeProviderChannelLogin(entry.channelLogin);
		return {
			provider,
			method,
			choiceId,
			...entry.modelTarget === "utility" ? { modelTarget: "utility" } : {},
			...entry.platforms !== void 0 ? { platforms: normalizeManifestPlatforms(entry.platforms) } : {},
			...choiceLabel ? { choiceLabel } : {},
			...choiceHint ? { choiceHint } : {},
			...icon ? { icon } : {},
			...website ? { website } : {},
			...docsUrl ? { docsUrl } : {},
			...assistantPriority !== void 0 ? { assistantPriority } : {},
			...assistantVisibility ? { assistantVisibility } : {},
			...deprecatedChoiceIds.length > 0 ? { deprecatedChoiceIds } : {},
			...groupId ? { groupId } : {},
			...groupLabel ? { groupLabel } : {},
			...groupHint ? { groupHint } : {},
			...entry.onboardingFeatured === true ? { onboardingFeatured: true } : {},
			...entry.appGuidedDiscovery === true ? { appGuidedDiscovery: true } : {},
			...optionKey ? { optionKey } : {},
			...cliFlag ? { cliFlag } : {},
			...cliOption ? { cliOption } : {},
			...cliDescription ? { cliDescription } : {},
			...entry.appGuidedSecret === true ? { appGuidedSecret: true } : {},
			...entry.personalAccount === true ? { personalAccount: true } : {},
			...appGuidedActionLabel ? { appGuidedActionLabel } : {},
			...appGuidedAuth ? { appGuidedAuth } : {},
			...entry.credentialOnly === true ? { credentialOnly: true } : {},
			...channelLogin ? { channelLogin } : {},
			...onboardingScopes.length > 0 ? { onboardingScopes } : {}
		};
	});
}
function normalizeConfigUiHints(value) {
	if (!isRecord(value)) return;
	const normalized = Object.create(null);
	for (const [hintPath, rawHint] of Object.entries(value)) {
		if (!isRecord(rawHint)) continue;
		const hint = { ...rawHint };
		if ("presentation" in hint && hint.presentation !== "phone-number") delete hint.presentation;
		normalized[hintPath] = hint;
	}
	return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeChannelConfigs(value) {
	return normalizeNamedMetadataRecord(value, (rawEntry) => {
		const schema = isRecord(rawEntry.schema) ? rawEntry.schema : null;
		if (!schema) return;
		const uiHints = normalizeConfigUiHints(rawEntry.uiHints);
		const runtime = isRecord(rawEntry.runtime) && typeof rawEntry.runtime.safeParse === "function" ? rawEntry.runtime : void 0;
		const label = normalizeOptionalString(rawEntry.label) ?? "";
		const description = normalizeOptionalString(rawEntry.description) ?? "";
		const preferOver = normalizeTrimmedStringList(rawEntry.preferOver);
		const commandDefaults = normalizeManifestChannelCommandDefaults(rawEntry.commands);
		return {
			schema,
			...uiHints ? { uiHints } : {},
			...runtime ? { runtime } : {},
			...label ? { label } : {},
			...description ? { description } : {},
			...preferOver.length > 0 ? { preferOver } : {},
			...commandDefaults ? { commands: commandDefaults } : {}
		};
	});
}
function normalizeManifestChannelCommandDefaults(value) {
	if (!isRecord(value)) return;
	const nativeCommandsAutoEnabled = typeof value.nativeCommandsAutoEnabled === "boolean" ? value.nativeCommandsAutoEnabled : void 0;
	const nativeSkillsAutoEnabled = typeof value.nativeSkillsAutoEnabled === "boolean" ? value.nativeSkillsAutoEnabled : void 0;
	return nativeCommandsAutoEnabled !== void 0 || nativeSkillsAutoEnabled !== void 0 ? {
		...nativeCommandsAutoEnabled !== void 0 ? { nativeCommandsAutoEnabled } : {},
		...nativeSkillsAutoEnabled !== void 0 ? { nativeSkillsAutoEnabled } : {}
	} : void 0;
}
//#endregion
//#region packages/gateway-protocol/src/theme-ids.ts
/** Lightweight theme identifiers used while browser preferences boot. */
const BUILTIN_THEME_IDS = [
	"claw",
	"knot",
	"dash",
	"absolutely",
	"tide",
	"beacon",
	"phosphor",
	"crt",
	"manuscript",
	"rose",
	"miami"
];
const THEME_LOCAL_ID_PATTERN = new RegExp(`^[a-z0-9][a-z0-9_-]{0,63}$`);
function isBuiltinThemeId(value) {
	return BUILTIN_THEME_IDS.some((id) => id === value);
}
function isThemeId(value) {
	if (isBuiltinThemeId(value)) return true;
	if (typeof value !== "string" || value.length > 256) return false;
	const separator = value.lastIndexOf("/");
	const owner = value.slice(0, separator);
	return separator > 0 && !owner.startsWith("user/") && /^@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)*$/i.test(owner) && THEME_LOCAL_ID_PATTERN.test(value.slice(separator + 1));
}
//#endregion
//#region packages/gateway-protocol/src/theme.ts
const THEME_COLOR_KEYS = [
	"background",
	"foreground",
	"card",
	"card-foreground",
	"popover",
	"popover-foreground",
	"primary",
	"primary-foreground",
	"secondary",
	"secondary-foreground",
	"muted",
	"muted-foreground",
	"accent",
	"accent-foreground",
	"destructive",
	"destructive-foreground",
	"border",
	"input",
	"ring"
];
const THEME_FONT_KEYS = ["font-sans", "font-mono"];
const THEME_MASCOT_VALUES = ["claw", "none"];
const THEME_CRITTER_IDS = ["penguin", "fedora"];
const THEME_AVATAR_HAT_IDS = [
	"fedora",
	"crown",
	"santa",
	"party",
	"pumpkin"
];
const THEME_ARTWORK_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/;
const MAX_THEME_DEFINITION_BYTES = 4096;
[
	{
		id: "claw",
		name: "Claw",
		description: "Signature coral red and teal on charcoal or pale surfaces, with Instrument Sans throughout. A balanced everyday workspace."
	},
	{
		id: "knot",
		name: "Knot",
		description: "Crimson accents on true black or clean white, with Geist typography. Sharp, minimal, and high contrast."
	},
	{
		id: "dash",
		name: "Dash",
		description: "Toasted caramel on deep cocoa or warm cream, with DM Sans controls and Fraunces serif chat. Warm and bookish."
	},
	{
		id: "absolutely",
		name: "Absolutely",
		description: "Terracotta clay on warm graphite or soft cream, with Space Grotesk controls and Lora serif chat. Quiet editorial character."
	},
	{
		id: "tide",
		name: "Tide",
		description: "Steel cyan on cool slate or pale blue-gray, with IBM Plex Sans throughout. Calm, technical, and understated."
	},
	{
		id: "beacon",
		name: "Beacon",
		description: "High-visibility amber on black or white, bold focus rings, and Atkinson Hyperlegible typography. Designed for maximum readability."
	},
	{
		id: "phosphor",
		name: "Phosphor",
		description: "Luminous green on green-tinted black or pale green, with JetBrains Mono throughout. A classic green terminal atmosphere."
	},
	{
		id: "crt",
		name: "CRT",
		description: "White phosphor and amber on tube black, with a light counterpart, JetBrains Mono, and nearly square corners. A retro computer console."
	},
	{
		id: "manuscript",
		name: "Manuscript",
		description: "Lapis blue and gold on aged paper, with a dark reading-room variant and Lora serif throughout. A quiet illuminated manuscript."
	},
	{
		id: "rose",
		name: "Rosé",
		description: "Dried rose and gold on deep plum-gray or soft rose-tinted cream, with DM Sans. Gentle, muted, and warm."
	},
	{
		id: "miami",
		name: "Miami",
		description: "Hot magenta and cyan on violet-black or pale lavender, with Space Grotesk. Bright neon energy and a synthwave character."
	}
].map((theme) => ({
	id: theme.id,
	name: theme.name,
	description: theme.description,
	source: "builtin",
	modes: ["light", "dark"]
}));
function requireRecord(value, label) {
	if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
	return value;
}
function requireKeys(value, allowed, label) {
	const unknown = Object.keys(value).find((key) => !allowed.includes(key));
	if (unknown) throw new Error(`${label} has unsupported field ${unknown}`);
}
function requireText(value, label, maxLength) {
	if (typeof value !== "string" || !value.trim() || value.trim().length > maxLength || Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) throw new Error(`${label} must be nonempty text of at most ${maxLength} characters`);
	return value.trim();
}
const NUMBER = "[+-]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:e[+-]?\\d+)?";
const COMPONENT = `${NUMBER}%?`;
const HUE = `${NUMBER}(?:deg|grad|rad|turn)?`;
const LEGACY_COLOR_FUNCTION = new RegExp(`^(?:(?:rgb|rgba)\\( *(?:${NUMBER} *, *${NUMBER} *, *${NUMBER}|${NUMBER}% *, *${NUMBER}% *, *${NUMBER}%)|(?:hsl|hsla)\\( *${HUE} *, *${NUMBER}% *, *${NUMBER}%)(?: *, *${COMPONENT})? *\\)$`, "i");
const COLOR_FUNCTION = new RegExp(`^(?:(?:rgb|rgba|oklab|lab)\\( *${COMPONENT} +${COMPONENT} +${COMPONENT}|(?:hsl|hsla)\\( *${HUE} +${COMPONENT} +${COMPONENT}|(?:oklch|lch)\\( *${COMPONENT} +${COMPONENT} +${HUE})(?: */ *${COMPONENT})? *\\)$`, "i");
const COLOR_SPACE_FUNCTION = new RegExp(`^color\\( *(?:srgb|srgb-linear|display-p3|a98-rgb|prophoto-rgb|rec2020|xyz|xyz-d50|xyz-d65) +${COMPONENT} +${COMPONENT} +${COMPONENT}(?: */ *${COMPONENT})? *\\)$`, "i");
const FONT_IDENTIFIER = "(?:--[a-z0-9_-]*|-?[a-z_][a-z0-9_-]*)";
const FONT_FAMILY = `(?:"[a-z0-9 ,'._-]*"|'[a-z0-9 ,"._-]*'|${FONT_IDENTIFIER}(?: +${FONT_IDENTIFIER})*)`;
const FONT_FAMILY_LIST = new RegExp(`^${FONT_FAMILY}(?: *, *${FONT_FAMILY})*$`, "i");
const CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
	"inherit",
	"initial",
	"unset",
	"revert",
	"revert-layer"
]);
const GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
	"serif",
	"sans-serif",
	"monospace",
	"cursive",
	"fantasy",
	"system-ui",
	"ui-serif",
	"ui-sans-serif",
	"ui-monospace",
	"ui-rounded",
	"emoji",
	"math",
	"fangsong",
	"-webkit-body"
]);
function isFontFamilyList(value) {
	if (!FONT_FAMILY_LIST.test(value)) return false;
	return value.replace(/"[^"]*"|'[^']*'/g, "").split(",").every((family) => {
		const words = family.trim().toLowerCase().split(/ +/);
		return words.every((word) => !CSS_WIDE_KEYWORDS.has(word) && word !== "default" && (words.length === 1 || !GENERIC_FONT_FAMILIES.has(word)));
	});
}
function requireColor(value, label) {
	const color = requireText(value, label, 120);
	if (!/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(color) && !/^(?:transparent|black|white)$/i.test(color) && !LEGACY_COLOR_FUNCTION.test(color) && !COLOR_FUNCTION.test(color) && !COLOR_SPACE_FUNCTION.test(color)) throw new Error(`${label} must be a hex, rgb, hsl, lab, lch, oklab, oklch, or color() color`);
	return color;
}
function normalizePalette(value, mode) {
	const palette = requireRecord(value, `theme.${mode}`);
	requireKeys(palette, [...THEME_COLOR_KEYS, ...THEME_FONT_KEYS], `theme.${mode}`);
	const entries = THEME_COLOR_KEYS.map((key) => [key, requireColor(palette[key], `theme.${mode}.${key}`)]);
	const result = { ...Object.fromEntries(entries) };
	for (const key of THEME_FONT_KEYS) {
		if (palette[key] === void 0) continue;
		const font = requireText(palette[key], `theme.${mode}.${key}`, 120);
		if (!isFontFamilyList(font)) throw new Error(`theme.${mode}.${key} must contain only font family names`);
		result[key] = font;
	}
	return result;
}
/** Rejects executable CSS and incomplete palettes before they reach storage or a stylesheet. */
function normalizeThemeDefinition(input, options) {
	const record = requireRecord(input, "theme");
	requireKeys(record, [
		"name",
		"description",
		"mascot",
		"workingPhrases",
		"critters",
		"avatarHat",
		"light",
		"dark"
	], "theme");
	const definition = {
		name: requireText(record.name, "theme.name", 80),
		description: requireText(record.description, "theme.description", 320),
		...record.light !== void 0 ? { light: normalizePalette(record.light, "light") } : {},
		...record.dark !== void 0 ? { dark: normalizePalette(record.dark, "dark") } : {}
	};
	if (record.mascot !== void 0) {
		const mascot = THEME_MASCOT_VALUES.find((candidate) => candidate === record.mascot);
		if (!mascot) throw new Error(`theme.mascot must be one of ${THEME_MASCOT_VALUES.join(", ")}`);
		definition.mascot = mascot;
	}
	if (record.workingPhrases !== void 0) {
		if (!Array.isArray(record.workingPhrases) || record.workingPhrases.length > 24) throw new Error(`theme.workingPhrases must be an array of at most 24 entries`);
		const phrases = Array.from(record.workingPhrases, (phrase, index) => requireText(phrase, `theme.workingPhrases[${index}]`, 24));
		if (new Set(phrases).size !== phrases.length) throw new Error("theme.workingPhrases must not contain duplicate entries after trimming");
		definition.workingPhrases = phrases;
	}
	if (record.critters !== void 0) {
		if (!Array.isArray(record.critters) || record.critters.length > 8) throw new Error("theme.critters must be an array of at most 8 entries");
		const allowedIds = [...THEME_CRITTER_IDS, ...options?.critterIds ?? []];
		const critters = Array.from(record.critters, (entry, index) => {
			const critter = allowedIds.find((id) => id === entry);
			if (!critter) throw new Error(`theme.critters[${index}] must be one of ${allowedIds.join(", ")}`);
			return critter;
		});
		if (new Set(critters).size !== critters.length) throw new Error("theme.critters must not contain duplicate entries");
		definition.critters = critters;
	}
	if (record.avatarHat !== void 0) {
		const allowedIds = [...THEME_AVATAR_HAT_IDS, ...options?.hatIds ?? []];
		const avatarHat = allowedIds.find((id) => id === record.avatarHat);
		if (!avatarHat) throw new Error(`theme.avatarHat must be one of ${allowedIds.join(", ")}`);
		definition.avatarHat = avatarHat;
	}
	if (!definition.light && !definition.dark) throw new Error("theme must provide at least one light or dark palette");
	if (new TextEncoder().encode(JSON.stringify(definition)).length > 4096) throw new Error(`theme definition exceeds ${MAX_THEME_DEFINITION_BYTES} bytes`);
	return definition;
}
//#endregion
//#region src/plugins/manifest-themes.ts
const MAX_PLUGIN_THEMES = 32;
const MAX_THEME_ARTWORK_ENTRIES = 8;
function objectProperties(value) {
	return value.type === "ObjectExpression" ? value.properties.filter((property) => property.type === "Property") : [];
}
function propertyName(property) {
	return property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : "";
}
function validateArtworkDuplicates(source) {
	const { parseExpressionAt } = createRequire(import.meta.url)("acorn");
	const root = parseExpressionAt(`(${source}\n)`, 0, { ecmaVersion: "latest" });
	for (const property of objectProperties(root)) {
		if (propertyName(property) !== "themes" || property.value.type !== "ArrayExpression") continue;
		for (const theme of property.value.elements) {
			if (!theme || theme.type !== "ObjectExpression") continue;
			for (const artwork of objectProperties(theme)) {
				const kind = propertyName(artwork);
				if (kind !== "hats" && kind !== "critters") continue;
				const ids = /* @__PURE__ */ new Set();
				for (const entry of objectProperties(artwork.value)) {
					const id = propertyName(entry);
					if (ids.has(id)) throw new Error(`themes.${kind} has duplicate artwork ID ${id}`);
					ids.add(id);
				}
			}
		}
	}
}
function normalizeArtworkEntries(value, label, catalogIds) {
	if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be a map with at most ${MAX_THEME_ARTWORK_ENTRIES} entries`);
	const entries = Object.entries(value);
	if (entries.length > MAX_THEME_ARTWORK_ENTRIES) throw new Error(`${label} must be a map with at most ${MAX_THEME_ARTWORK_ENTRIES} entries`);
	for (const [id] of entries) if (!THEME_ARTWORK_ID_PATTERN.test(id) || catalogIds.includes(id)) throw new Error(`${label}.${id} must use a safe local ID outside the built-in catalog`);
	return entries;
}
function normalizeArtworkPath(value, label) {
	const relativePath = typeof value === "string" ? value.replace(/^\.\//, "") : "";
	if (!/^(?:[a-z0-9_-][a-z0-9._-]*\/)*[a-z0-9_-][a-z0-9._-]*\.svg$/i.test(relativePath)) throw new Error(`${label} must be an SVG file inside the plugin root`);
	return relativePath;
}
function normalizeArtwork(hats, critters, label) {
	return {
		...hats !== void 0 ? { hats: Object.fromEntries(normalizeArtworkEntries(hats, `${label}.hats`, THEME_AVATAR_HAT_IDS).map(([id, source]) => [id, normalizeArtworkPath(source, `${label}.hats.${id}`)])) } : {},
		...critters !== void 0 ? { critters: Object.fromEntries(normalizeArtworkEntries(critters, `${label}.critters`, THEME_CRITTER_IDS).map(([id, metadata]) => {
			const entryLabel = `${label}.critters.${id}`;
			if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) throw new Error(`${entryLabel} must be an object with an SVG source`);
			const { source, title, crossMs } = metadata;
			if (Object.keys(metadata).some((key) => ![
				"source",
				"title",
				"crossMs"
			].includes(key))) throw new Error(`${entryLabel} has unsupported fields`);
			if (title !== void 0 && (typeof title !== "string" || title.length > 60 || /[\p{Cc}\p{Cf}\p{Cs}]/u.test(title))) throw new Error(`${entryLabel}.title must be at most 60 printable characters`);
			if (crossMs !== void 0 && (typeof crossMs !== "number" || !Number.isInteger(crossMs) || crossMs < 5e3 || crossMs > 9e4)) throw new Error(`${entryLabel}.crossMs must be an integer from 5000 to 90000`);
			return [id, {
				source: normalizeArtworkPath(source, `${entryLabel}.source`),
				...title !== void 0 ? { title } : {},
				...crossMs !== void 0 ? { crossMs } : {}
			}];
		})) } : {}
	};
}
function normalizeManifestThemes(value, pluginId, manifestSource) {
	if (value === void 0) return { ok: true };
	if (!Array.isArray(value) || value.length > MAX_PLUGIN_THEMES) return {
		ok: false,
		error: `themes must be an array with at most ${MAX_PLUGIN_THEMES} entries`
	};
	if (value.length && (pluginId === "user" || !isThemeId(`${pluginId}/x`))) return {
		ok: false,
		error: "themes require a portable plugin ID outside the reserved user namespace"
	};
	const themes = [];
	const ids = /* @__PURE__ */ new Set();
	for (const [index, entry] of value.entries()) {
		if (!entry || typeof entry !== "object" || Array.isArray(entry)) return {
			ok: false,
			error: `themes[${index}] must be an object`
		};
		const { id, name, description, source, hats, critters } = entry;
		if (Object.keys(entry).some((key) => ![
			"id",
			"name",
			"description",
			"source",
			"hats",
			"critters"
		].includes(key)) || typeof id !== "string" || !THEME_LOCAL_ID_PATTERN.test(id) || ids.has(id) || typeof name !== "string" || !name.trim() || name.trim().length > 80 || typeof description !== "string" || !description.trim() || description.trim().length > 320 || Array.from(name + description).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) || typeof source !== "string") return {
			ok: false,
			error: `themes[${index}] requires a unique safe id, name, description, and JSON source`
		};
		const relativePath = source.replace(/^\.\//, "");
		if (!/^(?:[a-z0-9_-][a-z0-9._-]*\/)*[a-z0-9_-][a-z0-9._-]*\.json$/i.test(relativePath)) return {
			ok: false,
			error: `themes[${index}].source must be a JSON file inside the plugin root`
		};
		try {
			themes.push({
				id,
				name: name.trim(),
				description: description.trim(),
				source: relativePath,
				...normalizeArtwork(hats, critters, `themes[${index}]`)
			});
		} catch (error) {
			return {
				ok: false,
				error: error instanceof Error ? error.message : "invalid theme artwork"
			};
		}
		ids.add(id);
	}
	if (manifestSource && themes.some((theme) => theme.hats || theme.critters)) try {
		validateArtworkDuplicates(manifestSource);
	} catch (error) {
		return {
			ok: false,
			error: error instanceof Error ? error.message : "invalid theme artwork"
		};
	}
	return {
		ok: true,
		themes
	};
}
//#endregion
//#region src/plugins/manifest.ts
/** Loads and normalizes OpenClaw plugin manifests, including contracts and config schemas. */
/** Canonical plugin manifest filename inside plugin roots. */
const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
const MAX_PLUGIN_MANIFEST_BYTES = 262144;
const CORE_RESERVED_PLUGIN_IDS = /* @__PURE__ */ new Set(["node-mcp"]);
const VALID_PLUGIN_KINDS = /* @__PURE__ */ new Set(["memory", "context-engine"]);
function isCoreReservedPluginId(id) {
	return CORE_RESERVED_PLUGIN_IDS.has(normalizePluginPolicyId(id));
}
function parsePluginKind(raw) {
	const values = typeof raw === "string" ? [raw] : Array.isArray(raw) ? raw : [];
	const kinds = [];
	for (const value of values) {
		if (typeof value !== "string" || !VALID_PLUGIN_KINDS.has(value)) continue;
		const kind = value;
		if (!kinds.includes(kind)) kinds.push(kind);
	}
	return kinds.length === 0 ? void 0 : kinds.length === 1 ? kinds[0] : kinds;
}
function parseDoctorStateMigrationDescriptors(raw) {
	if (typeof raw === "boolean") return raw;
	if (!Array.isArray(raw)) return;
	const seen = /* @__PURE__ */ new Set();
	return raw.flatMap((value) => {
		if (!isRecord(value)) return [];
		const id = normalizeOptionalString(value.id);
		if (!id || seen.has(id)) return [];
		seen.add(id);
		return [{
			id,
			...value.doctorOnly === true ? { doctorOnly: true } : {},
			...value.phase === "after-session-repair" ? { phase: "after-session-repair" } : {}
		}];
	});
}
function parseManifestBackupResources(raw) {
	if (raw === void 0) return { ok: true };
	if (!Array.isArray(raw)) return {
		ok: false,
		error: "backupResources must be an array"
	};
	const resources = /* @__PURE__ */ new Map();
	for (const [index, entry] of raw.entries()) {
		if (!isRecord(entry) || Object.keys(entry).length !== 3 || !("disposition" in entry) || !("scope" in entry) || !("relativePath" in entry)) return {
			ok: false,
			error: `backupResources[${index}] must contain only disposition, scope, and relativePath`
		};
		const { disposition, scope, relativePath } = entry;
		if (disposition !== "include" && disposition !== "regenerable") return {
			ok: false,
			error: `backupResources[${index}].disposition is invalid`
		};
		if (scope !== "state" && scope !== "agent") return {
			ok: false,
			error: `backupResources[${index}].scope is invalid`
		};
		if (typeof relativePath !== "string" || !relativePath || relativePath.includes("\\") || relativePath.includes("\0") || path.posix.isAbsolute(relativePath) || path.win32.isAbsolute(relativePath) || /^[A-Za-z][A-Za-z\d+.-]*:/.test(relativePath) || relativePath.split("/").some((segment) => !segment || segment === "." || segment === "..")) return {
			ok: false,
			error: `backupResources[${index}].relativePath must be a strict relative POSIX path`
		};
		const resource = {
			disposition,
			scope,
			relativePath
		};
		resources.set(`${scope}\0${relativePath}\0${disposition}`, resource);
	}
	return {
		ok: true,
		resources: [...resources.entries()].toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([, resource]) => resource)
	};
}
function loadPluginManifest(rootDir, rejectHardlinks = true, rootRealPath) {
	const file = readPluginCacheFile({
		rootDir,
		relativePath: PLUGIN_MANIFEST_FILENAME,
		...rootRealPath !== void 0 ? { rootRealPath } : {},
		maxBytes: MAX_PLUGIN_MANIFEST_BYTES,
		rejectHardlinks
	});
	const manifestPath = file.ok ? file.path : path.join(rootDir, PLUGIN_MANIFEST_FILENAME);
	if (!file.ok) return matchRootFileOpenFailure(file.failure, {
		path: () => ({
			ok: false,
			error: `plugin manifest not found: ${manifestPath}`,
			manifestPath
		}),
		fallback: (failure) => ({
			ok: false,
			error: `unsafe plugin manifest path: ${manifestPath} (${failure.reason})`,
			manifestPath
		})
	});
	if (file.manifest) return file.manifest;
	const cacheResult = (result) => {
		return file.manifest = result;
	};
	const parsed = parsePluginCacheJson(file, { json5: true });
	if (!parsed.ok) return cacheResult({
		ok: false,
		error: `failed to parse plugin manifest: ${String(parsed.error)}`,
		manifestPath
	});
	const raw = parsed.value;
	if (!isRecord(raw)) return cacheResult({
		ok: false,
		error: "plugin manifest must be an object",
		manifestPath
	});
	const id = normalizeOptionalString(raw.id) ?? "";
	if (!id) return cacheResult({
		ok: false,
		error: "plugin manifest requires id",
		manifestPath
	});
	if (isCoreReservedPluginId(id)) return cacheResult({
		ok: false,
		error: `plugin manifest id "${id}" is reserved by OpenClaw core`,
		manifestPath
	});
	const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null;
	if (!configSchema) return cacheResult({
		ok: false,
		error: "plugin manifest requires configSchema",
		manifestPath
	});
	const backupResources = parseManifestBackupResources(raw.backupResources);
	if (!backupResources.ok) return cacheResult({
		ok: false,
		error: `invalid plugin manifest backupResources: ${backupResources.error}`,
		manifestPath,
		diagnosticCode: "backup-resource-declaration-invalid"
	});
	const categories = validatePluginCategories(raw.categories);
	if (!categories.ok) return cacheResult({
		ok: false,
		error: `invalid plugin manifest categories: ${categories.error}`,
		manifestPath
	});
	const requiresPlugins = normalizeTrimmedStringList(raw.requiresPlugins);
	const enabledByDefaultOnPlatforms = normalizeManifestPlatforms(raw.enabledByDefaultOnPlatforms);
	const legacyPluginIds = normalizeTrimmedStringList(raw.legacyPluginIds);
	const autoEnableWhenConfiguredProviders = normalizeTrimmedStringList(raw.autoEnableWhenConfiguredProviders);
	const providers = normalizeTrimmedStringList(raw.providers);
	const channels = normalizeTrimmedStringList(raw.channels);
	const contracts = normalizeManifestContracts(raw.contracts);
	const cliBackends = normalizeTrimmedStringList(raw.cliBackends);
	const rawDoctorContract = isRecord(raw.doctorContract) ? raw.doctorContract : void 0;
	const stateMigrations = parseDoctorStateMigrationDescriptors(rawDoctorContract?.stateMigrations);
	const doctorContract = rawDoctorContract ? {
		...Object.fromEntries([
			"configRepair",
			"resolveSessionStoreAgentIds",
			"sessionRouteStateOwners"
		].flatMap((key) => typeof rawDoctorContract[key] === "boolean" ? [[key, rawDoctorContract[key]]] : [])),
		...stateMigrations !== void 0 ? { stateMigrations } : {}
	} : void 0;
	const manifestBeforeDashboard = {
		id,
		configSchema,
		..."categories" in categories ? { categories: categories.categories } : {},
		...backupResources.resources !== void 0 ? { backupResources: backupResources.resources } : {},
		...requiresPlugins.length > 0 ? { requiresPlugins } : {},
		...raw.enabledByDefault === true ? { enabledByDefault: true } : {},
		...enabledByDefaultOnPlatforms.length > 0 ? { enabledByDefaultOnPlatforms } : {},
		...legacyPluginIds.length > 0 ? { legacyPluginIds } : {},
		...autoEnableWhenConfiguredProviders.length > 0 ? { autoEnableWhenConfiguredProviders } : {},
		kind: parsePluginKind(raw.kind),
		channels,
		channelAccountKeyPolicies: normalizeChannelAccountKeyPolicies(raw.channelAccountKeyPolicies, channels),
		providers,
		providerCatalogEntry: normalizeOptionalString(raw.providerCatalogEntry),
		capabilityCatalogEntry: raw.capabilityCatalogEntry === void 0 ? void 0 : normalizeOptionalString(raw.capabilityCatalogEntry) ?? "",
		modelSupport: normalizeManifestModelSupport(raw.modelSupport),
		modelCatalog: normalizeModelCatalog(raw.modelCatalog, { ownedProviders: /* @__PURE__ */ new Set([...providers, ...cliBackends]) }),
		modelPricing: normalizeManifestModelPricing(raw.modelPricing, { ownedProviders: new Set(providers) }),
		modelIdNormalization: normalizeManifestModelIdNormalization(raw.modelIdNormalization, { ownedProviders: new Set(providers) }),
		providerEndpoints: normalizeManifestProviderEndpoints(raw.providerEndpoints),
		providerRequest: normalizeManifestProviderRequest(raw.providerRequest, { ownedProviders: new Set(providers) }),
		secretProviderIntegrations: normalizeManifestSecretProviderIntegrations(raw.secretProviderIntegrations),
		cliBackends,
		syntheticAuthRefs: normalizeTrimmedStringList(raw.syntheticAuthRefs),
		nonSecretAuthMarkers: normalizeTrimmedStringList(raw.nonSecretAuthMarkers),
		commandAliases: normalizeManifestCommandAliases(raw.commandAliases),
		cliCommands: normalizeManifestCliCommands(raw.cliCommands),
		providerUsageAuthEnvVars: normalizeStringListRecord(raw.providerUsageAuthEnvVars),
		providerAuthAliases: normalizeManifestProviderAuthAliases(raw.providerAuthAliases),
		providerAuthChoices: normalizeProviderAuthChoices(raw.providerAuthChoices),
		activation: normalizeManifestActivation(raw.activation),
		setup: normalizeManifestSetup(raw.setup),
		doctorContract,
		doctorHealthChecks: raw.doctorHealthChecks === true ? true : void 0,
		sessionRouteStateOwners: raw.sessionRouteStateOwners === void 0 ? void 0 : coerceDoctorSessionRouteStateOwners(raw.sessionRouteStateOwners),
		qaRunners: normalizeManifestQaRunners(raw.qaRunners)
	};
	const dashboardResult = normalizeManifestDashboard(raw.dashboard);
	if (!dashboardResult.ok) return cacheResult({
		ok: false,
		error: `invalid plugin manifest dashboard: ${dashboardResult.error}`,
		manifestPath
	});
	const uiCapabilities = validatePluginUiCapabilities(raw.uiCapabilities);
	const controlUiResult = normalizeManifestControlUi(raw.controlUi);
	if (!controlUiResult.ok) return cacheResult({
		ok: false,
		error: `invalid plugin manifest controlUi: ${controlUiResult.error}`,
		manifestPath
	});
	const themesResult = normalizeManifestThemes(raw.themes, id, file.contents.toString("utf8"));
	if (!themesResult.ok) return cacheResult({
		ok: false,
		error: `invalid plugin manifest themes: ${themesResult.error}`,
		manifestPath
	});
	return cacheResult({
		ok: true,
		...!uiCapabilities.ok ? { warnings: [`ignoring invalid plugin manifest uiCapabilities: ${uiCapabilities.error}`] } : {},
		manifest: {
			...manifestBeforeDashboard,
			dashboard: dashboardResult.dashboard,
			controlUi: controlUiResult.value,
			...uiCapabilities.ok && uiCapabilities.capabilities !== void 0 ? { uiCapabilities: uiCapabilities.capabilities } : {},
			themes: themesResult.themes,
			mcpServers: normalizeManifestMcpServers(raw.mcpServers),
			skills: normalizeTrimmedStringList(raw.skills),
			name: normalizeOptionalString(raw.name),
			description: normalizeOptionalString(raw.description),
			catalog: normalizeManifestCatalog(raw.catalog),
			version: normalizeOptionalString(raw.version),
			uiHints: normalizeConfigUiHints(raw.uiHints),
			configGroups: normalizeConfigGroups(raw.configGroups, configSchema),
			contracts,
			decisionModels: normalizeManifestDecisionModels(raw.decisionModels, contracts?.decisionProviders),
			transcriptSources: normalizeManifestTranscriptSources(raw.transcriptSources, contracts?.transcriptSourceProviders),
			mediaUnderstandingProviderMetadata: normalizeMediaUnderstandingProviderMetadata(raw.mediaUnderstandingProviderMetadata),
			imageGenerationProviderMetadata: normalizeCapabilityProviderMetadata(raw.imageGenerationProviderMetadata),
			videoGenerationProviderMetadata: normalizeCapabilityProviderMetadata(raw.videoGenerationProviderMetadata),
			musicGenerationProviderMetadata: normalizeCapabilityProviderMetadata(raw.musicGenerationProviderMetadata),
			toolMetadata: normalizePluginToolMetadata(raw.toolMetadata),
			configContracts: normalizeManifestConfigContracts(raw.configContracts),
			channelConfigs: normalizeChannelConfigs(raw.channelConfigs)
		},
		manifestPath
	});
}
//#endregion
//#region src/plugins/bundle-manifest.ts
/** Reads Agent/Codex/Claude/Cursor bundle manifests into OpenClaw plugin manifest metadata. */
/** Relative manifest path for Codex-style plugin bundles. */
const CODEX_BUNDLE_MANIFEST_RELATIVE_PATH = ".codex-plugin/plugin.json";
const CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH = ".claude-plugin/plugin.json";
const CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH = ".cursor-plugin/plugin.json";
const AGENT_BUNDLE_MANIFEST_RELATIVE_PATH = "plugin.json";
const AGENT_BUNDLE_EXTENSION_NAMESPACE = "ai.openclaw";
const AGENT_BUNDLE_MANIFEST_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
const MAX_AGENT_BUNDLE_MANIFEST_BYTES = 262144;
const log$1 = createSubsystemLogger("plugins/bundle-manifest");
/** Normalizes string-or-list path fields from bundle manifests. */
function normalizeBundlePathList(value) {
	return normalizeUniqueSingleOrTrimmedStringList(value);
}
function mergeBundlePathLists(...groups) {
	return [...new Set(groups.flat())];
}
function hasInlineCapabilityValue(value) {
	if (typeof value === "string") return value.trim().length > 0;
	if (Array.isArray(value)) return value.length > 0;
	if (isRecord(value)) return Object.keys(value).length > 0;
	return value === true;
}
function slugifyPluginId(raw, rootDir) {
	const fallback = path.basename(rootDir);
	return (normalizeLowercaseStringOrEmpty(raw) || normalizeLowercaseStringOrEmpty(fallback)).replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "") || "bundle-plugin";
}
function loadBundleManifestFile(params) {
	const manifestPath = path.join(params.rootDir, params.manifestRelativePath);
	const file = readPluginCacheFile({
		rootDir: params.rootDir,
		rootRealPath: params.rootRealPath,
		relativePath: params.manifestRelativePath,
		rejectHardlinks: params.rejectHardlinks,
		maxBytes: params.maxBytes ?? MAX_AGENT_BUNDLE_MANIFEST_BYTES
	});
	if (!file.ok) return matchRootFileOpenFailure(file.failure, {
		path: () => {
			if (params.allowMissing) return {
				ok: true,
				raw: {},
				manifestPath
			};
			return {
				ok: false,
				error: `plugin manifest not found: ${manifestPath}`,
				manifestPath
			};
		},
		fallback: (failure) => ({
			ok: false,
			error: `unsafe plugin manifest path: ${manifestPath} (${failure.reason})`,
			manifestPath
		})
	});
	const result = parsePluginCacheJson(file, { json5: !params.strictJson });
	if (!result.ok) return {
		ok: false,
		error: `failed to parse plugin manifest: ${formatErrorMessage(result.error)}`,
		manifestPath
	};
	if (!isRecord(result.value)) return {
		ok: false,
		error: "plugin manifest must be an object",
		manifestPath
	};
	return {
		ok: true,
		raw: result.value,
		manifestPath
	};
}
function resolveCodexComponentDirs(raw, rootDir, component) {
	const declared = normalizeBundlePathList(raw[component]);
	if (declared.length > 0) return declared;
	return pluginCacheExistsSync(path.join(rootDir, component)) ? [component] : [];
}
function resolveCursorCommandRootDirs(raw, rootDir) {
	return resolveBundleComponentPaths(raw.commands, rootDir, [".cursor/commands"]);
}
function resolveCursorSkillDirs(raw, rootDir) {
	return mergeBundlePathLists(resolveBundleComponentPaths(raw.skills, rootDir, ["skills"]), resolveCursorCommandRootDirs(raw, rootDir));
}
function resolveCursorAgentDirs(raw, rootDir) {
	return resolveBundleComponentPaths(raw.subagents ?? raw.agents, rootDir, [".cursor/agents"]);
}
function resolveBundleComponentPaths(value, rootDir, defaults) {
	const declared = normalizeBundlePathList(value);
	return mergeBundlePathLists(defaults.filter((candidate) => pluginCacheExistsSync(path.join(rootDir, candidate))), declared);
}
function buildCursorCapabilities(raw, rootDir) {
	const capabilities = [];
	if (resolveCursorSkillDirs(raw, rootDir).length > 0) capabilities.push("skills");
	if (resolveCursorCommandRootDirs(raw, rootDir).length > 0) capabilities.push("commands");
	if (resolveCursorAgentDirs(raw, rootDir).length > 0) capabilities.push("agents");
	for (const [capability, defaultPath] of [
		["hooks", ".cursor/hooks.json"],
		["rules", ".cursor/rules"],
		["mcpServers", ".mcp.json"]
	]) if (hasInlineCapabilityValue(raw[capability]) || pluginCacheExistsSync(path.join(rootDir, defaultPath))) capabilities.push(capability);
	return capabilities;
}
function resolveAgentSkillDirs(rootDir) {
	try {
		return pluginCacheStatSync(path.join(rootDir, "skills"))?.isDirectory() ? ["skills"] : [];
	} catch {
		return [];
	}
}
function resolveAgentActivation(raw, manifestPath) {
	if (raw.extensions === void 0) return;
	if (!isRecord(raw.extensions)) {
		log$1.warn(`ignoring Agent Plugins extensions in ${manifestPath}: expected an object`);
		return;
	}
	const openclawExtension = raw.extensions[AGENT_BUNDLE_EXTENSION_NAMESPACE];
	if (openclawExtension === void 0) return;
	if (!isRecord(openclawExtension)) {
		log$1.warn(`ignoring Agent Plugins ${AGENT_BUNDLE_EXTENSION_NAMESPACE} extension in ${manifestPath}: expected an object`);
		return;
	}
	return normalizeManifestActivation(openclawExtension.activation);
}
function loadBundleManifest(params) {
	const rejectHardlinks = params.rejectHardlinks ?? true;
	const manifestRelativePath = params.bundleFormat === "codex" ? CODEX_BUNDLE_MANIFEST_RELATIVE_PATH : params.bundleFormat === "cursor" ? CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH : params.bundleFormat === "agent" ? AGENT_BUNDLE_MANIFEST_RELATIVE_PATH : CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH;
	const loaded = loadBundleManifestFile({
		rootDir: params.rootDir,
		...params.rootRealPath !== void 0 ? { rootRealPath: params.rootRealPath } : {},
		manifestRelativePath,
		rejectHardlinks,
		allowMissing: params.bundleFormat === "claude",
		strictJson: params.bundleFormat === "agent",
		...params.bundleFormat === "agent" ? { maxBytes: MAX_AGENT_BUNDLE_MANIFEST_BYTES } : {}
	});
	if (!loaded.ok) return loaded;
	const raw = loaded.raw;
	const interfaceRecord = isRecord(raw.interface) ? raw.interface : void 0;
	const name = normalizeOptionalString(raw.name);
	const description = normalizeOptionalString(raw.description) ?? normalizeOptionalString(raw.shortDescription) ?? normalizeOptionalString(interfaceRecord?.shortDescription);
	const version = normalizeOptionalString(raw.version);
	const manifest = {
		id: slugifyPluginId(name, params.rootDir),
		name,
		description,
		version,
		skills: [],
		settingsFiles: [],
		hooks: [],
		bundleFormat: params.bundleFormat,
		activation: void 0,
		capabilities: []
	};
	if (params.bundleFormat === "agent") {
		if (raw.$schema !== AGENT_BUNDLE_MANIFEST_SCHEMA) return {
			ok: false,
			error: `root plugin.json is not an Agent Plugins manifest; expected $schema ${AGENT_BUNDLE_MANIFEST_SCHEMA}`,
			manifestPath: loaded.manifestPath
		};
		if (!name) return {
			ok: false,
			error: "agent plugin manifest name must be a non-empty string",
			manifestPath: loaded.manifestPath
		};
		manifest.skills = resolveAgentSkillDirs(params.rootDir);
		manifest.activation = resolveAgentActivation(raw, loaded.manifestPath);
		manifest.capabilities = [...manifest.skills.length > 0 ? ["skills"] : [], ...pluginCacheExistsSync(path.join(params.rootDir, "mcp.json")) ? ["mcpServers"] : []];
	} else {
		manifest.activation = normalizeManifestActivation(raw.activation);
		if (params.bundleFormat === "codex") {
			manifest.skills = resolveCodexComponentDirs(raw, params.rootDir, "skills");
			manifest.hooks = resolveCodexComponentDirs(raw, params.rootDir, "hooks");
			manifest.capabilities = [...manifest.skills.length > 0 ? ["skills"] : [], ...manifest.hooks.length > 0 ? ["hooks"] : []];
			for (const [capability, defaultPath] of [["mcpServers", ".mcp.json"], ["apps", ".app.json"]]) if (hasInlineCapabilityValue(raw[capability]) || pluginCacheExistsSync(path.join(params.rootDir, defaultPath))) manifest.capabilities.push(capability);
		} else if (params.bundleFormat === "cursor") {
			manifest.skills = resolveCursorSkillDirs(raw, params.rootDir);
			manifest.capabilities = buildCursorCapabilities(raw, params.rootDir);
		} else Object.assign(manifest, resolveClaudeComponents(raw, params.rootDir));
	}
	return {
		ok: true,
		manifest,
		manifestPath: loaded.manifestPath
	};
}
function resolveClaudeComponents(raw, rootDir) {
	const skillRoots = resolveBundleComponentPaths(raw.skills, rootDir, ["skills"]);
	const commands = resolveBundleComponentPaths(raw.commands, rootDir, ["commands"]);
	const agents = resolveBundleComponentPaths(raw.agents, rootDir, ["agents"]);
	const outputStyles = resolveBundleComponentPaths(raw.outputStyles, rootDir, ["output-styles"]);
	const skills = mergeBundlePathLists(skillRoots, commands, agents, outputStyles);
	const settingsFiles = pluginCacheExistsSync(path.join(rootDir, "settings.json")) ? ["settings.json"] : [];
	const hooks = resolveBundleComponentPaths(raw.hooks, rootDir, ["hooks/hooks.json"]);
	const capabilities = [];
	if (skills.length > 0) capabilities.push("skills");
	if (commands.length > 0) capabilities.push("commands");
	if (agents.length > 0) capabilities.push("agents");
	if (hasInlineCapabilityValue(raw.hooks) || hooks.length > 0) capabilities.push("hooks");
	if (hasInlineCapabilityValue(raw.mcpServers) || resolveBundleComponentPaths(raw.mcpServers, rootDir, [".mcp.json"]).length > 0) capabilities.push("mcpServers");
	if (hasInlineCapabilityValue(raw.lspServers) || resolveBundleComponentPaths(raw.lspServers, rootDir, [".lsp.json"]).length > 0) capabilities.push("lspServers");
	if (hasInlineCapabilityValue(raw.outputStyles) || outputStyles.length > 0) capabilities.push("outputStyles");
	if (settingsFiles.length > 0) capabilities.push("settings");
	return {
		skills,
		settingsFiles,
		hooks,
		capabilities
	};
}
function detectBundleManifestFormat(rootDir, hasPackageExtensions = false) {
	if (hasPackageExtensions && pluginCacheExistsSync(path.join(rootDir, "openclaw.plugin.json"))) return null;
	if (pluginCacheExistsSync(path.join(rootDir, ".codex-plugin/plugin.json"))) return "codex";
	if (pluginCacheExistsSync(path.join(rootDir, ".cursor-plugin/plugin.json"))) return "cursor";
	if (pluginCacheExistsSync(path.join(rootDir, ".claude-plugin/plugin.json"))) return "claude";
	if (pluginCacheExistsSync(path.join(rootDir, "openclaw.plugin.json"))) return null;
	if (pluginCacheExistsSync(path.join(rootDir, "plugin.json"))) {
		const agentManifest = loadBundleManifestFile({
			rootDir,
			manifestRelativePath: AGENT_BUNDLE_MANIFEST_RELATIVE_PATH,
			rejectHardlinks: false,
			strictJson: true,
			maxBytes: MAX_AGENT_BUNDLE_MANIFEST_BYTES
		});
		if (agentManifest.ok && agentManifest.raw.$schema === AGENT_BUNDLE_MANIFEST_SCHEMA) return "agent";
	}
	if (DEFAULT_PLUGIN_ENTRY_CANDIDATES.some((candidate) => pluginCacheExistsSync(path.join(rootDir, candidate)))) return null;
	if ([
		path.join(rootDir, "skills"),
		path.join(rootDir, "commands"),
		path.join(rootDir, "agents"),
		path.join(rootDir, "hooks", "hooks.json"),
		path.join(rootDir, ".mcp.json"),
		path.join(rootDir, ".lsp.json"),
		path.join(rootDir, "settings.json")
	].some((candidate) => pluginCacheExistsSync(candidate))) return "claude";
	return null;
}
//#endregion
//#region src/plugins/package-compat.ts
function normalizePartialComparableVersion(version) {
	const trimmed = version.trim();
	return /^[vV]?[0-9]+\.[0-9]+$/.test(trimmed) ? {
		version: `${trimmed}.0`,
		isPartial: true
	} : {
		version: trimmed,
		isPartial: false
	};
}
function shouldPreservePluginApiPrereleaseFloor(target) {
	return Boolean(prerelease(normalizePartialComparableVersion(target).version));
}
function normalizePluginApiVersionForComparator(version, target) {
	const normalizedCorrection = normalizeOpenClawNumericCorrectionForPluginApi(version);
	if (normalizedCorrection) return normalizedCorrection;
	return shouldPreservePluginApiPrereleaseFloor(target) ? version : normalizeOpenClawReleaseSuffixForPluginApi(version);
}
function satisfiesComparator(version, token) {
	const trimmed = token.trim();
	if (!trimmed) return true;
	const match = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(trimmed);
	if (!match) return false;
	const operator = match[1] ?? "";
	const target = match[2]?.trim();
	if (!target || /^[<>=^~]/.test(target)) return false;
	const comparableVersion = normalizePluginApiVersionForComparator(version, target);
	const normalizedTarget = normalizePartialComparableVersion(target);
	const comparator = normalizedTarget.isPartial && !operator ? `>=${normalizedTarget.version}` : `${operator}${normalizedTarget.version}`;
	return satisfies(comparableVersion, comparator, { includePrerelease: true });
}
function satisfiesSemverRange(version, range) {
	if (range.includes("||")) return false;
	const tokens = normalizeStringEntries(range.trim().split(/\s+/));
	if (tokens.length === 0) return false;
	return tokens.every((token) => satisfiesComparator(version, token));
}
const OPENCLAW_RELEASE_SUFFIX_PATTERN = /^[vV]?(\d{4}\.[1-9]\d?\.[1-9]\d*)(?:-\d+|-(?:alpha|beta|rc)\.\d+)$/i;
const OPENCLAW_NUMERIC_CORRECTION_PATTERN = /^[vV]?(\d{4}\.[1-9]\d?\.[1-9]\d*)-\d+$/;
function normalizeOpenClawNumericCorrectionForPluginApi(pluginApiVersion) {
	return OPENCLAW_NUMERIC_CORRECTION_PATTERN.exec(pluginApiVersion.trim())?.[1];
}
function normalizeOpenClawReleaseSuffixForPluginApi(pluginApiVersion) {
	return OPENCLAW_RELEASE_SUFFIX_PATTERN.exec(pluginApiVersion.trim())?.[1] ?? pluginApiVersion;
}
/** Resolves the plugin API compatibility range declared by package metadata. */
function resolvePackagePluginApiRange(packageMetadata) {
	if (!isRecord(packageMetadata) || !("compat" in packageMetadata)) return { ok: true };
	const compat = packageMetadata.compat;
	if (compat === void 0 || compat === null) return { ok: true };
	if (!isRecord(compat)) return {
		ok: false,
		error: "package.json openclaw.compat must be an object"
	};
	if (!("pluginApi" in compat)) return { ok: true };
	const pluginApi = compat.pluginApi;
	if (typeof pluginApi !== "string") return {
		ok: false,
		error: "package.json openclaw.compat.pluginApi must be a string"
	};
	const range = pluginApi.trim();
	if (!range) return {
		ok: false,
		error: "package.json openclaw.compat.pluginApi must not be empty"
	};
	return {
		ok: true,
		range
	};
}
/** Checks whether a host plugin API version satisfies a package plugin API range. */
function satisfiesPluginApiRange(pluginApiVersion, pluginApiRange) {
	if (!pluginApiRange) return true;
	return satisfiesSemverRange(pluginApiVersion, pluginApiRange);
}
//#endregion
//#region src/plugins/runtime-degraded-state.ts
/** Boot-stable quarantine state for configured plugins whose payload failed verification. */
const PLUGIN_AVAILABILITY_POLICY = {
	state: "configured-unavailable",
	severity: "warning",
	repairCommand: "openclaw doctor --fix"
};
//#endregion
//#region src/plugins/discovery-availability.ts
const CONFIGURED_PLUGIN_PATH_UNAVAILABLE = "configured-plugin-path-unavailable";
const CONFIGURED_PLUGIN_PATH_INSPECTION_FAILED = "configured-plugin-path-inspection-failed";
function shouldSkipIncompatiblePackagePluginApi(params) {
	if (params.origin === "bundled") return false;
	const packagePluginApiRangeCheck = resolvePackagePluginApiRange(params.packageManifest);
	if (!packagePluginApiRangeCheck.ok) {
		params.diagnostics.push({
			level: "warn",
			configDisposition: "preserve",
			source: path.join(params.packageDir, "package.json"),
			message: `invalid package plugin API metadata: ${packagePluginApiRangeCheck.error}; skipping discovery (check package.json openclaw.compat.pluginApi)`,
			pluginId: params.pluginId
		});
		return true;
	}
	const packagePluginApiRange = packagePluginApiRangeCheck.range;
	if (!packagePluginApiRange) return false;
	const compatibilityHostVersion = resolveCompatibilityHostVersion(params.env);
	if (satisfiesPluginApiRange(compatibilityHostVersion, packagePluginApiRange)) return false;
	params.diagnostics.push({
		level: "warn",
		configDisposition: "preserve",
		source: path.join(params.packageDir, "package.json"),
		message: `plugin requires plugin API ${packagePluginApiRange}, but this host is ${compatibilityHostVersion}; skipping discovery (check "openclaw --version", OPENCLAW_COMPATIBILITY_HOST_VERSION, or run "openclaw doctor")`,
		pluginId: params.pluginId
	});
	return true;
}
function pluginPathFailureDiagnostic(source, origin, error) {
	if (origin === "config" && !isMissingPathError(error)) {
		const errorCode = extractErrorCode(error) ?? "UNKNOWN";
		const fixHint = `${errorCode === "EACCES" || errorCode === "EPERM" ? `Fix permissions on ${source}` : errorCode === "ELOOP" ? `Fix the symbolic link loop at ${source}` : `Resolve the filesystem inspection error on ${source}`}, then run \`${PLUGIN_AVAILABILITY_POLICY.repairCommand}\`.`;
		return {
			level: "warn",
			code: CONFIGURED_PLUGIN_PATH_INSPECTION_FAILED,
			configDisposition: "preserve",
			errorCode,
			source,
			fixHint,
			message: `Configured plugin load path inspection failed: ${source} (${formatErrorMessageWithCode(error)}). Uninspected plugin configuration is preserved. ${fixHint}`
		};
	}
	return origin === "config" ? {
		level: "warn",
		code: CONFIGURED_PLUGIN_PATH_UNAVAILABLE,
		configDisposition: "preserve",
		source,
		message: `Configured plugin load path is unavailable: ${source}. Uninspected plugin configuration is preserved. Restore access to the path, then run \`${PLUGIN_AVAILABILITY_POLICY.repairCommand}\`.`
	} : {
		level: "error",
		source,
		message: `plugin path not found: ${source}`
	};
}
function inspectPluginLoadPath(source, origin, diagnostics) {
	try {
		const stat = pluginCacheStatSync(source, origin === "config");
		if (!stat && !pluginCacheExistsSync(source)) diagnostics.push(pluginPathFailureDiagnostic(source, origin, void 0));
		if (origin === "config" && stat) {
			if (stat.isDirectory()) readPluginCacheDirectory(source);
			else if (!stat.isFile()) throw Object.assign(/* @__PURE__ */ new Error("Plugin load path is not a regular file or directory"), { code: "ERR_INVALID_FILE_TYPE" });
		}
		return stat;
	} catch (error) {
		diagnostics.push(pluginPathFailureDiagnostic(source, origin, error));
		return null;
	}
}
//#endregion
//#region src/plugins/hardlink-policy.ts
/** Enforces plugin root hardlink policy with bundled and immutable Nix-store exceptions. */
const NIX_STORE_ROOT = "/nix/store";
/** Returns true when a plugin root resolves inside the immutable Nix store. */
function isNixStorePluginRoot(rootDir) {
	const rootRealPath = pluginCacheRealpathSync(rootDir) ?? path.resolve(rootDir);
	return rootRealPath === NIX_STORE_ROOT || rootRealPath.startsWith(`${NIX_STORE_ROOT}/`);
}
/** Decides whether plugin file hardlinks should fail boundary validation for one root. */
function shouldRejectHardlinkedPluginFiles(params) {
	if (params.origin === "bundled") return false;
	if (resolveIsNixMode(params.env) && isNixStorePluginRoot(params.rootDir)) return false;
	return true;
}
//#endregion
//#region src/plugins/discovery-required-plugins.ts
function addMissingRequiredPluginDiagnostics(result, params) {
	const candidatePolicyIds = new Set(result.candidates.map((candidate) => normalizePluginPolicyId(candidate.idHint)));
	const seen = /* @__PURE__ */ new Set();
	let configuredFileManifestPolicyIds;
	for (const candidate of result.candidates) for (const requiredPluginId of candidate.requiredPluginIds ?? []) {
		const requiredPolicyId = normalizePluginPolicyId(requiredPluginId);
		if (candidatePolicyIds.has(requiredPolicyId)) continue;
		if (!configuredFileManifestPolicyIds) {
			configuredFileManifestPolicyIds = /* @__PURE__ */ new Set();
			for (const configuredCandidate of result.candidates) {
				if (configuredCandidate.origin !== "config" || configuredCandidate.packageDir) continue;
				const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
					origin: configuredCandidate.origin,
					rootDir: configuredCandidate.rootDir,
					env: params.env
				});
				const manifest = loadPluginManifest(configuredCandidate.rootDir, rejectHardlinks);
				if (manifest.ok) configuredFileManifestPolicyIds.add(normalizePluginPolicyId(manifest.manifest.id));
			}
		}
		if (configuredFileManifestPolicyIds.has(requiredPolicyId)) continue;
		const key = `${normalizePluginPolicyId(candidate.idHint)}\0${requiredPolicyId}`;
		if (seen.has(key)) continue;
		seen.add(key);
		result.diagnostics.push({
			level: "warn",
			pluginId: candidate.idHint,
			source: candidate.requiredPluginSource ?? candidate.source,
			message: `plugin "${candidate.idHint}" requires plugin "${requiredPluginId}"; install "${requiredPluginId}" to use it`
		});
	}
}
//#endregion
//#region src/plugins/install-paths.ts
/** Validates a plugin id for install path safety. */
function validatePluginId(pluginId) {
	const trimmed = pluginId.trim();
	if (!trimmed) return "invalid plugin name: missing";
	if (trimmed.includes("\\")) return "invalid plugin name: path separators not allowed";
	const segments = trimmed.split("/");
	if (segments.some((segment) => !segment)) return "invalid plugin name: malformed scope";
	if (segments.some((segment) => segment === "." || segment === "..")) return "invalid plugin name: reserved path segment";
	if (segments.length === 1) {
		if (trimmed.startsWith("@")) return "invalid plugin name: scoped ids must use @scope/name format";
		return null;
	}
	if (segments.length !== 2) return "invalid plugin name: path separators not allowed";
	if (!segments[0]?.startsWith("@") || segments[0].length < 2) return "invalid plugin name: scoped ids must use @scope/name format";
	return null;
}
/** Resolves the default directory for path-installed plugin extensions. */
function resolveDefaultPluginExtensionsDir(env = process.env, homedir) {
	return resolveActivePluginInstallDir("extensions", env, homedir);
}
/** Resolves the default directory for managed npm plugin installs. */
function resolveDefaultPluginNpmDir(env = process.env, homedir) {
	return resolveActivePluginInstallDir("npm", env, homedir);
}
/** Encodes an npm package name into a managed npm project directory name. */
function encodePluginNpmProjectDirName(packageName) {
	const trimmed = packageName.trim();
	if (!trimmed) throw new Error("invalid npm package name: missing");
	return safePathSegmentHashed(trimmed);
}
/** Resolves the directory containing managed npm plugin projects. */
function resolvePluginNpmProjectsDir(npmDir) {
	const npmBase = npmDir ? resolveUserPath(npmDir) : resolveDefaultPluginNpmDir();
	return path.join(npmBase, "projects");
}
/** Resolves the managed npm project directory for a package name. */
function resolvePluginNpmProjectDir(params) {
	return path.join(resolvePluginNpmProjectsDir(params.npmDir), encodePluginNpmProjectDirName(params.packageName));
}
const PLUGIN_NPM_GENERATION_PROJECT_SEPARATOR = "__openclaw-generation__";
const PLUGIN_NPM_GENERATION_KEY_DIR_NAME_PATTERN = new RegExp(`^g-[a-f0-9]{16}$`, "u");
/** Resolves the managed npm artifact-generation project directory prefix for a package. */
function resolvePluginNpmGenerationProjectDirPrefix(packageName) {
	return `${encodePluginNpmProjectDirName(packageName)}${PLUGIN_NPM_GENERATION_PROJECT_SEPARATOR}`;
}
/** Checks that a managed path preserves its npm-root-relative real path. */
function isPluginNpmManagedPath(params) {
	const npmDir = path.resolve(params.npmDir ?? resolveDefaultPluginNpmDir());
	const managedPath = path.resolve(params.managedPath);
	try {
		return lstatSync(npmDir).isDirectory() && path.relative(npmDir, managedPath) === path.relative(realpathSync(npmDir), params.allowMissing ? resolvePathViaExistingAncestorSync$1(managedPath) : realpathSync(managedPath));
	} catch {
		return false;
	}
}
/** Checks whether a project directory has the exact managed shape owned by a package. */
function isPluginNpmProjectDir(params) {
	const npmDir = path.resolve(params.npmDir ?? resolveDefaultPluginNpmDir());
	const projectDir = path.resolve(params.projectDir);
	if (path.dirname(projectDir) !== path.resolve(resolvePluginNpmProjectsDir(npmDir))) return false;
	if (!isPluginNpmManagedPath({
		managedPath: projectDir,
		npmDir
	})) return false;
	if (!isPluginNpmManagedPath({
		managedPath: path.join(projectDir, "node_modules", ...params.packageName.split("/")),
		npmDir,
		allowMissing: true
	})) return false;
	if (projectDir === path.resolve(resolvePluginNpmProjectDir(params))) return true;
	const projectName = path.basename(projectDir);
	const generationPrefix = resolvePluginNpmGenerationProjectDirPrefix(params.packageName);
	return projectName.startsWith(generationPrefix) && PLUGIN_NPM_GENERATION_KEY_DIR_NAME_PATTERN.test(projectName.slice(generationPrefix.length));
}
//#endregion
//#region src/plugins/legacy-npm-declaration.ts
/** Reads legacy npm plugin declaration files left by early plugin installs. */
/** Legacy declaration filename used by early npm-backed plugin installs. */
const LEGACY_NPM_DECLARATION_FILE = "openclaw.extension.json";
/** Reads a legacy npm plugin declaration when a plugin directory still has one. */
function readLegacyNpmPluginDeclaration(pluginDir) {
	const source = path.join(pluginDir, LEGACY_NPM_DECLARATION_FILE);
	const parsed = tryReadJsonSync(source);
	if (!isRecord(parsed) || parsed.type !== "npm") return null;
	const pluginId = typeof parsed.name === "string" ? parsed.name.trim() : "";
	const npmSpec = typeof parsed.npmSpec === "string" ? parsed.npmSpec.trim() : "";
	if (!pluginId || validatePluginId(pluginId) || !parseRegistryNpmSpec(npmSpec)) return null;
	return {
		pluginId,
		npmSpec,
		source
	};
}
//#endregion
//#region src/plugins/package-entry-resolution.ts
function resolvePackageRuntimeExtensionEntries(params) {
	const declared = getPackageManifestMetadata(params.manifest ?? void 0)?.runtimeExtensions;
	const runtimeExtensions = [];
	if (!Array.isArray(declared)) return {
		ok: true,
		runtimeExtensions
	};
	for (const [index, entry] of declared.entries()) {
		const normalized = normalizeOptionalString(entry);
		if (!normalized) return {
			ok: false,
			error: `package.json openclaw.runtimeExtensions[${index}] must be a non-empty string`
		};
		runtimeExtensions.push(normalized);
	}
	if (runtimeExtensions.length > 0 && runtimeExtensions.length !== params.extensions.length) return {
		ok: false,
		error: `package.json openclaw.runtimeExtensions length (${runtimeExtensions.length}) must match openclaw.extensions length (${params.extensions.length})`
	};
	return {
		ok: true,
		runtimeExtensions
	};
}
function missingCompiledRuntimeEntryMessage(params) {
	const label = params.context === "install" ? "package install" : "installed plugin package";
	const recovery = params.context === "install" ? "retry installation after the publisher ships compiled JavaScript" : "update or reinstall the plugin after the publisher ships compiled JavaScript, or disable/uninstall the plugin until then";
	return `${label} requires compiled runtime output for TypeScript entry ${params.entry}: expected ${params.candidates.join(", ")}. This is a plugin packaging issue, not a local config problem; ${recovery}. TypeScript source fallback is only supported for source checkouts and local development paths.`;
}
function resolvePackageEntrySource(params) {
	const source = path.resolve(params.packageDir, params.entryPath);
	const rejectHardlinks = params.rejectHardlinks ?? true;
	const candidates = [source];
	const openCandidate = (absolutePath) => {
		const opened = checkPluginCacheEntry({
			rootDir: params.packageDir,
			relativePath: path.relative(params.packageDir, absolutePath),
			rootRealPath: params.packageRootRealPath,
			rejectHardlinks
		});
		if (!opened.ok) return matchRootFileOpenFailure(opened, {
			path: () => null,
			io: () => {
				params.diagnostics.push({
					level: "warn",
					...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
					message: `extension entry unreadable (I/O error): ${params.entryPath}`,
					source: params.sourceLabel
				});
				return null;
			},
			fallback: () => {
				params.diagnostics.push({
					level: "error",
					...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
					message: `extension entry escapes package directory: ${params.entryPath}`,
					source: params.sourceLabel
				});
				return null;
			}
		});
		return opened.exists ? opened.path : null;
	};
	if (!rejectHardlinks) {
		const builtCandidate = source.replace(/\.[^.]+$/u, ".js");
		if (builtCandidate !== source) candidates.push(builtCandidate);
	}
	for (const candidate of candidates) {
		if (!pluginCacheExistsSync(candidate)) continue;
		return openCandidate(candidate);
	}
	return openCandidate(source);
}
function resolveSafePackageEntry(params) {
	const absolutePath = path.resolve(params.packageDir, params.entryPath);
	if (pluginCacheExistsSync(absolutePath)) {
		const existingSource = resolvePackageEntrySource(params);
		if (!existingSource) return null;
		return {
			relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/"),
			existingSource
		};
	}
	if (!checkPluginCacheEntry({
		rootDir: params.packageDir,
		relativePath: params.entryPath,
		rootRealPath: params.packageRootRealPath,
		rejectHardlinks: params.rejectHardlinks ?? true
	}).ok) {
		params.diagnostics.push({
			level: "error",
			...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
			message: `extension entry escapes package directory: ${params.entryPath}`,
			source: params.sourceLabel
		});
		return null;
	}
	return { relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/") };
}
function resolveOptionalExistingPackageEntrySource(params) {
	if (!pluginCacheExistsSync(path.resolve(params.packageDir, params.entryPath))) return { status: "missing" };
	const resolved = resolvePackageEntrySource(params);
	return resolved ? {
		status: "resolved",
		source: resolved
	} : { status: "invalid" };
}
function resolvePackageRuntimeEntrySource(params) {
	const safeEntry = resolveSafePackageEntry(params);
	if (!safeEntry) return null;
	if (params.runtimeEntryPath) {
		const runtimeSource = resolvePackageEntrySource({
			...params,
			entryPath: params.runtimeEntryPath
		});
		if (runtimeSource) return runtimeSource;
		params.diagnostics.push({
			level: "error",
			...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
			message: `${params.runtimeEntryLabel ?? "runtime entry"} not found: ${params.runtimeEntryPath}`,
			source: params.sourceLabel
		});
		return null;
	}
	if (params.origin === "config" || params.origin === "global") {
		const builtEntryCandidates = listBuiltRuntimeEntryCandidates(safeEntry.relativePath);
		for (const candidate of builtEntryCandidates) {
			const runtimeSource = resolveOptionalExistingPackageEntrySource({
				...params,
				entryPath: candidate
			});
			if (runtimeSource.status === "resolved") return runtimeSource.source;
			if (runtimeSource.status === "invalid") return null;
		}
		if ((params.requireBuiltRuntimeEntry ?? params.origin === "global") && isTypeScriptPackageEntry(safeEntry.relativePath)) {
			params.diagnostics.push({
				level: "warn",
				...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
				message: missingCompiledRuntimeEntryMessage({
					context: "installed",
					entry: safeEntry.relativePath,
					candidates: builtEntryCandidates
				}),
				source: params.sourceLabel
			});
			return null;
		}
	}
	if (safeEntry.existingSource) return safeEntry.existingSource;
	if (params.rejectHardlinks === false) {
		const trustedFallbackSource = resolvePackageEntrySource(params);
		if (trustedFallbackSource) return trustedFallbackSource;
	}
	params.diagnostics.push({
		level: "error",
		...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
		message: `${params.sourceEntryLabel ?? "extension entry"} not found: ${safeEntry.relativePath}`,
		source: params.sourceLabel
	});
	return null;
}
/** Resolves the runtime setup source for a plugin package manifest. */
function resolvePackageSetupSource(params) {
	const packageManifest = getPackageManifestMetadata(params.manifest ?? void 0);
	const setupEntryPath = normalizeOptionalString(packageManifest?.setupEntry);
	if (!setupEntryPath) return null;
	return resolvePackageRuntimeEntrySource({
		...params,
		entryPath: setupEntryPath,
		sourceEntryLabel: "setup entry",
		runtimeEntryPath: normalizeOptionalString(packageManifest?.runtimeSetupEntry),
		runtimeEntryLabel: "runtime setup entry",
		pluginIdHint: params.pluginIdHint ?? normalizeOptionalString(packageManifest?.plugin?.id) ?? normalizeOptionalString(packageManifest?.channel?.id)
	});
}
/** Keeps declarations paired with their runtime sources when earlier entries cannot resolve. */
function resolvePackageRuntimeExtensions(params) {
	const runtimeResolution = resolvePackageRuntimeExtensionEntries(params);
	if (!runtimeResolution.ok) {
		params.diagnostics.push({
			level: "error",
			...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
			message: runtimeResolution.error,
			source: params.sourceLabel
		});
		return [];
	}
	return params.extensions.flatMap((entryPath, index) => {
		const source = resolvePackageRuntimeEntrySource({
			...params,
			entryPath,
			sourceEntryLabel: "extension entry",
			runtimeEntryPath: runtimeResolution.runtimeExtensions[index],
			runtimeEntryLabel: "runtime extension entry"
		});
		return source ? [{
			entryPath,
			source
		}] : [];
	});
}
//#endregion
//#region src/plugins/plugin-lifecycle-trace.ts
/** Checks the opt-in plugin lifecycle tracing environment flag. */
function isPluginLifecycleTraceEnabled() {
	const raw = process.env.OPENCLAW_PLUGIN_LIFECYCLE_TRACE?.trim().toLowerCase();
	return raw === "1" || raw === "true" || raw === "yes";
}
function emitPluginLifecycleTrace(params) {
	const elapsedMs = Number(process.hrtime.bigint() - params.start) / 1e6;
	const detailText = Object.entries(params.details ?? {}).filter((entry) => entry[1] !== void 0).map(([key, value]) => `${key}=${typeof value === "string" ? JSON.stringify(value) : value}`).join(" ");
	const suffix = detailText ? ` ${detailText}` : "";
	console.error(`[plugins:lifecycle] phase=${JSON.stringify(params.phase)} ms=${elapsedMs.toFixed(2)} status=${params.status}${suffix}`);
}
/** Traces a synchronous plugin lifecycle phase when tracing is enabled. */
function tracePluginLifecyclePhase(phase, fn, details) {
	if (!isPluginLifecycleTraceEnabled()) return fn();
	const start = process.hrtime.bigint();
	let status = "error";
	try {
		const result = fn();
		status = "ok";
		return result;
	} finally {
		emitPluginLifecycleTrace({
			phase,
			start,
			status,
			details
		});
	}
}
//#endregion
//#region src/plugins/record-groups.ts
function groupPluginRecords(records, keyOf) {
	const groups = /* @__PURE__ */ new Map();
	for (const record of records) {
		const key = keyOf(record);
		const group = groups.get(key);
		if (group) group.push(record);
		else groups.set(key, [record]);
	}
	return groups;
}
//#endregion
//#region src/plugins/dev-source-root.ts
/** Env var that points bundled-plugin lookup at an OpenClaw source checkout. */
const OPENCLAW_DEV_SOURCE_ROOT_ENV = "OPENCLAW_DEV_SOURCE_ROOT";
function readPackageName(packageJsonPath) {
	try {
		const parsed = JSON.parse(fsSync.readFileSync(packageJsonPath, "utf-8"));
		return typeof parsed.name === "string" ? parsed.name : null;
	} catch {
		return null;
	}
}
/** Resolves and validates the configured OpenClaw development source root. */
function resolveOpenClawDevSourceRoot(env = process.env) {
	const rawRoot = env[OPENCLAW_DEV_SOURCE_ROOT_ENV]?.trim();
	if (!rawRoot) return null;
	const resolvedRoot = resolveUserPath(rawRoot, env);
	const roots = getPluginCache().sdk.devSourceRoots;
	if (roots.has(resolvedRoot)) return roots.get(resolvedRoot) ?? null;
	const realRoot = pluginCacheRealpathSync(resolvedRoot);
	const result = realRoot && readPackageName(path.join(realRoot, "package.json")) === "openclaw" && pluginCacheExistsSync(path.join(realRoot, "src")) && pluginCacheExistsSync(path.join(realRoot, "extensions")) ? realRoot : null;
	roots.set(resolvedRoot, result);
	return result;
}
/** Source builds own their bundled SDK consumers even without an explicit development selector. */
function resolveBundledPluginSourceRoot(env = process.env) {
	const selected = resolveOpenClawDevSourceRoot(env);
	if (selected) return selected;
	const hostRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
	return hostRoot && isSourceCheckoutRoot(hostRoot) ? hostRoot : null;
}
/** Prioritizes already-bundled candidates; the selector itself never grants provenance. */
function isBundledPluginInsideDevSourceRoot(params) {
	const devSourceRoot = resolveBundledPluginSourceRoot(params.env);
	if (!devSourceRoot) return false;
	if (!isPluginInPackageBundledRoots({
		packageRoot: devSourceRoot,
		rootDir: resolveUserPath(params.rootDir, params.env)
	})) return false;
	if (resolveOpenClawDevSourceRoot(params.env)) return true;
	const hostRoot = pluginCacheRealpathSync(devSourceRoot);
	const pluginRoot = pluginCacheRealpathSync(resolveUserPath(params.rootDir, params.env));
	return Boolean(hostRoot && pluginRoot && ["dist", "dist-runtime"].some((tree) => isPathInside$2(path.join(hostRoot, tree, "extensions"), pluginRoot)));
}
//#endregion
//#region src/plugins/roots.ts
function resolvePluginSourceRoots(params) {
	const env = params.env ?? process.env;
	const workspaceRoot = params.workspaceDir ? resolveUserPath(params.workspaceDir, env) : void 0;
	return {
		stock: resolveBundledPluginsDir(env),
		global: resolveDefaultPluginExtensionsDir(env),
		workspace: workspaceRoot ? path.join(workspaceRoot, ".openclaw", "extensions") : void 0
	};
}
function resolvePluginCacheInputs(params) {
	const env = params.env ?? process.env;
	return {
		roots: resolvePluginSourceRoots({
			workspaceDir: params.workspaceDir,
			env
		}),
		loadPaths: normalizeStringEntries((params.loadPaths ?? []).filter((entry) => typeof entry === "string")).map((entry) => resolveUserPath(entry, env)),
		devSourceRoot: resolveOpenClawDevSourceRoot(env)
	};
}
//#endregion
//#region src/plugins/status-dependencies-core.ts
function normalizeDependencyMap(raw) {
	if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
	const normalized = {};
	for (const [name, spec] of Object.entries(raw)) {
		const normalizedName = name.trim();
		if (!normalizedName || typeof spec !== "string" || !spec.trim()) continue;
		normalized[normalizedName] = spec.trim();
	}
	return normalized;
}
/** Normalizes raw package dependency maps into sorted plugin dependency specs. */
function normalizePluginDependencySpecs(params) {
	const dependencies = normalizeDependencyMap(params.dependencies);
	const optionalDependencies = normalizeDependencyMap(params.optionalDependencies);
	for (const name of Object.keys(optionalDependencies)) delete dependencies[name];
	return {
		dependencies,
		optionalDependencies
	};
}
//#endregion
//#region src/plugins/discovery.ts
/** Discovers plugin candidates from bundled, workspace, global, package, and bundle roots. */
const EXTENSION_EXTS = /* @__PURE__ */ new Set([
	".ts",
	".js",
	".mts",
	".cts",
	".mjs",
	".cjs"
]);
const SCANNED_DIRECTORY_IGNORE_NAMES = /* @__PURE__ */ new Set([
	".git",
	".hg",
	".svn",
	".turbo",
	".yarn",
	".yarn-cache",
	"build",
	"coverage",
	"dist",
	"node_modules"
]);
function currentUid(overrideUid) {
	if (overrideUid !== void 0) return overrideUid;
	if (process.platform === "win32") return null;
	if (typeof process.getuid !== "function") return null;
	return process.getuid();
}
function checkSourceEscapesRoot(params) {
	const sourceRealPath = pluginCacheRealpathSync(params.source);
	const rootRealPath = pluginCacheRealpathSync(params.rootDir);
	if (!sourceRealPath || !rootRealPath) return null;
	if (isPathInside$1(rootRealPath, sourceRealPath)) return null;
	return {
		reason: "source_escapes_root",
		sourcePath: params.source,
		targetPath: params.source,
		sourceRealPath,
		rootRealPath
	};
}
function checkPathStatAndPermissions(params) {
	if (process.platform === "win32") return null;
	const pathsToCheck = [params.rootDir, params.source];
	const seen = /* @__PURE__ */ new Set();
	for (const targetPath of pathsToCheck) {
		const normalized = path.resolve(targetPath);
		if (seen.has(normalized)) continue;
		seen.add(normalized);
		let stat = pluginCacheStatSync(targetPath);
		if (!stat) return {
			reason: "path_stat_failed",
			sourcePath: params.source,
			targetPath
		};
		let modeBits = stat.mode & 511;
		if ((modeBits & 2) !== 0 && params.origin === "bundled") try {
			fsSync.chmodSync(targetPath, modeBits & -19);
			const repairedStat = refreshPluginCacheStat(targetPath);
			if (!repairedStat) return {
				reason: "path_stat_failed",
				sourcePath: params.source,
				targetPath
			};
			stat = repairedStat;
			modeBits = repairedStat.mode & 511;
		} catch {}
		if ((modeBits & 2) !== 0) return {
			reason: "path_world_writable",
			sourcePath: params.source,
			targetPath,
			modeBits
		};
		if (params.origin !== "bundled" && params.uid !== null && typeof stat.uid === "number" && stat.uid !== params.uid && stat.uid !== 0) return {
			reason: "path_suspicious_ownership",
			sourcePath: params.source,
			targetPath,
			foundUid: stat.uid,
			expectedUid: params.uid
		};
	}
	return null;
}
function formatCandidateBlockMessage(issue) {
	if (issue.reason === "source_escapes_root") return `blocked plugin candidate: source escapes plugin root (${issue.sourcePath} -> ${issue.sourceRealPath}; root=${issue.rootRealPath})`;
	if (issue.reason === "path_stat_failed") return `blocked plugin candidate: cannot stat path (${issue.targetPath})`;
	if (issue.reason === "path_world_writable") return `blocked plugin candidate: world-writable path (${issue.targetPath}, mode=${formatPosixMode(issue.modeBits ?? 0)})`;
	return `blocked plugin candidate: suspicious ownership (${issue.targetPath}, uid=${issue.foundUid}, expected uid=${issue.expectedUid} or root)`;
}
function isUnsafePluginCandidate(params) {
	const issue = checkSourceEscapesRoot(params) ?? checkPathStatAndPermissions({
		...params,
		uid: currentUid(params.ownershipUid)
	});
	if (!issue) return false;
	params.diagnostics.push({
		level: "warn",
		...params.pluginId ? { pluginId: params.pluginId } : {},
		source: issue.targetPath,
		message: formatCandidateBlockMessage(issue)
	});
	return true;
}
function isExtensionFile(filePath) {
	const ext = path.extname(filePath);
	if (!EXTENSION_EXTS.has(ext)) return false;
	if (/\.d\.[cm]?ts$/.test(filePath)) return false;
	return !normalizeLowercaseStringOrEmpty(path.basename(filePath)).includes(".test.");
}
function shouldIgnoreScannedDirectory(dirName) {
	const normalized = normalizeLowercaseStringOrEmpty(dirName);
	return !normalized || SCANNED_DIRECTORY_IGNORE_NAMES.has(normalized) || normalized.endsWith(".bak") || normalized.includes(".backup-") || normalized.includes(".disabled");
}
function resolveScannedEntryType(entry, fullPath) {
	if (entry.isFile()) return "file";
	if (entry.isDirectory()) return "directory";
	if (!entry.isSymbolicLink()) return null;
	const stat = pluginCacheStatSync(fullPath);
	if (!stat) return null;
	if (stat.isFile()) return "file";
	if (stat.isDirectory()) return "directory";
	return null;
}
function resolvesToSameDirectory(left, right) {
	if (!left || !right) return false;
	const leftRealPath = pluginCacheRealpathSync(left);
	const rightRealPath = pluginCacheRealpathSync(right);
	if (leftRealPath && rightRealPath) return leftRealPath === rightRealPath;
	return path.resolve(left) === path.resolve(right);
}
function mergeCandidateInstallOwner(existing, candidateOwner, candidateOwnerAmbiguous) {
	const existingOwner = resolvePluginCandidateInstallOwner(existing);
	const ownerConflict = existingOwner && candidateOwner && existingOwner !== candidateOwner;
	if (isPluginCandidateInstallOwnerAmbiguous(existing) || candidateOwnerAmbiguous || ownerConflict) recordPluginCandidateInstallOwner(existing, void 0, true);
	else if (candidateOwner) recordPluginCandidateInstallOwner(existing, candidateOwner);
}
function prepareInstalledPluginPaths(installRecords, env, diagnostics) {
	const byPath = /* @__PURE__ */ new Map();
	const installedPluginDirKeys = /* @__PURE__ */ new Set();
	const managedPluginDirs = /* @__PURE__ */ new Set();
	const resolveRecordPath = (rawPath) => typeof rawPath === "string" && rawPath.trim() ? resolveUserPath(rawPath, env) : void 0;
	for (const [installOwner, record] of Object.entries(installRecords ?? {})) {
		const installPath = resolveRecordPath(record.installPath);
		const sourcePath = resolveRecordPath(record.sourcePath);
		for (const recordedPath of [installPath, sourcePath]) if (recordedPath && pluginCacheExistsSync(recordedPath)) {
			const key = resolveManagedPluginDirKey(recordedPath);
			if (key) managedPluginDirs.add(key);
		}
		const resolved = installPath ?? sourcePath;
		if (!resolved || !pluginCacheExistsSync(resolved)) continue;
		const pathKey = pluginCacheRealpathSync(resolved) ?? path.resolve(resolved);
		const requireBuiltRuntimeEntry = !(record.source === "path" && installPath && sourcePath && resolvesToSameDirectory(installPath, sourcePath));
		const existing = byPath.get(pathKey);
		if (existing) {
			existing.requireBuiltRuntimeEntry ||= requireBuiltRuntimeEntry;
			if (existing.installOwner !== installOwner) {
				delete existing.installOwner;
				existing.installOwnerAmbiguous = true;
				diagnostics.push({
					level: "error",
					source: resolved,
					message: "multiple plugin install records claim the same package path; refresh or reinstall the package before using managed lifecycle actions"
				});
			}
		} else {
			byPath.set(pathKey, {
				path: resolved,
				requireBuiltRuntimeEntry,
				installOwner
			});
			const dirKey = resolveManagedPluginDirKey(resolved);
			if (dirKey) installedPluginDirKeys.add(dirKey);
		}
	}
	return {
		installedPaths: [...byPath.values()],
		installedPluginDirKeys,
		managedPluginDirs
	};
}
function resolveManagedPluginDirKey(installedPath) {
	const stat = pluginCacheStatSync(installedPath);
	if (!stat) return null;
	const pluginDir = stat.isFile() ? path.dirname(installedPath) : installedPath;
	return pluginCacheRealpathSync(pluginDir) ?? path.resolve(pluginDir);
}
function isManagedPluginDir(params) {
	if (!params.managedPluginDirs || params.managedPluginDirs.size === 0) return false;
	const key = params.realpath ?? pluginCacheRealpathSync(params.dir) ?? path.resolve(params.dir);
	return params.managedPluginDirs.has(key);
}
function readPackageManifest(dir, rejectHardlinks = true, rootRealPath) {
	const file = readPluginCacheFile({
		rootDir: dir,
		...rootRealPath !== void 0 ? { rootRealPath } : {},
		relativePath: "package.json",
		rejectHardlinks
	});
	if (!file.ok) return null;
	const parsed = parsePluginCacheJson(file);
	return parsed.ok && isRecord(parsed.value) ? parsed.value : null;
}
function deriveIdHint(params) {
	const base = path.basename(params.filePath, path.extname(params.filePath));
	const pluginId = normalizeOptionalString(params.manifestId) ?? derivePackagePluginIdHint(params.packageName) ?? params.fallbackId;
	return params.hasMultipleExtensions ? `${pluginId}/${base}` : pluginId;
}
function derivePackagePluginIdHint(packageName) {
	const rawPackageName = normalizeOptionalString(packageName);
	if (!rawPackageName) return;
	const unscoped = rawPackageName.slice(rawPackageName.lastIndexOf("/") + 1);
	for (const suffix of ["-provider", "-plugin"]) if (unscoped.endsWith(suffix) && unscoped.length > suffix.length) return unscoped.slice(0, -suffix.length);
	return normalizeOptionalString(unscoped);
}
function resolvePluginPackageEntries(params) {
	const entryIdSources = groupPluginRecords(resolvePackageRuntimeExtensions(params), (entry) => deriveIdHint({
		filePath: entry.source,
		manifestId: params.manifestId,
		packageName: params.manifest?.name,
		fallbackId: path.basename(params.packageDir),
		hasMultipleExtensions: params.extensions.length > 1
	}));
	const entries = [];
	for (const [idHint, sources] of entryIdSources) {
		if (params.extensions.length > 1 && sources.length > 1) {
			params.diagnostics.push({
				level: "error",
				pluginId: idHint,
				source: params.sourceLabel,
				message: `plugin package entries collide on derived id "${idHint}" (${sources.map((entry) => path.relative(params.packageDir, entry.source)).join(", ")}); rename the entry files to unique basenames`
			});
			continue;
		}
		for (const entry of sources) entries.push({
			...entry,
			idHint
		});
	}
	return entries;
}
function pushInvalidPackageExtensionDiagnostic(params) {
	if (params.resolution.status !== "invalid" && params.resolution.status !== "empty") return false;
	params.diagnostics.push({
		level: "error",
		source: params.source,
		message: params.resolution.status === "invalid" ? params.resolution.error : "package.json openclaw.extensions is empty",
		...params.pluginId ? { pluginId: params.pluginId } : {}
	});
	return true;
}
function addLegacyNpmDeclarationDiagnostic(params) {
	const declaration = readLegacyNpmPluginDeclaration(params.pluginDir);
	if (!declaration) return false;
	params.diagnostics.push({
		level: "warn",
		pluginId: declaration.pluginId,
		source: declaration.source,
		message: `legacy npm plugin declaration ignored for "${declaration.pluginId}"; run "openclaw doctor --fix" to install ${declaration.npmSpec} into the managed plugin root`
	});
	return true;
}
function isSourceCheckoutExtensionsDir(extensionsDir) {
	const packageRoot = path.dirname(extensionsDir);
	return pluginCacheExistsSync(path.join(packageRoot, ".git")) && pluginCacheExistsSync(path.join(packageRoot, "pnpm-workspace.yaml")) && pluginCacheExistsSync(path.join(packageRoot, "src")) && hasUsableBundledPluginTree(extensionsDir);
}
function resolveBundledSourceCheckoutExtensionsDir(bundledRoot) {
	if (!bundledRoot) return;
	const legacyRoot = buildLegacyBundledRootPath(bundledRoot);
	if (!legacyRoot || !isPluginInPackageBundledRoots({
		rootDir: legacyRoot,
		packageRoot: path.dirname(legacyRoot)
	}) || !isSourceCheckoutExtensionsDir(legacyRoot)) return;
	return legacyRoot;
}
function isHostBundledPluginRoot(dir, env) {
	const bundledRoot = resolveBundledPluginsDir(env);
	const realDir = pluginCacheRealpathSync(dir) ?? path.resolve(dir);
	return [bundledRoot, resolveBundledSourceCheckoutExtensionsDir(bundledRoot)].some((root) => root && isPathInside$1(pluginCacheRealpathSync(root) ?? path.resolve(root), realDir));
}
function readChildDirectoryNames(dir) {
	if (!dir || !pluginCacheExistsSync(dir)) return /* @__PURE__ */ new Set();
	try {
		return new Set(sortUniqueStrings(readPluginCacheDirectory(dir).filter((entry) => entry.isDirectory()).map((entry) => entry.name)));
	} catch {
		return /* @__PURE__ */ new Set();
	}
}
function readBundledDistOptOutDirectoryNames(sourceExtensionsDir) {
	const names = /* @__PURE__ */ new Set();
	if (!sourceExtensionsDir) return names;
	for (const name of readChildDirectoryNames(sourceExtensionsDir)) if (getPackageManifestMetadata(readPackageManifest(path.join(sourceExtensionsDir, name), false) ?? void 0)?.build?.bundledDist === false) names.add(name);
	return names;
}
function createPluginScanner(env, ownershipUid) {
	const result = {
		candidates: [],
		diagnostics: []
	};
	const { candidates, diagnostics } = result;
	const attemptedSources = /* @__PURE__ */ new Map();
	function addCandidate(params) {
		const resolved = path.resolve(params.source);
		if (attemptedSources.has(resolved)) {
			const existing = attemptedSources.get(resolved);
			if (existing) mergeCandidateInstallOwner(existing, params.installOwner, params.installOwnerAmbiguous === true);
			return;
		}
		const resolvedRoot = pluginCacheRealpathSync(params.rootDir) ?? path.resolve(params.rootDir);
		if (isUnsafePluginCandidate({
			source: resolved,
			rootDir: resolvedRoot,
			origin: params.origin,
			pluginId: params.idHint,
			diagnostics,
			ownershipUid
		})) {
			attemptedSources.set(resolved, void 0);
			return;
		}
		const manifest = params.manifest ?? null;
		const packageManifest = getPackageManifestMetadata(manifest ?? void 0);
		const packageDependencies = normalizePluginDependencySpecs({
			dependencies: manifest?.dependencies,
			optionalDependencies: manifest?.optionalDependencies
		});
		const candidate = {
			idHint: params.idHint,
			...params.effectivePluginId ? { effectivePluginId: params.effectivePluginId } : {},
			...params.diagnosticIdHint && params.diagnosticIdHint !== params.idHint ? { diagnosticIdHint: params.diagnosticIdHint } : {},
			source: resolved,
			setupSource: params.setupSource,
			rootDir: resolvedRoot,
			origin: params.origin,
			format: params.format ?? "openclaw",
			bundleFormat: params.bundleFormat,
			workspaceDir: params.workspaceDir,
			packageName: normalizeOptionalString(manifest?.name),
			packageVersion: normalizeOptionalString(manifest?.version),
			packageDescription: normalizeOptionalString(manifest?.description),
			packageDir: params.packageDir,
			packageManifest,
			packageDependencies: packageDependencies.dependencies,
			packageOptionalDependencies: packageDependencies.optionalDependencies,
			rawPackageManifest: manifest ?? void 0,
			bundledManifestId: params.bundledManifestId,
			bundledManifest: params.bundledManifest,
			bundledManifestPath: params.bundledManifestPath,
			...params.requiredPluginIds && params.requiredPluginIds.length > 0 ? { requiredPluginIds: params.requiredPluginIds } : {},
			...params.requiredPluginSource ? { requiredPluginSource: params.requiredPluginSource } : {}
		};
		recordPluginCandidateInstallOwner(candidate, params.installOwner, params.installOwnerAmbiguous === true);
		candidates.push(candidate);
		attemptedSources.set(resolved, candidate);
	}
	function discoverBundleInRoot(params) {
		const bundleFormat = detectBundleManifestFormat(params.rootDir);
		if (!bundleFormat) return false;
		const rootRealPath = pluginCacheRealpathSync(params.rootDir) ?? void 0;
		const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
			origin: params.origin,
			rootDir: params.rootDir,
			env
		});
		const bundleManifest = loadBundleManifest({
			rootDir: params.rootDir,
			...rootRealPath !== void 0 ? { rootRealPath } : {},
			bundleFormat,
			rejectHardlinks
		});
		if (!bundleManifest.ok) {
			diagnostics.push({
				level: "error",
				message: bundleManifest.error,
				source: bundleManifest.manifestPath
			});
			return false;
		}
		addCandidate({
			idHint: bundleManifest.manifest.id,
			source: params.rootDir,
			rootDir: params.rootDir,
			origin: params.origin,
			format: "bundle",
			bundleFormat,
			workspaceDir: params.workspaceDir,
			...params.installOwner ? { installOwner: params.installOwner } : {},
			...params.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {},
			manifest: params.manifest,
			packageDir: params.rootDir,
			bundledManifestId: bundleManifest.manifest.id,
			bundledManifestPath: bundleManifest.manifestPath
		});
		return true;
	}
	function discoverPluginDirectory(params) {
		const { dir, rootRealPath } = params;
		const requireBuiltRuntimeEntry = params.requireBuiltRuntimeEntry ?? isManagedPluginDir({
			dir,
			realpath: rootRealPath,
			managedPluginDirs: params.managedPluginDirs
		});
		const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
			origin: params.origin,
			rootDir: dir,
			env
		});
		const manifest = readPackageManifest(dir, rejectHardlinks, rootRealPath);
		const packageMetadata = getPackageManifestMetadata(manifest ?? void 0);
		const manifestResult = loadPluginManifest(dir, rejectHardlinks, rootRealPath);
		const candidateManifest = manifestResult.ok ? manifestResult : void 0;
		const manifestId = candidateManifest?.manifest.id;
		const pluginIdHint = normalizeOptionalString(manifestId) ?? normalizeOptionalString(packageMetadata?.plugin?.id) ?? normalizeOptionalString(packageMetadata?.channel?.id) ?? derivePackagePluginIdHint(manifest?.name) ?? path.basename(dir);
		if (shouldSkipIncompatiblePackagePluginApi({
			origin: params.origin,
			packageManifest: packageMetadata,
			pluginId: pluginIdHint,
			packageDir: dir,
			env,
			diagnostics
		})) return true;
		const extensionResolution = resolvePackageExtensionEntries(manifest ?? void 0);
		if (pushInvalidPackageExtensionDiagnostic({
			resolution: extensionResolution,
			source: dir,
			pluginId: pluginIdHint,
			diagnostics
		})) return true;
		const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
		const setupSource = resolvePackageSetupSource({
			packageDir: dir,
			...rootRealPath !== void 0 ? { packageRootRealPath: rootRealPath } : {},
			manifest,
			pluginIdHint,
			origin: params.origin,
			requireBuiltRuntimeEntry,
			sourceLabel: dir,
			diagnostics,
			rejectHardlinks
		});
		const addPackageCandidate = (source, idHint, effectivePluginId) => {
			addCandidate({
				idHint,
				...effectivePluginId ? { effectivePluginId } : {},
				diagnosticIdHint: pluginIdHint,
				source,
				...setupSource ? { setupSource } : {},
				rootDir: dir,
				origin: params.origin,
				workspaceDir: params.workspaceDir,
				...params.installOwner ? { installOwner: params.installOwner } : {},
				...params.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {},
				manifest,
				packageDir: dir,
				requiredPluginIds: candidateManifest?.manifest.requiresPlugins,
				requiredPluginSource: candidateManifest?.manifestPath
			});
		};
		if (extensions.length > 0) {
			const entries = resolvePluginPackageEntries({
				packageDir: dir,
				...rootRealPath !== void 0 ? { packageRootRealPath: rootRealPath } : {},
				manifest,
				extensions,
				manifestId: manifestId ?? normalizeOptionalString(packageMetadata?.plugin?.id),
				origin: params.origin,
				pluginIdHint,
				requireBuiltRuntimeEntry,
				sourceLabel: dir,
				diagnostics,
				rejectHardlinks
			});
			for (const entry of entries) addPackageCandidate(entry.source, entry.idHint, extensions.length > 1 ? entry.idHint : void 0);
			return true;
		}
		if (discoverBundleInRoot({
			rootDir: dir,
			origin: params.origin,
			workspaceDir: params.workspaceDir,
			...params.installOwner ? { installOwner: params.installOwner } : {},
			...params.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {},
			manifest
		})) return true;
		const indexFile = [...DEFAULT_PLUGIN_ENTRY_CANDIDATES].map((candidate) => path.join(dir, candidate)).find((candidate) => pluginCacheExistsSync(candidate));
		if (indexFile && isExtensionFile(indexFile)) {
			addPackageCandidate(indexFile, manifestId ?? path.basename(dir));
			return true;
		}
		return addLegacyNpmDeclarationDiagnostic({
			pluginDir: dir,
			diagnostics
		});
	}
	function discoverInDirectory(params) {
		if (!pluginCacheExistsSync(params.dir)) return;
		let entries;
		try {
			entries = readPluginCacheDirectory(params.dir).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
		} catch (err) {
			diagnostics.push(params.origin === "config" ? pluginPathFailureDiagnostic(params.dir, params.origin, err) : {
				level: "warn",
				message: `failed to read extensions dir: ${params.dir} (${String(err)})`,
				source: params.dir
			});
			return;
		}
		for (const entry of entries) {
			const fullPath = path.join(params.dir, entry.name);
			const entryType = resolveScannedEntryType(entry, fullPath);
			if (entryType === "file") {
				if (!(params.scanFiles ?? params.origin === "bundled") || !isExtensionFile(fullPath)) continue;
				addCandidate({
					idHint: path.basename(entry.name, path.extname(entry.name)),
					source: fullPath,
					rootDir: path.dirname(fullPath),
					origin: params.origin,
					workspaceDir: params.workspaceDir,
					...params.installOwner ? { installOwner: params.installOwner } : {},
					...params.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {}
				});
				continue;
			}
			if (entryType !== "directory") continue;
			if (params.skipDirectories?.has(entry.name)) continue;
			if (shouldIgnoreScannedDirectory(entry.name)) continue;
			const fullPathRealPath = pluginCacheRealpathSync(fullPath) ?? void 0;
			const fullPathDirKey = fullPathRealPath ?? path.resolve(fullPath);
			if (params.origin === "bundled" && !isPathInside$1(pluginCacheRealpathSync(params.dir) ?? path.resolve(params.dir), fullPathDirKey)) {
				diagnostics.push({
					level: "error",
					source: fullPath,
					message: `blocked plugin candidate: package directory escapes bundled root (${fullPath})`
				});
				continue;
			}
			if (params.skipRootDirKeys?.has(fullPathDirKey)) continue;
			discoverPluginDirectory({
				...params,
				dir: fullPath,
				rootRealPath: fullPathRealPath
			});
		}
	}
	function discoverFromPath(params) {
		const resolved = resolveUserPath(params.rawPath, env);
		const stat = inspectPluginLoadPath(resolved, params.origin, diagnostics);
		if (!stat) return;
		const origin = (params.origin === "config" || params.origin === "global") && isHostBundledPluginRoot(stat.isFile() ? path.dirname(resolved) : resolved, env) ? "bundled" : params.origin;
		if (stat.isFile()) {
			if (!isExtensionFile(resolved)) {
				diagnostics.push({
					level: "error",
					message: `plugin path is not a supported file: ${resolved}`,
					source: resolved
				});
				return;
			}
			addCandidate({
				idHint: path.basename(resolved, path.extname(resolved)),
				source: resolved,
				rootDir: path.dirname(resolved),
				origin,
				workspaceDir: params.workspaceDir,
				...params.installOwner ? { installOwner: params.installOwner } : {},
				...params.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {}
			});
			return;
		}
		if (stat.isDirectory()) {
			if (discoverPluginDirectory({
				...params,
				origin,
				dir: resolved,
				rootRealPath: pluginCacheRealpathSync(resolved) ?? void 0
			})) return;
			discoverInDirectory({
				dir: resolved,
				origin,
				workspaceDir: params.workspaceDir,
				...params.scanFiles !== void 0 || params.origin === "config" ? { scanFiles: params.scanFiles ?? true } : {},
				...params.requireBuiltRuntimeEntry !== void 0 ? { requireBuiltRuntimeEntry: params.requireBuiltRuntimeEntry } : {},
				...params.managedPluginDirs ? { managedPluginDirs: params.managedPluginDirs } : {},
				...params.skipRootDirKeys ? { skipRootDirKeys: params.skipRootDirKeys } : {}
			});
		}
	}
	function discoverConfiguredPaths(loadPaths, workspaceDir) {
		const firstConfigured = candidates.length;
		for (const loadPath of loadPaths) if (typeof loadPath === "string" && loadPath.trim()) discoverFromPath({
			rawPath: loadPath.trim(),
			origin: "config",
			workspaceDir
		});
		for (const candidate of candidates.slice(firstConfigured)) candidate.configSelected = true;
	}
	function finish() {
		const candidatesBySource = /* @__PURE__ */ new Map();
		const seenDiagnostics = /* @__PURE__ */ new Set();
		const uniqueDiagnostics = [];
		for (const candidate of candidates) {
			const key = pluginCacheRealpathSync(candidate.source) ?? path.resolve(candidate.source);
			const existing = candidatesBySource.get(key);
			if (existing) {
				const retained = candidate.origin === "bundled" ? candidate : existing;
				const duplicate = retained === candidate ? existing : candidate;
				if (duplicate.origin === "config" || duplicate.configSelected) retained.configSelected = true;
				if (duplicate.sourcePreferred) retained.sourcePreferred = true;
				mergeCandidateInstallOwner(retained, resolvePluginCandidateInstallOwner(duplicate), isPluginCandidateInstallOwnerAmbiguous(duplicate));
				candidatesBySource.set(key, retained);
				continue;
			}
			candidatesBySource.set(key, candidate);
		}
		for (const diagnostic of diagnostics) {
			const key = [
				diagnostic.level,
				diagnostic.pluginId ?? "",
				diagnostic.source ?? "",
				diagnostic.message
			].join("\0");
			if (seenDiagnostics.has(key)) continue;
			seenDiagnostics.add(key);
			uniqueDiagnostics.push(diagnostic);
		}
		result.candidates = [...candidatesBySource.values()];
		result.diagnostics = uniqueDiagnostics;
		return result;
	}
	return {
		result,
		discoverConfiguredPaths,
		discoverFromPath,
		discoverInDirectory,
		finish
	};
}
function discoveryPolicy(env, ownershipUid, bundledRoot) {
	return {
		ownershipUid: currentUid(ownershipUid),
		compatibilityHostVersion: resolveCompatibilityHostVersion(env),
		bundledRoot: bundledRoot ?? resolveBundledPluginsDir(env) ?? "",
		nix: resolveIsNixMode(env),
		sourceOverlaysDisabled: env.OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS ?? "",
		home: env.OPENCLAW_HOME ?? "",
		userHome: env.HOME ?? "",
		userProfile: env.USERPROFILE ?? "",
		cwd: process.cwd()
	};
}
function discoverSharedPluginRoots(params) {
	const { roots, env } = params;
	const cache = getPluginCache().metadata.sharedDiscovery;
	const key = hashStableJson({
		roots,
		installRecords: Object.entries(params.installRecords ?? {}),
		rootScope: params.rootScope ?? "all",
		policy: discoveryPolicy(env, params.ownershipUid, roots.stock)
	});
	const cached = cache.get(key);
	if (cached) return cached;
	const scanner = createPluginScanner(env, params.ownershipUid);
	const { result, discoverInDirectory } = scanner;
	const workspaceCandidates = /* @__PURE__ */ new Set();
	const discoverWorkspacePath = (options) => {
		const first = result.candidates.length;
		scanner.discoverFromPath(options);
		for (const candidate of result.candidates.slice(first)) workspaceCandidates.add(candidate);
	};
	tracePluginLifecyclePhase("discovery scan", () => {
		for (const sourceOverlayDir of listBundledSourceOverlayDirs({
			bundledRoot: roots.stock,
			env
		})) {
			const firstOverlay = result.candidates.length;
			discoverWorkspacePath({
				rawPath: sourceOverlayDir,
				origin: "bundled"
			});
			for (const candidate of result.candidates.slice(firstOverlay)) candidate.sourcePreferred = true;
			result.diagnostics.push({
				level: "warn",
				source: sourceOverlayDir,
				message: "using bind-mounted bundled plugin source overlay; this source overrides the packaged dist bundle for the same plugin id"
			});
		}
		const sourceCheckoutDependencyDiagnostic = resolveSourceCheckoutDependencyDiagnostic(env);
		if (sourceCheckoutDependencyDiagnostic) result.diagnostics.push({
			level: "warn",
			source: sourceCheckoutDependencyDiagnostic.source,
			message: sourceCheckoutDependencyDiagnostic.message
		});
		const sourceCheckoutExtensionsDir = resolveBundledSourceCheckoutExtensionsDir(roots.stock);
		const bundledDistOptOutDirectories = readBundledDistOptOutDirectoryNames(sourceCheckoutExtensionsDir);
		if (sourceCheckoutExtensionsDir) for (const dirName of bundledDistOptOutDirectories) discoverWorkspacePath({
			rawPath: path.join(sourceCheckoutExtensionsDir, dirName),
			origin: "bundled"
		});
		if (roots.stock) discoverInDirectory({
			dir: roots.stock,
			origin: "bundled",
			skipDirectories: bundledDistOptOutDirectories
		});
		const sourceCheckoutMatchesBundledRoot = resolvesToSameDirectory(sourceCheckoutExtensionsDir, roots.stock);
		if (sourceCheckoutExtensionsDir && !sourceCheckoutMatchesBundledRoot) discoverInDirectory({
			dir: sourceCheckoutExtensionsDir,
			origin: "bundled",
			skipDirectories: readChildDirectoryNames(roots.stock)
		});
		if (params.rootScope !== "bundled") {
			const { installedPaths, installedPluginDirKeys, managedPluginDirs } = prepareInstalledPluginPaths(params.installRecords, env, result.diagnostics);
			for (const installedPath of installedPaths) discoverWorkspacePath({
				rawPath: installedPath.path,
				origin: "global",
				...installedPath.installOwner ? { installOwner: installedPath.installOwner } : {},
				...installedPath.installOwnerAmbiguous ? { installOwnerAmbiguous: true } : {},
				requireBuiltRuntimeEntry: installedPath.requireBuiltRuntimeEntry,
				managedPluginDirs,
				scanFiles: true
			});
			discoverInDirectory({
				dir: roots.global,
				origin: "global",
				managedPluginDirs,
				skipRootDirKeys: installedPluginDirKeys
			});
		}
	}, { scope: "shared" });
	const shared = {
		candidates: result.candidates.map((candidate) => ({
			candidate,
			usesWorkspace: workspaceCandidates.has(candidate)
		})),
		diagnostics: result.diagnostics
	};
	cache.set(key, shared);
	return shared;
}
function discoverOpenClawPlugins(params) {
	const env = params.env ?? process.env;
	const workspaceDir = normalizeOptionalString(params.workspaceDir);
	const workspaceRoot = workspaceDir ? resolveUserPath(workspaceDir, env) : void 0;
	const defaultRoots = resolvePluginSourceRoots({
		workspaceDir: workspaceRoot,
		env
	});
	const roots = params.bundledRoot ? {
		...defaultRoots,
		stock: path.resolve(params.bundledRoot)
	} : defaultRoots;
	const cache = getPluginCache().metadata.discovery;
	const key = hashStableJson({
		phase: "all",
		roots,
		workspaceDir,
		loadPaths: params.extraPaths ?? [],
		installRecords: Object.entries(params.installRecords ?? {}),
		rootScope: params.rootScope ?? "all",
		policy: discoveryPolicy(env, params.ownershipUid, roots.stock)
	});
	const cached = cache.get(key);
	if (cached) return cached;
	const scanner = createPluginScanner(env, params.ownershipUid);
	const { result, discoverInDirectory } = scanner;
	if (params.rootScope !== "bundled") tracePluginLifecyclePhase("discovery scan", () => {
		scanner.discoverConfiguredPaths(params.extraPaths ?? [], workspaceDir);
		const workspaceMatchesBundledRoot = resolvesToSameDirectory(workspaceRoot, roots.stock);
		if (roots.workspace && workspaceRoot && !workspaceMatchesBundledRoot) discoverInDirectory({
			dir: roots.workspace,
			origin: "workspace",
			workspaceDir: workspaceRoot
		});
	}, {
		scope: "scoped",
		extraPathCount: params.extraPaths?.length ?? 0
	});
	const shared = discoverSharedPluginRoots({
		roots: {
			stock: roots.stock,
			global: roots.global
		},
		installRecords: params.installRecords,
		ownershipUid: params.ownershipUid,
		env,
		rootScope: params.rootScope
	});
	for (const { candidate, usesWorkspace } of shared.candidates) result.candidates.push({
		...candidate,
		...usesWorkspace ? { workspaceDir } : {}
	});
	result.diagnostics.push(...shared.diagnostics);
	scanner.finish();
	addMissingRequiredPluginDiagnostics(result, { env });
	cache.set(key, result);
	return result;
}
//#endregion
//#region src/plugins/installed-plugin-index-facts.ts
/** Package facts share the existing generation; mutable management inputs stay uncached. */
function getInstalledPluginIndexFacts(index) {
	const entries = getPluginCache().metadata.indexFacts;
	const existing = entries.get(index);
	if (existing) return existing;
	if (!isDeeplyFrozenPlainData(index)) return;
	const facts = {};
	entries.set(index, facts);
	return facts;
}
//#endregion
//#region src/config/zod-schema.installs.ts
const InstallSourceSchema = union([
	literal("npm"),
	literal("archive"),
	literal("path"),
	literal("clawhub"),
	literal("git")
]);
const PluginInstallSourceSchema = union([InstallSourceSchema, literal("marketplace")]);
/** Zod object shape for persisted generic install records. */
const InstallRecordShape = {
	source: InstallSourceSchema,
	spec: string().optional(),
	sourcePath: string().optional(),
	installPath: string().optional(),
	version: string().optional(),
	resolvedName: string().optional(),
	resolvedVersion: string().optional(),
	resolvedSpec: string().optional(),
	integrity: string().optional(),
	shasum: string().optional(),
	resolvedAt: string().optional(),
	installedAt: string().optional(),
	clawhubUrl: string().optional(),
	clawhubPackage: string().optional(),
	clawhubFamily: union([literal("code-plugin"), literal("bundle-plugin")]).optional(),
	clawhubChannel: union([
		literal("official"),
		literal("community"),
		literal("private")
	]).optional(),
	clawhubTrustDisposition: union([
		literal("clean"),
		literal("review-recommended"),
		literal("review-required"),
		literal("blocked")
	]).optional(),
	clawhubTrustScanStatus: string().optional(),
	clawhubTrustModerationState: string().optional(),
	clawhubTrustReasons: array(string()).optional(),
	clawhubTrustPending: boolean().optional(),
	clawhubTrustStale: boolean().optional(),
	clawhubTrustCheckedAt: string().optional(),
	clawhubTrustAcknowledgedAt: string().optional(),
	artifactKind: union([literal("legacy-zip"), literal("npm-pack")]).optional(),
	artifactFormat: union([literal("zip"), literal("tgz")]).optional(),
	npmIntegrity: string().optional(),
	npmShasum: string().optional(),
	npmTarballName: string().optional(),
	clawpackSha256: string().optional(),
	clawpackSpecVersion: number().int().nonnegative().optional(),
	clawpackManifestSha256: string().optional(),
	clawpackSize: number().int().nonnegative().optional(),
	gitUrl: string().optional(),
	gitRef: string().optional(),
	gitCommit: string().optional()
};
object(InstallRecordShape);
const PluginInstallRecordShape = {
	...InstallRecordShape,
	source: PluginInstallSourceSchema,
	marketplaceName: string().optional(),
	marketplaceSource: string().optional(),
	marketplacePlugin: string().optional(),
	acceptedSurface: object({
		channels: array(string().min(1)),
		providers: array(string().min(1)),
		tools: array(string().min(1)),
		contracts: array(string().min(1)),
		hooks: array(string().min(1)),
		mcpServers: array(string().min(1)),
		cliCommands: array(string().min(1)),
		cliBackends: array(string().min(1)),
		skills: array(string().min(1)),
		dangerousConfigFlags: array(string().min(1))
	}).strict().optional(),
	acceptedSurfaceHash: string().optional(),
	acceptedSurfaceAt: string().optional(),
	acceptedSurfaceIntegrity: string().optional()
};
//#endregion
//#region src/config/plugin-install-record-map.ts
const PluginInstallRecordSchema = object(PluginInstallRecordShape).passthrough();
const NORMALIZED_STRING_FIELDS = [
	"spec",
	"sourcePath",
	"installPath",
	"version",
	"resolvedName",
	"resolvedVersion",
	"resolvedSpec",
	"integrity",
	"shasum",
	"resolvedAt",
	"installedAt",
	"clawhubUrl",
	"clawhubPackage",
	"clawhubTrustScanStatus",
	"clawhubTrustModerationState",
	"clawhubTrustCheckedAt",
	"clawhubTrustAcknowledgedAt",
	"npmIntegrity",
	"npmShasum",
	"npmTarballName",
	"clawpackSha256",
	"clawpackManifestSha256",
	"gitUrl",
	"gitRef",
	"gitCommit",
	"marketplaceName",
	"marketplaceSource",
	"marketplacePlugin",
	"acceptedSurfaceHash",
	"acceptedSurfaceAt",
	"acceptedSurfaceIntegrity"
];
function createPluginInstallRecordMap() {
	return Object.create(null);
}
function setPluginInstallRecordMapEntry(records, pluginId, record) {
	Object.defineProperty(records, pluginId, {
		configurable: true,
		enumerable: true,
		value: record,
		writable: true
	});
}
function getPluginInstallRecordMapEntry(records, pluginId) {
	return records && Object.hasOwn(records, pluginId) ? records[pluginId] : void 0;
}
function copyPluginInstallRecordMap(records) {
	const copied = createPluginInstallRecordMap();
	for (const [pluginId, record] of Object.entries(records ?? {})) setPluginInstallRecordMapEntry(copied, pluginId, record);
	return copied;
}
function parsePluginInstallRecord(value) {
	const parsed = PluginInstallRecordSchema.safeParse(value);
	if (!parsed.success) return null;
	const record = parsed.data;
	for (const field of NORMALIZED_STRING_FIELDS) {
		const fieldValue = record[field];
		if (fieldValue === void 0) continue;
		const normalized = fieldValue.trim();
		if (normalized) record[field] = normalized;
		else delete record[field];
	}
	if (record.clawhubTrustReasons) {
		const reasons = record.clawhubTrustReasons.map((entry) => entry.trim()).filter(Boolean);
		if (reasons.length > 0) record.clawhubTrustReasons = reasons;
		else delete record.clawhubTrustReasons;
	}
	return record;
}
function parsePluginInstallRecordMap(value) {
	if (!isRecord(value)) return null;
	const records = createPluginInstallRecordMap();
	for (const [pluginId, rawRecord] of Object.entries(value)) {
		const record = parsePluginInstallRecord(rawRecord);
		if (!record) return null;
		setPluginInstallRecordMapEntry(records, pluginId, record);
	}
	return records;
}
function inspectPluginInstallRecordMap(value) {
	if (value === void 0) return { status: "missing" };
	const records = parsePluginInstallRecordMap(value);
	return records ? {
		status: "valid",
		records
	} : { status: "invalid" };
}
//#endregion
//#region src/plugins/doctor-contract-artifact.ts
/** Resolves the Doctor artifact shared by loading, index hashing, and freshness. */
const CONTRACT_API_EXTENSIONS = [
	".js",
	".mjs",
	".cjs",
	".ts",
	".mts",
	".cts"
];
const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
const ORDERED_EXTENSIONS = CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`) || CURRENT_MODULE_PATH.includes(`${path.sep}dist-runtime${path.sep}`) ? CONTRACT_API_EXTENSIONS : [...CONTRACT_API_EXTENSIONS.slice(3), ...CONTRACT_API_EXTENSIONS.slice(0, 3)];
["doctor-contract-api", "contract-api"].map((basename) => {
	const rootPaths = ORDERED_EXTENSIONS.map((extension) => `${basename}${extension}`);
	return {
		rootPaths,
		paths: rootPaths.flatMap((filename) => [filename, path.join("dist", filename)])
	};
});
//#endregion
//#region src/infra/clawhub-spec.ts
/** Parses explicit `clawhub:<name>[@version]` package specs for ClawHub installs. */
function parseClawHubPluginSpec(raw) {
	const trimmed = raw.trim();
	if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("clawhub:")) return null;
	const spec = trimmed.slice(8).trim();
	if (!spec) return null;
	const atIndex = spec.lastIndexOf("@");
	if (atIndex <= 0) return { name: spec };
	if (atIndex >= spec.length - 1) return null;
	const name = spec.slice(0, atIndex).trim();
	const version = spec.slice(atIndex + 1).trim();
	if (!name || !version) return null;
	return {
		name,
		version
	};
}
//#endregion
//#region src/plugins/manifest-install-owner.ts
const PLUGIN_MANIFEST_INSTALL_OWNER = Symbol.for("openclaw.pluginManifestInstallOwner");
function recordPluginManifestInstallOwner(record, installOwner, ambiguous = false) {
	if (!installOwner && !ambiguous) return record;
	Object.defineProperty(record, PLUGIN_MANIFEST_INSTALL_OWNER, {
		configurable: false,
		enumerable: true,
		value: ambiguous ? { ambiguous: true } : { installOwner }
	});
	return record;
}
path.join("plugins", "installs.json");
function resolveStoreEnv(options) {
	const env = options.env ?? process.env;
	if (options.stateDir) return {
		...env,
		OPENCLAW_STATE_DIR: options.stateDir
	};
	if (hasActivePluginInstallRoots()) return {
		...env,
		OPENCLAW_STATE_DIR: resolveActivePluginInstallRoots(env).stateDir
	};
	return env;
}
/** Resolves the canonical SQLite-backed installed plugin index path. */
function resolveInstalledPluginIndexStorePath(options = {}) {
	if (options.filePath) return options.filePath;
	return resolveOpenClawStateSqlitePath(resolveStoreEnv(options));
}
/** Resolves state database options for the installed plugin index store. */
function resolveInstalledPluginIndexStateDatabaseOptions(options = {}) {
	if (options.filePath) return {
		...options.env ? { env: options.env } : {},
		path: options.filePath
	};
	return { env: resolveStoreEnv(options) };
}
//#endregion
//#region src/plugins/installed-plugin-index-record-state.ts
/** Read failures must escape before either projection can authorize recovery or rebuilding. */
function readPersistedInstalledPluginIndexRowSync(options) {
	if (options.filePath?.endsWith(".json")) return;
	return readPluginMetadataStateRowSync("installed-index", resolveInstalledPluginIndexStateDatabaseOptions(options), options.artifactPreservingReadOnly);
}
/** Share the SQLite row while validating install records independently from index metadata. */
function getPersistedInstalledPluginIndexCacheEntry(options, readRow = () => readPersistedInstalledPluginIndexRowSync(options)) {
	const cache = getPluginCache().persistedInstalledIndex;
	const key = path.resolve(resolveInstalledPluginIndexStorePath(options));
	const current = cache.get(key);
	if (current && "value" in current) return current.value;
	const row = readRow();
	const entry = { state: row ? {
		status: "present",
		value: safeParseJson(row.value_json)
	} : { status: "missing" } };
	cache.set(key, { value: entry });
	return entry;
}
function inspectPersistedInstalledPluginIndexInstallRecords(entry) {
	if (!entry.records) {
		const state = entry.state;
		const records = (state.status === "present" ? state.value : void 0)?.index?.installRecords;
		entry.records = state.status === "missing" ? { status: "missing" } : records === void 0 ? { status: "invalid" } : inspectPluginInstallRecordMap(records);
	}
	return entry.records;
}
function inspectPersistedInstalledPluginIndexInstallRecordsSync(options = {}) {
	return inspectPersistedInstalledPluginIndexInstallRecords(getPersistedInstalledPluginIndexCacheEntry(options));
}
//#endregion
//#region src/plugins/npm-project-roots.ts
function sortPaths(paths) {
	return paths.toSorted((left, right) => left.localeCompare(right));
}
function readManagedProjects(projectDirs, npmDir) {
	return sortPaths(projectDirs).flatMap((projectRoot) => {
		const manifest = tryReadJsonSync(path.join(projectRoot, "package.json"));
		if (!isRecord(manifest) || !isRecord(manifest.dependencies)) return [];
		return Object.keys(manifest.dependencies).some((packageName) => validatePluginId(packageName) === null && isPluginNpmProjectDir({
			packageName,
			projectDir: projectRoot,
			npmDir
		})) ? [{
			projectRoot,
			manifest
		}] : [];
	});
}
/** Lists directory candidates; each consumer establishes its own package ownership. */
function listPluginNpmProjectCandidatesSync(npmRoot) {
	const projectsDir = resolvePluginNpmProjectsDir(npmRoot);
	try {
		return sortPaths(fsSync.readdirSync(projectsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(projectsDir, entry.name)));
	} catch (error) {
		if (isNotFoundPathError(error)) return [];
		throw error;
	}
}
/** Lists admitted projects with the manifest that established their managed ownership. */
function listManagedPluginNpmProjectsSync(npmRoot) {
	return readManagedProjects(listPluginNpmProjectCandidatesSync(npmRoot), npmRoot);
}
//#endregion
//#region src/plugins/managed-npm-retention.ts
const RETAINED_MANAGED_NPM_INSTALL_MARKER_DIR = ".openclaw-retained-npm-installs";
function resolveRetainedManagedNpmInstallPackageInfo(packageDir) {
	const resolvedPackageDir = path.resolve(packageDir);
	const packageBase = path.basename(resolvedPackageDir);
	const parentDir = path.dirname(resolvedPackageDir);
	const parentBase = path.basename(parentDir);
	const scopedPackage = parentBase.startsWith("@");
	const nodeModulesRoot = scopedPackage ? path.dirname(parentDir) : parentDir;
	if (path.basename(nodeModulesRoot) !== "node_modules") return null;
	const packageName = scopedPackage ? `${parentBase}/${packageBase}` : packageBase;
	if (!packageBase || packageBase === "." || !packageName.trim()) return null;
	const projectRoot = path.dirname(nodeModulesRoot);
	return {
		packageName,
		projectRoot,
		markerPath: path.join(projectRoot, RETAINED_MANAGED_NPM_INSTALL_MARKER_DIR, `${safePathSegmentHashed(packageName)}.json`)
	};
}
function hasRetainedManagedNpmInstallMarker(packageDir) {
	const info = resolveRetainedManagedNpmInstallPackageInfo(packageDir);
	return info ? fsSync.existsSync(info.markerPath) : false;
}
//#endregion
//#region src/plugins/installed-plugin-index-record-reader.ts
/** Reads installed-index records back into manifest registry records. */
function readJsonObjectFileSync(filePath) {
	const parsed = tryReadJsonSync(filePath);
	return isRecord(parsed) ? parsed : null;
}
function readStringRecord(value) {
	if (!isRecord(value)) return {};
	const record = {};
	for (const [key, raw] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
		if (isBlockedObjectKey$1(key)) continue;
		if (typeof raw === "string" && raw.trim()) record[key] = raw.trim();
	}
	return record;
}
function hasPackagePluginMetadata(manifest) {
	const openclaw = manifest.openclaw;
	if (!isRecord(openclaw)) return false;
	const extensions = openclaw.extensions;
	return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string");
}
function readManifestPluginId(packageDir) {
	const manifest = readJsonObjectFileSync(path.join(packageDir, "openclaw.plugin.json"));
	return (typeof manifest?.id === "string" ? manifest.id.trim() : "") || void 0;
}
function resolveRecoveredManagedNpmRoot(options = {}) {
	return path.resolve(options.stateDir ? path.join(options.stateDir, "npm") : resolveDefaultPluginNpmDir(options.env));
}
function resolveRecoveredManagedNpmPluginId(params) {
	const { packageManifest } = params;
	if (!packageManifest || !hasPackagePluginMetadata(packageManifest)) return;
	const packageName = typeof packageManifest.name === "string" && packageManifest.name.trim() ? packageManifest.name.trim() : params.packageName;
	const pluginId = readManifestPluginId(params.packageDir) ?? packageName;
	return validatePluginId(pluginId) ? void 0 : pluginId;
}
function readManagedNpmInstallTimestampMs(params) {
	const timestampPaths = params.sharedLegacyRoot ? [params.packageDir] : [path.join(params.projectRoot, "package.json"), params.projectRoot];
	for (const filePath of timestampPaths) try {
		return fsSync.statSync(filePath).mtimeMs;
	} catch {}
	return 0;
}
function buildRecoveredManagedNpmInstallCandidatesForRoot(params) {
	const dependencies = readStringRecord(params.rootManifest?.dependencies);
	const candidates = [];
	for (const [packageName, dependencySpec] of Object.entries(dependencies)) {
		const packageDir = path.join(params.projectRoot, "node_modules", ...packageName.split("/"));
		if (!safeStatSync(packageDir)?.isDirectory()) continue;
		if (hasRetainedManagedNpmInstallMarker(packageDir)) continue;
		const packageManifest = readJsonObjectFileSync(path.join(packageDir, "package.json"));
		const pluginId = resolveRecoveredManagedNpmPluginId({
			packageName,
			packageDir,
			packageManifest
		});
		if (!pluginId) continue;
		const version = typeof packageManifest?.version === "string" && packageManifest.version.trim() ? packageManifest.version.trim() : void 0;
		candidates.push({
			pluginId,
			installTimestampMs: readManagedNpmInstallTimestampMs({
				packageDir,
				projectRoot: params.projectRoot,
				sharedLegacyRoot: params.sharedLegacyRoot
			}),
			installRecord: {
				source: "npm",
				spec: `${packageName}@${dependencySpec}`,
				installPath: packageDir,
				...version ? {
					version,
					resolvedName: packageName,
					resolvedVersion: version
				} : {},
				...version ? { resolvedSpec: `${packageName}@${version}` } : {}
			}
		});
	}
	return candidates;
}
/** Lists recoverable managed npm installs without assigning active precedence. */
function listRecoveredManagedNpmInstallCandidates(options = {}) {
	const npmRoot = resolveRecoveredManagedNpmRoot(options);
	return [...buildRecoveredManagedNpmInstallCandidatesForRoot({
		projectRoot: npmRoot,
		rootManifest: readJsonObjectFileSync(path.join(npmRoot, "package.json")),
		sharedLegacyRoot: true
	}), ...listManagedPluginNpmProjectsSync(npmRoot).flatMap(({ projectRoot, manifest }) => buildRecoveredManagedNpmInstallCandidatesForRoot({
		projectRoot,
		rootManifest: manifest,
		sharedLegacyRoot: false
	}))];
}
function recordsShareInstallPath(left, right) {
	if (!left?.installPath || !right.installPath) return false;
	return normalizeInstallPathForComparison(left.installPath) === normalizeInstallPathForComparison(right.installPath);
}
function normalizeInstallPathForComparison(filePath) {
	const resolved = path.resolve(filePath);
	return process.platform === "win32" ? normalizeWindowsPathForComparison$1(resolved) : resolved;
}
function pickMostRecentRecoveredManagedNpmCandidate(candidates) {
	return candidates.toSorted((left, right) => {
		const byTimestamp = right.installTimestampMs - left.installTimestampMs;
		if (byTimestamp !== 0) return byTimestamp;
		return (right.installRecord.installPath ?? "").localeCompare(left.installRecord.installPath ?? "");
	})[0];
}
function emitManagedNpmRecoveryFallbackWarning(params) {
	process.emitWarning(`Managed npm recovery found ${params.candidates.length} installs for plugin "${params.pluginId}" without an authoritative active path; selected the most recently installed candidate. Run \`openclaw doctor --fix\` to persist and retire stale generations.`, {
		code: "OPENCLAW_PLUGIN_INSTALL_RECOVERY_FALLBACK",
		type: "OpenClawPluginRecoveryWarning",
		detail: JSON.stringify({
			pluginId: params.pluginId,
			selectedInstallPath: params.selected.installRecord.installPath,
			candidates: params.candidates.map((candidate) => ({
				installPath: candidate.installRecord.installPath,
				installTimestampMs: candidate.installTimestampMs
			}))
		})
	});
}
function buildRecoveredManagedNpmInstallRecords(persisted, options = {}) {
	const npmRoot = resolveRecoveredManagedNpmRoot(options);
	const records = createPluginInstallRecordMap();
	const candidatesByPluginId = /* @__PURE__ */ new Map();
	for (const candidate of listRecoveredManagedNpmInstallCandidates(options)) {
		const candidates = candidatesByPluginId.get(candidate.pluginId) ?? [];
		candidates.push(candidate);
		candidatesByPluginId.set(candidate.pluginId, candidates);
	}
	for (const [pluginId, candidates] of candidatesByPluginId) {
		const persistedRecord = getPluginInstallRecordMapEntry(persisted ?? void 0, pluginId);
		const authoritative = candidates.find((candidate) => recordsShareInstallPath(persistedRecord, candidate.installRecord));
		const selected = authoritative ?? pickMostRecentRecoveredManagedNpmCandidate(candidates);
		setPluginInstallRecordMapEntry(records, pluginId, selected.installRecord);
		const recoversUnavailableManagedPath = isUnavailableManagedNpmInstallRecord({
			npmRoot,
			persisted: persistedRecord,
			recovered: selected.installRecord
		});
		if (!authoritative && candidates.length > 1 && (!persistedRecord || recoversUnavailableManagedPath)) emitManagedNpmRecoveryFallbackWarning({
			pluginId,
			selected,
			candidates
		});
	}
	return records;
}
function readInstallRecordVersion(record) {
	return record?.resolvedVersion ?? record?.version;
}
function isUnavailableManagedNpmInstallRecord(params) {
	const installPath = params.persisted?.installPath;
	if (params.persisted?.source !== "npm" || !installPath) return false;
	try {
		if (fsSync.statSync(installPath).isDirectory()) return false;
	} catch (error) {
		if (!isNotFoundPathError(error)) return false;
	}
	const packageInfo = resolveRetainedManagedNpmInstallPackageInfo(installPath);
	if (!packageInfo || packageInfo.packageName !== params.recovered.resolvedName) return false;
	const npmRoot = normalizeInstallPathForComparison(params.npmRoot);
	return normalizeInstallPathForComparison(packageInfo.projectRoot) === npmRoot || normalizeInstallPathForComparison(path.dirname(packageInfo.projectRoot)) === normalizeInstallPathForComparison(resolvePluginNpmProjectsDir(params.npmRoot));
}
function mergeRecoveredManagedNpmMetadata(persisted, recovered, options = {}) {
	const next = {
		...persisted,
		...recovered
	};
	if (options.preservePersistedSpec) {
		const persistedSpec = persisted.spec ? parseRegistryNpmSpec(persisted.spec) : null;
		const selectorIsCompatible = persistedSpec !== null && isPrereleaseResolutionAllowed({
			spec: persistedSpec,
			resolvedVersion: recovered.resolvedVersion
		}) && (persistedSpec.selectorKind !== "exact-version" || persistedSpec.selector !== void 0 && recovered.resolvedVersion !== void 0 && compareValidSemver(persistedSpec.selector, recovered.resolvedVersion) === 0);
		if (persistedSpec?.name === recovered.resolvedName && selectorIsCompatible) next.spec = persisted.spec;
	}
	delete next.integrity;
	delete next.shasum;
	delete next.resolvedAt;
	delete next.installedAt;
	return next;
}
function mergeRecoveredManagedNpmRecord(params) {
	if (params.persisted && isUnavailableManagedNpmInstallRecord(params)) return mergeRecoveredManagedNpmMetadata(params.persisted, params.recovered, { preservePersistedSpec: true });
	const persistedVersion = readInstallRecordVersion(params.persisted);
	const recoveredVersion = readInstallRecordVersion(params.recovered);
	if (params.persisted?.source === "npm" && recordsShareInstallPath(params.persisted, params.recovered) && recoveredVersion && persistedVersion !== recoveredVersion) return mergeRecoveredManagedNpmMetadata(params.persisted, params.recovered);
	return params.persisted ?? params.recovered;
}
/** Merges persisted records with managed npm installs recovered from the current root. */
function mergeRecoveredManagedNpmInstallRecords(persisted, options) {
	const npmRoot = resolveRecoveredManagedNpmRoot(options);
	const recovered = buildRecoveredManagedNpmInstallRecords(persisted, options);
	const merged = copyPluginInstallRecordMap(persisted ?? void 0);
	for (const [pluginId, record] of Object.entries(recovered)) setPluginInstallRecordMapEntry(merged, pluginId, mergeRecoveredManagedNpmRecord({
		npmRoot,
		persisted: getPluginInstallRecordMapEntry(merged, pluginId),
		recovered: record
	}));
	return merged;
}
function requireLoadablePluginInstallRecordState(state) {
	if (state.status === "invalid") throw new Error("Persisted plugin install records are invalid. Run openclaw doctor to inspect and repair plugin installation state.");
	return state.status === "valid" ? state.records : null;
}
function resolveInstallRecordsCacheKey(options) {
	return [path.resolve(resolveInstalledPluginIndexStorePath(options)), resolveRecoveredManagedNpmRoot(options)].join("\0");
}
/** Synchronously loads installed plugin records, recovering managed npm installs and caching them. */
function loadInstalledPluginIndexInstallRecordsSync(params = {}) {
	const cacheKey = resolveInstallRecordsCacheKey(params);
	const cache = getPluginCache().installRecords;
	const cached = cache.get(cacheKey);
	if (cached) return copyPluginInstallRecordMap(cached);
	const records = mergeRecoveredManagedNpmInstallRecords(requireLoadablePluginInstallRecordState(inspectPersistedInstalledPluginIndexInstallRecordsSync(params)), params);
	cache.set(cacheKey, records);
	return copyPluginInstallRecordMap(records);
}
//#endregion
//#region src/plugins/official-external-install-records.ts
function resolveNpmSpecPackageName(spec) {
	return spec ? parseRegistryNpmSpec(spec)?.name : void 0;
}
function resolveClawHubSpecPackageName(spec) {
	return spec ? parseClawHubPluginSpec(spec)?.name : void 0;
}
function resolveExactNpmPackageName(value) {
	const packageName = resolveNpmSpecPackageName(value);
	return packageName && value.trim() === packageName ? packageName : void 0;
}
function resolveRecordedPackageNames(fields) {
	const packageNames = [];
	for (const [value, resolvePackageName] of fields) {
		if (value === void 0) continue;
		const packageName = resolvePackageName(value);
		if (!packageName) return;
		packageNames.push(packageName);
	}
	return packageNames;
}
function resolveUnanimousRecordedNpmPackageName(record) {
	if (record.source !== "npm") return;
	const packageNames = resolveRecordedPackageNames([
		[record.spec, resolveNpmSpecPackageName],
		[record.resolvedName, resolveExactNpmPackageName],
		[record.resolvedSpec, resolveNpmSpecPackageName]
	]);
	return packageNames && new Set(packageNames).size === 1 ? packageNames[0] : void 0;
}
function hasTrustedOfficialNpmProvenance(record) {
	return record.source === "npm" && record.artifactKind === void 0 && record.sourcePath === void 0;
}
function resolveRecordedClawHubPackageNames(record) {
	return resolveRecordedPackageNames([
		[record.clawhubPackage, resolveExactNpmPackageName],
		[record.spec, resolveClawHubSpecPackageName],
		[record.resolvedSpec, (spec) => resolveClawHubSpecPackageName(spec) ?? resolveNpmSpecPackageName(spec)],
		[record.resolvedName, resolveExactNpmPackageName]
	]);
}
function isOfficialClawHubInstallRecord(record) {
	if (record.source !== "clawhub" || record.clawhubChannel !== "official") return false;
	return (record.clawhubUrl ?? "").trim().replace(/\/+$/, "") === "https://clawhub.ai";
}
/** Resolves one package identity from a current trusted official ClawHub install record. */
function resolveTrustedOfficialClawHubPackageName(record) {
	if (!isOfficialClawHubInstallRecord(record)) return;
	const packageNames = resolveRecordedClawHubPackageNames(record);
	if (!packageNames || packageNames.length === 0 || new Set(packageNames).size !== 1) return;
	return packageNames[0];
}
/** Binds official trust to the actual package and consistent recorded source identity. */
function isTrustedOfficialPluginInstallRecord(params) {
	const packageName = params.packageName?.trim();
	const entry = packageName ? getOfficialExternalPluginCatalogEntryForPackage(packageName) : void 0;
	if (!entry || resolveOfficialExternalPluginId(entry) !== params.pluginId && !resolveOfficialExternalPluginLegacyIds(entry).includes(params.pluginId)) return false;
	const install = resolveOfficialExternalPluginInstall(entry);
	const record = params.record;
	if (record.source === "npm") return hasTrustedOfficialNpmProvenance(record) && resolveNpmSpecPackageName(install?.npmSpec) === packageName && resolveUnanimousRecordedNpmPackageName(record) === packageName && (record.clawhubPackage === void 0 || resolveExactNpmPackageName(record.clawhubPackage) === packageName);
	return Boolean(install?.clawhubSpec || install?.npmSpec) && (record.clawhubPackage !== void 0 || record.spec !== void 0) && resolveTrustedOfficialClawHubPackageName(record) === packageName;
}
//#endregion
//#region src/plugins/installed-plugin-record-match.ts
function resolveCandidateInstallOwner(params) {
	if (isPluginCandidateInstallOwnerAmbiguous(params.candidate)) return;
	const installOwner = resolvePluginCandidateInstallOwner(params.candidate);
	if (installOwner) return Object.hasOwn(params.installRecords, installOwner) ? installOwner : void 0;
}
function matchesInstalledPluginRecord(params) {
	if (params.candidate.origin !== "global" && params.candidate.origin !== "config") return false;
	const installOwner = resolveCandidateInstallOwner(params);
	const record = installOwner ? params.installRecords[installOwner] : void 0;
	if (!record) return false;
	const candidatePaths = [
		params.candidate.rootDir,
		params.candidate.packageDir,
		params.candidate.source,
		params.candidate.setupSource
	].filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => {
		const resolved = resolveUserPath(entry, params.env);
		return pluginCacheRealpathSync(resolved) ?? resolved;
	});
	const trackedPaths = (params.installPathOnly ? [record.installPath] : [record.installPath, record.sourcePath]).filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => {
		const resolved = resolveUserPath(entry, params.env);
		return pluginCacheRealpathSync(resolved) ?? resolved;
	});
	if (candidatePaths.length === 0 || trackedPaths.length === 0) return false;
	return trackedPaths.some((trackedPath) => candidatePaths.some((candidatePath) => candidatePath === trackedPath || isPathInside$1(trackedPath, candidatePath) || isPathInside$1(candidatePath, trackedPath)));
}
function resolvePluginTrust(params) {
	const installOwner = resolveCandidateInstallOwner(params);
	const record = installOwner ? params.installRecords[installOwner] : void 0;
	const origin = params.candidate.origin;
	let reason;
	if (origin === "bundled") reason = "bundled";
	else if (isPluginCandidateInstallOwnerAmbiguous(params.candidate)) reason = "owner-ambiguous";
	else if (origin === "workspace" || record?.source === "path" || record?.source === "npm" && (record.artifactKind !== void 0 || record.sourcePath !== void 0)) reason = "origin-path";
	else if (!record || !installOwner) reason = "record-missing";
	else if (!matchesInstalledPluginRecord({
		pluginId: params.pluginId,
		candidate: params.candidate,
		env: params.env,
		installRecords: params.installRecords,
		installPathOnly: true
	})) reason = "install-path-mismatch";
	else if (isTrustedOfficialPluginInstallRecord({
		pluginId: installOwner,
		packageName: params.candidate.packageName,
		record
	})) reason = "trusted-official";
	else if (record.source === "npm" && record.spec === void 0 && record.resolvedName === void 0 && record.resolvedSpec === void 0 || record.source === "clawhub" && record.clawhubUrl === void 0 && record.clawhubChannel === void 0) reason = "provenance-missing";
	else reason = "provenance-invalid";
	return {
		reason,
		registryPath: params.registryPath,
		origin,
		installSource: record?.source,
		installSpec: record?.spec === void 0 ? void 0 : redactSensitiveText(record.spec, { mode: "tools" })
	};
}
//#endregion
//#region packages/gateway-protocol/src/svg-image.ts
const SVG_PROLOGUE_WHITESPACE_RE = /\s*/y;
function skipSvgPrologueWhitespace(text, index) {
	SVG_PROLOGUE_WHITESPACE_RE.lastIndex = index;
	SVG_PROLOGUE_WHITESPACE_RE.exec(text);
	return SVG_PROLOGUE_WHITESPACE_RE.lastIndex;
}
function startsWithToken(text, index, token) {
	return text.slice(index, index + token.length).toLowerCase() === token;
}
/**
* Recognizes an SVG root element after an optional XML declaration and comments.
*
* An index scan rather than a regex on purpose: the equivalent
* `(?:<!--[\s\S]*?-->\s*)*<svg` backtracks exponentially on comment-like bytes that
* never reach a root element, and these bytes arrive from remote icon and
* link-favicon responses on the Gateway's single event loop, so one crafted
* response would stall every session. A comment ends at its first `-->`, so text
* between a closed comment and the root element is rejected, not absorbed.
*/
function startsWithSvgRootElement(text) {
	let index = skipSvgPrologueWhitespace(text, 0);
	if (startsWithToken(text, index, "<?xml")) {
		const declarationEnd = text.indexOf(">", index);
		if (declarationEnd < 0) return false;
		index = skipSvgPrologueWhitespace(text, declarationEnd + 1);
	}
	while (startsWithToken(text, index, "<!--")) {
		const commentEnd = text.indexOf("-->", index + 4);
		if (commentEnd < 0) return false;
		index = skipSvgPrologueWhitespace(text, commentEnd + 3);
	}
	if (!startsWithToken(text, index, "<svg")) return false;
	const delimiter = text[index + 4];
	return delimiter === ">" || delimiter === "/" && text[index + 5] === ">" || delimiter !== void 0 && /\s/u.test(delimiter);
}
/**
* SVG images stay self-contained: no script, document expansion, embedded
* documents, or outbound fetches can reach the browser through an image route.
*/
function isSelfContainedSvg(text) {
	return !text.includes("\0") && !/<!doctype|<!entity/iu.test(text) && !/<\s*(?:script|foreignObject|image|use|iframe)\b/iu.test(text) && !/\b(?:href|xlink:href|src)\s*=/iu.test(text) && startsWithSvgRootElement(text);
}
//#endregion
//#region src/plugins/portable-icon-paths.ts
/** Package-local artwork contracts shared by discovery and package builders. */
const PORTABLE_PLUGIN_ICON_PATH = "assets/icon.png";
const PLUGIN_ACTIVITY_ICON_PATH = "assets/activity.svg";
const PLUGIN_TOOL_ACTIVITY_ICON_DIR = "assets/activity";
const PLUGIN_ACTIVITY_ICON_MAX_BYTES = 32768;
function isPluginActivityToolName(value) {
	return /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/u.test(value);
}
//#endregion
//#region src/plugins/manifest-theme-definitions.ts
/** Capture palettes and artwork so a published generation never reads changed files. */
function loadManifestThemeDefinitions(params) {
	if (!params.themes?.length) return;
	return params.themes.flatMap((theme) => {
		try {
			if (params.pluginId === "user" || !isThemeId(`${params.pluginId}/${theme.id}`)) throw new Error("qualified theme ID must be portable, outside user/, and at most 256 characters");
			const file = readPluginCacheFile({
				rootDir: params.rootDir,
				relativePath: theme.source,
				rejectHardlinks: params.rejectHardlinks,
				maxBytes: MAX_THEME_DEFINITION_BYTES * 4
			});
			if (!file.ok) throw new Error("source must be a readable JSON file inside the plugin root");
			const definition = normalizeThemeDefinition(JSON.parse(file.contents.toString("utf8")), {
				hatIds: Object.keys(theme.hats ?? {}),
				critterIds: Object.keys(theme.critters ?? {})
			});
			if (definition.name !== theme.name || definition.description !== theme.description) throw new Error("name and description must match the manifest declaration");
			const readSvg = (source) => {
				const svgFile = readPluginCacheFile({
					rootDir: params.rootDir,
					relativePath: source,
					rejectHardlinks: params.rejectHardlinks,
					maxBytes: PLUGIN_ACTIVITY_ICON_MAX_BYTES
				});
				if (!svgFile.ok) throw new Error(`artwork ${source} must be a readable SVG inside the plugin root, at most ${PLUGIN_ACTIVITY_ICON_MAX_BYTES} bytes`);
				const svg = svgFile.contents.toString("utf8");
				if (!isSelfContainedSvg(svg)) throw new Error(`artwork ${source} must be a self-contained SVG`);
				return svg;
			};
			const artwork = {
				...theme.hats ? { hats: Object.fromEntries(Object.entries(theme.hats).map(([id, source]) => [id, { svg: readSvg(source) }])) } : {},
				...theme.critters ? { critters: Object.fromEntries(Object.entries(theme.critters).map(([id, { source, ...metadata }]) => [id, {
					svg: readSvg(source),
					...metadata
				}])) } : {}
			};
			return [{
				id: theme.id,
				definition,
				...theme.hats || theme.critters ? { artwork } : {}
			}];
		} catch (error) {
			params.diagnostics.push({
				level: "warn",
				pluginId: params.pluginId,
				message: `theme ${theme.id} is unavailable: ${error instanceof Error ? error.message : "invalid definition"}`
			});
			return [];
		}
	});
}
//#endregion
//#region src/plugins/manifest-record.ts
function resolvePluginSourcePath(sourcePath) {
	if (pluginCacheExistsSync(sourcePath)) return sourcePath;
	if (sourcePath.endsWith(".ts")) {
		const jsPath = sourcePath.slice(0, -3) + ".js";
		if (pluginCacheExistsSync(jsPath)) return jsPath;
	}
	return sourcePath;
}
function isPluginRootPath(params) {
	const resolvedTargetPath = path.resolve(params.targetPath);
	if (!isPathInside$1(path.resolve(params.rootPath), resolvedTargetPath)) return false;
	const targetRealPath = pluginCacheRealpathSync(resolvedTargetPath);
	if (!targetRealPath) return params.targetMustExist !== true;
	if (!isPathInside$1(params.rootRealPath, targetRealPath)) return false;
	if (params.rejectHardlinks === true) {
		const targetStat = pluginCacheStatSync(resolvedTargetPath);
		if (!targetStat || targetStat.nlink > 1) return false;
	}
	return true;
}
function resolveManifestPluginSourcePath(params) {
	const pushDiagnostic = () => {
		params.diagnostics.push({
			level: "warn",
			pluginId: sanitizeForLog(params.pluginId),
			source: sanitizeForLog(params.manifestPath),
			message: `plugin manifest ${params.entryName} must resolve inside the plugin root; ignoring entry`
		});
	};
	if (!params.entry || path.isAbsolute(params.entry)) {
		pushDiagnostic();
		return;
	}
	const rootPath = path.resolve(params.rootDir);
	const rootRealPath = pluginCacheRealpathSync(rootPath) ?? rootPath;
	const sourcePath = path.resolve(rootPath, params.entry);
	if (!isPluginRootPath({
		rootPath,
		targetPath: sourcePath,
		rootRealPath,
		rejectHardlinks: params.rejectHardlinks,
		targetMustExist: pluginCacheExistsSync(sourcePath)
	})) {
		pushDiagnostic();
		return;
	}
	const resolvedSourcePath = resolvePluginSourcePath(sourcePath);
	if (!isPluginRootPath({
		rootPath,
		targetPath: resolvedSourcePath,
		rootRealPath,
		rejectHardlinks: params.rejectHardlinks,
		targetMustExist: pluginCacheExistsSync(resolvedSourcePath)
	})) {
		pushDiagnostic();
		return;
	}
	return resolvedSourcePath;
}
function resolvePortablePluginIcons(params) {
	let root;
	const resolveIcon = (relativePath) => {
		const iconPath = path.resolve(params.rootDir, relativePath);
		const iconStat = pluginCacheLstatSync(iconPath);
		if (!iconStat?.isFile() || params.rejectHardlinks && iconStat.nlink > 1) return;
		if (!root) {
			const rootPath = path.resolve(params.rootDir);
			root = {
				path: rootPath,
				realPath: pluginCacheRealpathSync(rootPath) ?? rootPath
			};
		}
		return isPluginRootPath({
			rootPath: root.path,
			targetPath: iconPath,
			rootRealPath: root.realPath,
			rejectHardlinks: params.rejectHardlinks,
			targetMustExist: true
		}) ? iconPath : void 0;
	};
	const iconPath = resolveIcon(PORTABLE_PLUGIN_ICON_PATH);
	const activityIconPath = resolveIcon(PLUGIN_ACTIVITY_ICON_PATH);
	const directory = path.resolve(params.rootDir, PLUGIN_TOOL_ACTIVITY_ICON_DIR);
	if (!isPluginRootPath({
		rootPath: params.rootDir,
		rootRealPath: params.rootDir === root?.path ? root.realPath : pluginCacheRealpathSync(params.rootDir) ?? params.rootDir,
		targetPath: directory,
		targetMustExist: true
	})) return {
		iconPath,
		activityIconPath
	};
	let entries;
	try {
		entries = readPluginCacheDirectory(directory);
	} catch {
		return {
			iconPath,
			activityIconPath
		};
	}
	if (entries.length > 128) return {
		iconPath,
		activityIconPath
	};
	const paths = [];
	for (const name of entries.map((entry) => entry.name).toSorted()) {
		const toolName = name.endsWith(".svg") ? name.slice(0, -4) : "";
		if (!isPluginActivityToolName(toolName)) continue;
		const toolIconPath = resolveIcon(`${PLUGIN_TOOL_ACTIVITY_ICON_DIR}/${name}`);
		if (toolIconPath) paths.push([toolName, toolIconPath]);
	}
	return {
		iconPath,
		activityIconPath,
		...paths.length ? { toolActivityIconPaths: Object.fromEntries(paths) } : {}
	};
}
function mergePackageChannelMetaIntoChannelConfigs(params) {
	const channelId = params.packageChannel?.id?.trim();
	if (!channelId || isBlockedObjectKey$1(channelId) || !params.channelConfigs || !Object.hasOwn(params.channelConfigs, channelId)) return params.channelConfigs;
	const existing = params.channelConfigs[channelId];
	if (!existing) return params.channelConfigs;
	const label = existing.label ?? normalizeOptionalString(params.packageChannel?.label) ?? "";
	const description = existing.description ?? normalizeOptionalString(params.packageChannel?.blurb) ?? "";
	const preferOver = existing.preferOver ?? normalizeOptionalTrimmedStringList(params.packageChannel?.preferOver);
	const commands = existing.commands ?? normalizeManifestChannelCommandDefaults(params.packageChannel?.commands);
	const merged = Object.create(null);
	for (const [key, value] of Object.entries(params.channelConfigs)) if (!isBlockedObjectKey$1(key)) merged[key] = value;
	merged[channelId] = {
		...existing,
		...label ? { label } : {},
		...description ? { description } : {},
		...preferOver?.length ? { preferOver } : {},
		...commands ? { commands } : {}
	};
	return merged;
}
function mergeContractLists(left, right) {
	const merged = uniqueStrings([...left ?? [], ...right ?? []].map((value) => value.trim()).filter((value) => value.length > 0));
	return merged.length > 0 ? merged : void 0;
}
function mergeManifestContracts(manifestContracts, catalogContracts) {
	if (!catalogContracts) return manifestContracts;
	const contracts = {};
	for (const key of PLUGIN_MANIFEST_CONTRACT_KEYS) {
		const merged = mergeContractLists(manifestContracts?.[key], catalogContracts[key]);
		if (merged) contracts[key] = merged;
	}
	return Object.keys(contracts).length > 0 ? contracts : void 0;
}
function mergeCatalogChannelConfigs(params) {
	if (!params.catalogChannelConfigs) return params.manifestChannelConfigs;
	const merged = Object.create(null);
	for (const [key, value] of Object.entries(params.catalogChannelConfigs)) if (!isBlockedObjectKey$1(key)) merged[key] = value;
	for (const [key, value] of Object.entries(params.manifestChannelConfigs ?? {})) if (!isBlockedObjectKey$1(key)) {
		const catalogValue = merged[key];
		merged[key] = catalogValue ? {
			...catalogValue,
			...value,
			schema: value.schema ?? catalogValue.schema,
			...catalogValue.uiHints || value.uiHints ? { uiHints: {
				...catalogValue.uiHints,
				...value.uiHints
			} } : {},
			...value.runtime ?? catalogValue.runtime ? { runtime: value.runtime ?? catalogValue.runtime } : {},
			...value.label ?? catalogValue.label ? { label: value.label ?? catalogValue.label } : {},
			...value.description ?? catalogValue.description ? { description: value.description ?? catalogValue.description } : {},
			...value.preferOver ?? catalogValue.preferOver ? { preferOver: value.preferOver ?? catalogValue.preferOver } : {},
			...value.commands ?? catalogValue.commands ? { commands: value.commands ?? catalogValue.commands } : {}
		} : value;
	}
	return Object.keys(merged).length > 0 ? merged : void 0;
}
function buildPluginManifestRecord(params) {
	const pluginId = params.candidate.effectivePluginId ?? params.manifest.id;
	const resolveCatalogEntry = (entryName) => {
		const entry = params.manifest[entryName];
		return entry === void 0 ? void 0 : resolveManifestPluginSourcePath({
			rootDir: params.candidate.rootDir,
			manifestPath: params.manifestPath,
			pluginId,
			entryName,
			entry,
			rejectHardlinks: params.rejectHardlinks,
			diagnostics: params.diagnostics
		});
	};
	const manifestChannelConfigs = params.candidate.origin === "bundled" && params.bundledChannelConfigCollector ? params.bundledChannelConfigCollector({
		pluginDir: params.candidate.packageDir ?? params.candidate.rootDir,
		manifest: params.manifest,
		packageManifest: params.candidate.packageManifest
	}) : params.manifest.channelConfigs;
	const officialCatalogManifest = params.candidate.origin !== "bundled" ? getOfficialExternalPluginCatalogManifest(getOfficialExternalPluginCatalogEntryForPackage(params.candidate.packageName) ?? {}) : void 0;
	const channelConfigs = mergePackageChannelMetaIntoChannelConfigs({
		channelConfigs: mergeCatalogChannelConfigs({
			manifestChannelConfigs,
			catalogChannelConfigs: officialCatalogManifest?.channelConfigs
		}),
		packageChannel: params.candidate.packageManifest?.channel
	});
	const packageChannelCommands = normalizeManifestChannelCommandDefaults(params.candidate.packageManifest?.channel?.commands);
	return {
		id: pluginId,
		categories: params.manifest.categories,
		backupResources: params.manifest.backupResources,
		doctorContract: params.manifest.doctorContract,
		doctorHealthChecks: params.manifest.doctorHealthChecks,
		sessionRouteStateOwners: params.manifest.sessionRouteStateOwners,
		name: normalizeOptionalString(params.manifest.name) ?? params.candidate.packageName,
		description: normalizeOptionalString(params.manifest.description) ?? params.candidate.packageDescription,
		catalog: normalizeManifestCatalog({
			featured: params.manifest.catalog?.featured ?? officialCatalogManifest?.catalog?.featured,
			order: params.manifest.catalog?.order ?? officialCatalogManifest?.catalog?.order
		}),
		...resolvePortablePluginIcons({
			rootDir: params.candidate.rootDir,
			rejectHardlinks: params.rejectHardlinks
		}),
		version: normalizeOptionalString(params.manifest.version) ?? params.candidate.packageVersion,
		packageName: params.candidate.packageName,
		packageVersion: params.candidate.packageVersion,
		packageDescription: params.candidate.packageDescription,
		enabledByDefault: params.manifest.enabledByDefault === true ? true : void 0,
		enabledByDefaultOnPlatforms: params.manifest.enabledByDefaultOnPlatforms,
		autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders,
		legacyPluginIds: params.manifest.legacyPluginIds,
		format: params.candidate.format ?? "openclaw",
		bundleFormat: params.candidate.bundleFormat,
		kind: params.manifest.kind,
		channels: params.manifest.channels ?? [],
		channelAccountKeyPolicies: params.manifest.channelAccountKeyPolicies,
		providers: params.manifest.providers ?? [],
		providerDiscoverySource: resolveCatalogEntry("providerCatalogEntry"),
		capabilityCatalogSource: params.manifest.capabilityCatalogEntry === void 0 ? void 0 : resolveCatalogEntry("capabilityCatalogEntry") ?? null,
		modelSupport: params.manifest.modelSupport,
		modelCatalog: params.manifest.modelCatalog,
		modelPricing: params.manifest.modelPricing,
		modelIdNormalization: params.manifest.modelIdNormalization,
		providerEndpoints: params.manifest.providerEndpoints,
		providerRequest: params.manifest.providerRequest,
		secretProviderIntegrations: params.manifest.secretProviderIntegrations,
		cliBackends: params.manifest.cliBackends ?? [],
		syntheticAuthRefs: params.manifest.syntheticAuthRefs ?? [],
		nonSecretAuthMarkers: params.manifest.nonSecretAuthMarkers ?? [],
		commandAliases: params.manifest.commandAliases,
		cliCommands: params.manifest.cliCommands,
		providerUsageAuthEnvVars: params.manifest.providerUsageAuthEnvVars,
		providerAuthAliases: params.manifest.providerAuthAliases,
		providerAuthChoices: params.manifest.providerAuthChoices,
		activation: params.manifest.activation,
		setup: params.manifest.setup,
		packageManifest: params.candidate.packageManifest,
		packageDependencies: params.candidate.packageDependencies,
		packageOptionalDependencies: params.candidate.packageOptionalDependencies,
		packageChannel: params.candidate.packageManifest?.channel,
		packageInstall: params.candidate.packageManifest?.install,
		trustedOfficialInstall: params.trust.reason === "trusted-official" ? true : void 0,
		trust: params.trust,
		qaRunners: params.manifest.qaRunners,
		dashboard: params.manifest.dashboard,
		controlUi: params.manifest.controlUi,
		uiCapabilities: params.manifest.uiCapabilities,
		themes: params.manifest.themes,
		themeDefinitions: loadManifestThemeDefinitions({
			pluginId,
			rootDir: params.candidate.rootDir,
			themes: params.manifest.themes,
			rejectHardlinks: params.rejectHardlinks,
			diagnostics: params.diagnostics
		}),
		mcpServers: params.manifest.mcpServers,
		skills: params.manifest.skills ?? [],
		settingsFiles: [],
		hooks: [],
		origin: params.candidate.origin,
		workspaceDir: params.candidate.workspaceDir,
		rootDir: params.candidate.rootDir,
		source: params.candidate.source,
		setupSource: params.candidate.setupSource,
		manifestPath: params.manifestPath,
		schemaCacheKey: params.schemaCacheKey,
		configSchema: params.configSchema,
		configUiHints: params.manifest.uiHints,
		configGroups: params.manifest.configGroups,
		contracts: mergeManifestContracts(params.manifest.contracts, officialCatalogManifest?.contracts),
		transcriptSources: params.manifest.transcriptSources,
		decisionModels: params.manifest.decisionModels,
		mediaUnderstandingProviderMetadata: params.manifest.mediaUnderstandingProviderMetadata,
		imageGenerationProviderMetadata: params.manifest.imageGenerationProviderMetadata,
		videoGenerationProviderMetadata: params.manifest.videoGenerationProviderMetadata,
		musicGenerationProviderMetadata: params.manifest.musicGenerationProviderMetadata,
		toolMetadata: params.manifest.toolMetadata,
		configContracts: params.manifest.configContracts,
		channelConfigs,
		...params.candidate.packageManifest?.channel?.id ? { channelCatalogMeta: {
			id: params.candidate.packageManifest.channel.id,
			...typeof params.candidate.packageManifest.channel.label === "string" ? { label: params.candidate.packageManifest.channel.label } : {},
			...typeof params.candidate.packageManifest.channel.blurb === "string" ? { blurb: params.candidate.packageManifest.channel.blurb } : {},
			...params.candidate.packageManifest.channel.preferOver ? { preferOver: params.candidate.packageManifest.channel.preferOver } : {},
			...packageChannelCommands ? { commands: packageChannelCommands } : {}
		} } : {}
	};
}
function buildBundleManifestRecord(params) {
	return {
		id: params.manifest.id,
		name: normalizeOptionalString(params.manifest.name) ?? params.candidate.idHint,
		description: normalizeOptionalString(params.manifest.description),
		...resolvePortablePluginIcons({
			rootDir: params.candidate.rootDir,
			rejectHardlinks: params.rejectHardlinks
		}),
		version: normalizeOptionalString(params.manifest.version),
		packageName: params.candidate.packageName,
		packageVersion: params.candidate.packageVersion,
		packageDescription: params.candidate.packageDescription,
		packageManifest: params.candidate.packageManifest,
		packageDependencies: params.candidate.packageDependencies,
		packageOptionalDependencies: params.candidate.packageOptionalDependencies,
		packageChannel: params.candidate.packageManifest?.channel,
		packageInstall: params.candidate.packageManifest?.install,
		format: "bundle",
		bundleFormat: params.candidate.bundleFormat,
		bundleCapabilities: params.manifest.capabilities,
		activation: params.manifest.activation,
		channels: [],
		providers: [],
		cliBackends: [],
		syntheticAuthRefs: [],
		nonSecretAuthMarkers: [],
		skills: params.manifest.skills ?? [],
		settingsFiles: params.manifest.settingsFiles ?? [],
		hooks: params.manifest.hooks ?? [],
		origin: params.candidate.origin,
		workspaceDir: params.candidate.workspaceDir,
		rootDir: params.candidate.rootDir,
		source: params.candidate.source,
		manifestPath: params.manifestPath,
		schemaCacheKey: void 0,
		configSchema: void 0,
		configUiHints: void 0,
		configContracts: void 0,
		channelConfigs: void 0
	};
}
//#endregion
//#region src/plugins/min-host-version.ts
/** Validation message for plugin minHostVersion manifest fields. */
const MIN_HOST_VERSION_FORMAT = "openclaw.install.minHostVersion must use a semver floor in the form \">=x.y.z[-prerelease][+build]\"";
const SEMVER_LABEL_RE = String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
const MIN_HOST_VERSION_RE = new RegExp(`^>=(${SEMVER_LABEL_RE})$`);
const LEGACY_MIN_HOST_VERSION_RE = new RegExp(`^(${SEMVER_LABEL_RE})$`);
/** Parses a plugin minHostVersion manifest field. */
function parseMinHostVersionRequirement(raw, options = {}) {
	const trimmed = normalizeOptionalString(raw);
	if (!trimmed) return null;
	const match = trimmed.match(MIN_HOST_VERSION_RE) ?? (options.allowLegacyBareSemver ? trimmed.match(LEGACY_MIN_HOST_VERSION_RE) : null);
	if (!match) return null;
	const minimumLabel = match[1] ?? "";
	if (!valid(minimumLabel)) return null;
	return {
		raw: trimmed,
		minimumLabel
	};
}
/** Checks whether the current host satisfies a plugin minHostVersion requirement. */
function checkMinHostVersion(params) {
	if (params.minHostVersion === void 0) return {
		ok: true,
		requirement: null
	};
	const requirement = parseMinHostVersionRequirement(params.minHostVersion, { allowLegacyBareSemver: params.allowLegacyBareSemver });
	if (!requirement) return {
		ok: false,
		kind: "invalid",
		error: MIN_HOST_VERSION_FORMAT
	};
	const currentVersion = normalizeOptionalString(params.currentVersion) || "unknown";
	const comparison = compareOpenClawVersions(currentVersion, requirement.minimumLabel);
	if (comparison === null) return {
		ok: false,
		kind: "unknown_host_version",
		requirement
	};
	if (comparison < 0) return {
		ok: false,
		kind: "incompatible",
		requirement,
		currentVersion
	};
	return {
		ok: true,
		requirement
	};
}
//#endregion
//#region src/plugins/manifest-registry-build.ts
const PLUGIN_ORIGIN_RANK = {
	config: 0,
	workspace: 1,
	global: 2,
	bundled: 3
};
function rejectCaseFoldedIdCollisions(records, diagnostics) {
	const rejected = /* @__PURE__ */ new Set();
	for (const [policyId, matches] of groupPluginRecords(records, (record) => normalizePluginPolicyId(record.id))) {
		const declaredIds = [...new Set(matches.map((record) => record.id))].toSorted();
		if (declaredIds.length < 2) continue;
		const message = `plugin ids ${declaredIds.map((id) => JSON.stringify(id)).join(", ")} collide as normalized id ${JSON.stringify(policyId)}; refusing all colliding plugins`;
		for (const record of matches) {
			rejected.add(record);
			diagnostics.push({
				level: "error",
				pluginId: record.id,
				source: record.source,
				message
			});
		}
	}
	return records.filter((record) => !rejected.has(record));
}
function pushNonBundledChannelConfigDescriptorDiagnostic(params) {
	if (params.record.origin === "bundled" || params.record.format === "bundle") return;
	const configuredEntry = params.normalized?.entries[params.record.id];
	if (params.normalized?.enabled === false || configuredEntry?.enabled === false || params.normalized?.deny.includes(params.record.id) || params.normalized?.allow.length && !params.normalized.allow.includes(params.record.id)) return;
	const declaredChannels = params.record.channels.map((channelId) => channelId.trim()).filter((channelId) => channelId.length > 0);
	if (declaredChannels.length === 0) return;
	const channelConfigs = params.record.channelConfigs ?? {};
	const missingChannels = declaredChannels.filter((channelId) => !Object.hasOwn(channelConfigs, channelId));
	if (missingChannels.length === 0) return;
	const safeMissingChannels = missingChannels.map(sanitizeForLog);
	params.diagnostics.push({
		level: "warn",
		pluginId: sanitizeForLog(params.record.id),
		source: sanitizeForLog(params.record.manifestPath),
		message: `channel plugin manifest declares ${safeMissingChannels.join(", ")} without channelConfigs metadata; add openclaw.plugin.json#channelConfigs so config schema and setup surfaces work before runtime loads. Channels without channelConfigs still appear in channel listings, but setup UI may be limited.`
	});
}
function dedupePluginDiagnostics(diagnostics, discoveryDiagnostics) {
	const seen = /* @__PURE__ */ new Set();
	const deduped = [];
	for (const diagnostic of diagnostics) {
		const key = JSON.stringify([
			diagnostic.level,
			diagnostic.pluginId ?? "",
			diagnostic.message,
			diagnostic.level === "error" || discoveryDiagnostics.has(diagnostic) ? diagnostic.source ?? "" : ""
		]);
		if (seen.has(key)) continue;
		seen.add(key);
		deduped.push(diagnostic);
	}
	return deduped;
}
function isStaleForeignBundledPin(params) {
	return isForeignBundledPluginRoot(params.candidate.rootDir, params.env) && !isBundledPluginInsideDevSourceRoot({
		rootDir: params.candidate.rootDir,
		env: params.env
	});
}
function resolveDuplicatePrecedenceRank(params) {
	if (params.candidate.origin === "config" || params.candidate.configSelected) return 0;
	if (params.candidate.origin === "bundled" && isBundledPluginInsideDevSourceRoot({
		rootDir: params.candidate.rootDir,
		env: params.env
	})) return 1;
	if (params.candidate.origin === "global" && !isStaleForeignBundledPin({
		candidate: params.candidate,
		env: params.env
	}) && matchesInstalledPluginRecord({
		pluginId: params.pluginId,
		candidate: params.candidate,
		config: params.config,
		env: params.env,
		installRecords: params.installRecords
	})) return 2;
	if (params.candidate.origin === "bundled") return 3;
	if (params.candidate.origin === "workspace") return 4;
	return 5;
}
function isIntentionalInstalledBundledDuplicate(params) {
	const leftIsInstalled = matchesInstalledPluginRecord({
		pluginId: params.pluginId,
		candidate: params.left,
		config: params.config,
		env: params.env,
		installRecords: params.installRecords
	});
	const rightIsInstalled = matchesInstalledPluginRecord({
		pluginId: params.pluginId,
		candidate: params.right,
		config: params.config,
		env: params.env,
		installRecords: params.installRecords
	});
	return leftIsInstalled && !isStaleForeignBundledPin({
		candidate: params.left,
		env: params.env
	}) && params.right.origin === "bundled" && !isBundledPluginInsideDevSourceRoot({
		rootDir: params.right.rootDir,
		env: params.env
	}) || rightIsInstalled && !isStaleForeignBundledPin({
		candidate: params.right,
		env: params.env
	}) && params.left.origin === "bundled" && !isBundledPluginInsideDevSourceRoot({
		rootDir: params.left.rootDir,
		env: params.env
	});
}
function isSameGlobalPackageDuplicate(left, right) {
	if (left.origin !== "global" || right.origin !== "global") return false;
	const leftPackageName = normalizeOptionalString(left.packageName);
	const rightPackageName = normalizeOptionalString(right.packageName);
	if (!leftPackageName || leftPackageName !== rightPackageName) return false;
	const leftPackageVersion = normalizeOptionalString(left.packageVersion);
	const rightPackageVersion = normalizeOptionalString(right.packageVersion);
	return Boolean(leftPackageVersion && rightPackageVersion && leftPackageVersion === rightPackageVersion);
}
function buildPluginManifestRegistry(params) {
	const config = params.config ?? {};
	const normalized = normalizePluginsConfigWithResolverCore(config.plugins);
	const env = params.env ?? process.env;
	const registryPath = params.registryPath ?? resolveInstalledPluginIndexStorePath({ env });
	const { getInstallRecords } = params;
	const discovery = params.candidates ? {
		candidates: params.candidates,
		diagnostics: params.diagnostics ?? []
	} : params.discovery ?? discoverOpenClawPlugins({
		workspaceDir: params.workspaceDir,
		extraPaths: normalized.loadPaths,
		env,
		installRecords: getInstallRecords()
	});
	const discovered = new Set(discovery.diagnostics);
	const diagnostics = [...discovered];
	const candidates = discovery.candidates.toSorted((left, right) => Number(right.origin === "config" || right.configSelected === true) - Number(left.origin === "config" || left.configSelected === true));
	const seenIds = /* @__PURE__ */ new Map();
	const manifestWarnings = /* @__PURE__ */ new Map();
	const reportedExplicitOverrides = /* @__PURE__ */ new Set();
	const currentHostVersion = resolveCompatibilityHostVersion(env);
	const explicitConfiguredFileSources = new Set(normalized.loadPaths.map((loadPath) => resolveUserPath(loadPath, env)).filter((loadPath) => pluginCacheStatSync(loadPath)?.isFile() === true).map((loadPath) => path.resolve(loadPath)));
	for (const candidate of candidates) {
		const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
			origin: candidate.origin,
			rootDir: candidate.rootDir,
			env
		});
		const isBundleRecord = (candidate.format ?? "openclaw") === "bundle";
		const isManifestlessConfiguredFile = candidate.origin === "config" && explicitConfiguredFileSources.has(path.resolve(candidate.source)) && !pluginCacheExistsSync(path.join(candidate.rootDir, "openclaw.plugin.json"));
		if (isManifestlessConfiguredFile && isCoreReservedPluginId(candidate.idHint)) {
			diagnostics.push({
				level: "error",
				pluginId: candidate.idHint,
				source: candidate.source,
				message: `plugin manifest id "${candidate.idHint}" is reserved by OpenClaw core`
			});
			continue;
		}
		const manifestRes = candidate.origin === "bundled" && candidate.bundledManifest && candidate.bundledManifestPath ? {
			ok: true,
			manifest: candidate.bundledManifest,
			manifestPath: candidate.bundledManifestPath
		} : isBundleRecord && candidate.bundleFormat ? loadBundleManifest({
			rootDir: candidate.rootDir,
			bundleFormat: candidate.bundleFormat,
			rejectHardlinks
		}) : isManifestlessConfiguredFile ? {
			ok: true,
			manifest: {
				id: candidate.idHint,
				configSchema: {
					type: "object",
					additionalProperties: false
				}
			},
			manifestPath: candidate.source
		} : loadPluginManifest(candidate.rootDir, rejectHardlinks);
		if (!manifestRes.ok) {
			diagnostics.push({
				level: "error",
				pluginId: candidate.diagnosticIdHint ?? candidate.idHint,
				message: manifestRes.error,
				source: manifestRes.manifestPath,
				..."diagnosticCode" in manifestRes && manifestRes.diagnosticCode ? { code: manifestRes.diagnosticCode } : {}
			});
			continue;
		}
		if ("warnings" in manifestRes && manifestRes.warnings) manifestWarnings.set(manifestRes.manifestPath, manifestRes.warnings);
		const manifest = manifestRes.manifest;
		const effectivePluginId = candidate.effectivePluginId ?? manifest.id;
		if (candidate.origin !== "bundled") {
			const packageManifestSource = path.join(candidate.packageDir ?? candidate.rootDir, "package.json");
			const allowLegacyBareMinHostVersion = candidate.origin === "global" && matchesInstalledPluginRecord({
				pluginId: effectivePluginId,
				candidate,
				config,
				env,
				installRecords: getInstallRecords()
			});
			const minHostVersionCheck = checkMinHostVersion({
				currentVersion: currentHostVersion,
				minHostVersion: candidate.packageManifest?.install?.minHostVersion,
				allowLegacyBareSemver: allowLegacyBareMinHostVersion
			});
			if (!minHostVersionCheck.ok) {
				diagnostics.push({
					level: minHostVersionCheck.kind === "invalid" ? "error" : "warn",
					pluginId: effectivePluginId,
					source: packageManifestSource,
					message: minHostVersionCheck.kind === "invalid" ? `plugin manifest invalid | ${minHostVersionCheck.error}` : minHostVersionCheck.kind === "unknown_host_version" ? `plugin requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host version could not be determined; skipping load` : `plugin requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host is ${minHostVersionCheck.currentVersion}; skipping load`
				});
				continue;
			}
			const packagePluginApiRangeCheck = resolvePackagePluginApiRange(candidate.packageManifest);
			if (!packagePluginApiRangeCheck.ok) {
				diagnostics.push({
					level: "error",
					pluginId: effectivePluginId,
					source: packageManifestSource,
					message: `plugin manifest invalid | ${packagePluginApiRangeCheck.error}`
				});
				continue;
			}
			const packagePluginApiRange = packagePluginApiRangeCheck.range;
			if (packagePluginApiRange && !satisfiesPluginApiRange(currentHostVersion, packagePluginApiRange)) {
				diagnostics.push({
					level: "warn",
					configDisposition: "preserve",
					pluginId: effectivePluginId,
					source: packageManifestSource,
					message: `plugin requires plugin API ${packagePluginApiRange}, but this host is ${currentHostVersion}; skipping load (check "openclaw --version", OPENCLAW_COMPATIBILITY_HOST_VERSION, or run "openclaw doctor")`
				});
				continue;
			}
		}
		const configSchema = "configSchema" in manifest ? manifest.configSchema : void 0;
		const schemaCacheKey = (() => {
			if (!configSchema || isManifestlessConfiguredFile) return;
			const file = readPluginCacheFile({
				rootDir: candidate.rootDir,
				relativePath: path.relative(candidate.rootDir, manifestRes.manifestPath),
				rejectHardlinks,
				maxBytes: 262144
			});
			return file.ok ? `${manifestRes.manifestPath}:${file.hash}` : manifestRes.manifestPath;
		})();
		const record = isBundleRecord ? buildBundleManifestRecord({
			manifest,
			candidate,
			manifestPath: manifestRes.manifestPath,
			rejectHardlinks
		}) : buildPluginManifestRecord({
			manifest,
			candidate,
			manifestPath: manifestRes.manifestPath,
			diagnostics,
			rejectHardlinks,
			schemaCacheKey,
			configSchema,
			trust: resolvePluginTrust({
				registryPath,
				pluginId: effectivePluginId,
				candidate,
				env,
				installRecords: getInstallRecords()
			}),
			...params.bundledChannelConfigCollector ? { bundledChannelConfigCollector: params.bundledChannelConfigCollector } : {}
		});
		if (candidate.sourcePreferred || candidate.origin === "bundled" && candidate.configSelected) record.sourcePreferred = true;
		recordPluginManifestInstallOwner(record, resolvePluginCandidateInstallOwner(candidate), isPluginCandidateInstallOwnerAmbiguous(candidate));
		const existing = seenIds.get(effectivePluginId);
		if (existing) {
			const samePath = existing.candidate.rootDir === candidate.rootDir;
			if ((() => {
				if (samePath) return true;
				const existingReal = pluginCacheRealpathSync(existing.candidate.rootDir);
				const candidateReal = pluginCacheRealpathSync(candidate.rootDir);
				return Boolean(existingReal && candidateReal && existingReal === candidateReal);
			})()) {
				if (record.sourcePreferred || existing.record.sourcePreferred) {
					record.sourcePreferred = true;
					existing.record.sourcePreferred = true;
				}
				if (PLUGIN_ORIGIN_RANK[candidate.origin] < PLUGIN_ORIGIN_RANK[existing.candidate.origin]) seenIds.set(effectivePluginId, {
					candidate,
					record
				});
				continue;
			}
			const candidateRank = resolveDuplicatePrecedenceRank({
				pluginId: effectivePluginId,
				candidate,
				config,
				env,
				installRecords: getInstallRecords()
			});
			const existingRank = resolveDuplicatePrecedenceRank({
				pluginId: effectivePluginId,
				candidate: existing.candidate,
				config,
				env,
				installRecords: getInstallRecords()
			});
			const candidateWins = candidateRank < existingRank;
			const winnerCandidate = candidateWins ? candidate : existing.candidate;
			const overriddenCandidate = candidateWins ? existing.candidate : candidate;
			if (candidateWins) seenIds.set(effectivePluginId, {
				candidate,
				record
			});
			if (isIntentionalInstalledBundledDuplicate({
				pluginId: effectivePluginId,
				left: candidate,
				right: existing.candidate,
				config,
				env,
				installRecords: getInstallRecords()
			})) continue;
			if (isSameGlobalPackageDuplicate(candidate, existing.candidate)) continue;
			const staleForeignPin = winnerCandidate.origin === "bundled" && isStaleForeignBundledPin({
				candidate: overriddenCandidate,
				env
			}) && matchesInstalledPluginRecord({
				pluginId: effectivePluginId,
				candidate: overriddenCandidate,
				env,
				installRecords: getInstallRecords()
			});
			const explicitOverride = !staleForeignPin && Math.min(candidateRank, existingRank) === 0 && candidateRank !== existingRank;
			if (explicitOverride) {
				if (reportedExplicitOverrides.has(effectivePluginId)) continue;
				reportedExplicitOverrides.add(effectivePluginId);
			}
			diagnostics.push({
				level: explicitOverride ? "info" : "warn",
				...explicitOverride ? { code: "explicit-config-plugin-selection" } : {},
				pluginId: effectivePluginId,
				source: overriddenCandidate.source,
				message: staleForeignPin ? `stale plugin install record: "${effectivePluginId}" is pinned to ${overriddenCandidate.rootDir}, which belongs to a different OpenClaw installation. This installation's bundled plugin is being used instead. No uninstall is needed to use the bundled plugin. Uninstalling, even with \`--keep-files\`, removes plugin configuration; re-enabling does not restore it.` : explicitOverride ? `duplicate plugin id resolved by explicit config-selected plugin; ${overriddenCandidate.origin} plugin will be overridden by config plugin (${winnerCandidate.source})` : `duplicate plugin id detected; ${overriddenCandidate.origin} plugin will be overridden by ${winnerCandidate.origin} plugin (${winnerCandidate.source})`
			});
			continue;
		}
		seenIds.set(effectivePluginId, {
			candidate,
			record
		});
	}
	const plugins = rejectCaseFoldedIdCollisions([...seenIds.values()].map(({ record }) => record), diagnostics);
	for (const record of plugins) {
		for (const warning of manifestWarnings.get(record.manifestPath) ?? []) diagnostics.push({
			level: "warn",
			pluginId: sanitizeForLog(record.id),
			source: sanitizeForLog(record.manifestPath),
			message: sanitizeForLog(warning)
		});
		pushNonBundledChannelConfigDescriptorDiagnostic({
			record,
			diagnostics,
			normalized
		});
	}
	return {
		plugins,
		diagnostics: dedupePluginDiagnostics(diagnostics, discovered)
	};
}
//#endregion
//#region src/plugins/manifest-registry.ts
function loadPluginManifestRegistryCore(params = {}) {
	if (!params.candidates && !params.discovery && !params.installRecords) {
		const gatewaySnapshot = getGatewayPluginMetadataSnapshot();
		if (gatewaySnapshot) return gatewaySnapshot.manifestRegistry;
	}
	const env = params.env ?? process.env;
	let installRecords = params.installRecords;
	return buildPluginManifestRegistry({
		...params,
		env,
		getInstallRecords: () => installRecords ??= loadInstalledPluginIndexInstallRecordsSync({ env })
	});
}
//#endregion
//#region src/plugins/manifest-registry-installed.ts
function resolveInstalledManifestRegistryIndexFingerprint(index) {
	const facts = getInstalledPluginIndexFacts(index);
	const cached = facts?.fingerprint;
	if (cached) return cached;
	const fingerprint = hashStableJson({
		version: index.version,
		hostContractVersion: index.hostContractVersion,
		compatRegistryVersion: index.compatRegistryVersion,
		migrationVersion: index.migrationVersion,
		policyHash: index.policyHash,
		installRecords: index.installRecords,
		diagnostics: index.diagnostics,
		plugins: index.plugins.map(({ doctorContractFile: _doctorContractFile, sourceAdmissions: _sourceAdmissions, packageBuild, ...plugin }) => ({
			...plugin,
			...packageBuild?.bundledDist === void 0 ? {} : { packageBuild: { bundledDist: packageBuild.bundledDist } }
		}))
	});
	if (facts) facts.fingerprint = fingerprint;
	return fingerprint;
}
//#endregion
//#region src/plugins/plugin-control-plane-context.ts
function resolveConfiguredPluginLoadPaths(config) {
	const paths = config?.plugins?.load?.paths;
	return Array.isArray(paths) ? paths : void 0;
}
/** Resolves plugin discovery roots and load paths for cache/fingerprint callers. */
function resolvePluginDiscoveryContext(params = {}) {
	return resolvePluginCacheInputs({
		env: params.env ?? process.env,
		workspaceDir: params.workspaceDir,
		loadPaths: params.loadPaths ?? resolveConfiguredPluginLoadPaths(params.config)
	});
}
/** Resolves a stable fingerprint for plugin control-plane activation state. */
function resolvePluginControlPlaneFingerprint(params = {}) {
	const inventoryFingerprint = params.inventoryFingerprint ?? (params.index ? resolveInstalledManifestRegistryIndexFingerprint(params.index) : void 0);
	return hashJson({
		discovery: resolvePluginDiscoveryContext(params),
		policyFingerprint: params.policyHash ?? resolveInstalledPluginIndexPolicyHash(params.config, params.env),
		...inventoryFingerprint ? { inventoryFingerprint } : {},
		...params.activationFingerprint ? { activationFingerprint: params.activationFingerprint } : {}
	});
}
//#endregion
//#region src/plugins/plugin-metadata-env.ts
const PLUGIN_METADATA_ENV_KEYS = [
	"ANDROID_DATA",
	"APPDATA",
	"HOME",
	"OPENCLAW_BUNDLED_PLUGINS_DIR",
	"OPENCLAW_COMPATIBILITY_HOST_VERSION",
	"OPENCLAW_CONFIG_PATH",
	"OPENCLAW_DEV_SOURCE_ROOT",
	"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
	"OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS",
	"OPENCLAW_HOME",
	"OPENCLAW_NIX_MODE",
	"OPENCLAW_STATE_DIR",
	"PREFIX",
	"USERPROFILE",
	"XDG_CONFIG_HOME"
];
/** Compares discovery namespaces without resolving or probing filesystem roots. */
function resolvePluginMetadataEnvFingerprint(env = process.env) {
	return hashJson({
		env: Object.fromEntries(PLUGIN_METADATA_ENV_KEYS.flatMap((key) => {
			const value = env[key];
			return value === void 0 ? [] : [[key, value]];
		})),
		installRoots: hasActivePluginInstallRoots() ? resolveActivePluginInstallRoots() : void 0,
		trustBundledPluginsDirOverride: shouldTrustTestBundledPluginsDirOverride(env),
		cwd: tryProcessCwd()
	});
}
//#endregion
//#region src/plugins/plugin-scope.ts
/** Normalizes plugin id scope input into a sorted unique string list. */
function normalizePluginIdScope(ids) {
	return ids === void 0 ? void 0 : normalizeSortedUniqueTrimmedStringList(ids);
}
/** Serializes plugin scope for cache keys. */
function serializePluginIdScope(ids) {
	return ids === void 0 ? "__unscoped__" : JSON.stringify(ids);
}
//#endregion
//#region src/plugins/current-plugin-metadata-snapshot.ts
function resolveAgentWorkspaceFingerprint(config, env) {
	return JSON.stringify(listAgentWorkspaceDirs(config, env));
}
function prepareCurrentPluginMetadataSnapshotPublication(snapshot, options, owner = "operation") {
	const fingerprint = (config, policyHash) => resolvePluginControlPlaneFingerprint({
		config,
		env: options.env,
		index: snapshot.index,
		policyHash,
		workspaceDir: options.workspaceDir ?? snapshot.workspaceDir
	});
	const compatiblePolicyHashes = options.compatibleConfigs?.map((config) => resolveInstalledPluginIndexPolicyHash(config, options.env));
	const compatibleConfigFingerprints = options.compatibleConfigs?.map((config, index) => fingerprint(config, compatiblePolicyHashes?.[index]));
	const configFingerprint = fingerprint(options.config, snapshot.policyHash);
	const defaultDiscoveryConfigFingerprint = fingerprint({}, snapshot.policyHash);
	const defaultDiscoveryCompatible = configFingerprint === defaultDiscoveryConfigFingerprint || snapshot.configFingerprint === defaultDiscoveryConfigFingerprint || Boolean(compatibleConfigFingerprints?.includes(defaultDiscoveryConfigFingerprint));
	const envFingerprint = resolvePluginMetadataEnvFingerprint(options.env);
	const agentWorkspaceFingerprint = owner === "gateway" && options.config ? resolveAgentWorkspaceFingerprint(options.config, options.env) : void 0;
	const configIdentities = [...options.compatibleConfigs ?? []];
	if (options.config) {
		const policyHash = resolveInstalledPluginIndexPolicyHash(options.config, options.env);
		if (policyHash === snapshot.policyHash || Boolean(compatiblePolicyHashes?.includes(policyHash))) configIdentities.push(options.config);
	}
	return () => {
		if (getCurrentPluginMetadataSnapshotState().owner === "gateway" && owner !== "gateway") throw new Error("Gateway plugin metadata can only be replaced after shutdown");
		currentPluginMetadataConfigIdentityCache.clear();
		setCurrentPluginMetadataSnapshotState(snapshot, configFingerprint, compatiblePolicyHashes, compatibleConfigFingerprints, owner === "gateway" || defaultDiscoveryCompatible ? snapshot.owners.modelIdNormalizationPolicies : void 0, owner, envFingerprint, defaultDiscoveryCompatible, agentWorkspaceFingerprint);
		for (const config of configIdentities) currentPluginMetadataConfigIdentityCache.add(config);
	};
}
/** Publishes a prepared CLI snapshot without displacing a lifecycle owner. */
function adoptCurrentPluginMetadataSnapshotIfAbsent(snapshot, options = {}) {
	if (getScopedPluginCache()?.kind === "operation" || getCurrentPluginMetadataSnapshotState().snapshot !== void 0) return;
	prepareCurrentPluginMetadataSnapshotPublication(snapshot, options)();
}
/** Installation revokes operation facts even when it runs between metadata scopes. */
function revokeCurrentPluginMetadataSnapshotScopes() {
	const caches = new Set(getScopedPluginCaches());
	const runtimeCaches = /* @__PURE__ */ new Set();
	for (let scoped = getPluginExecutionFrame()?.metadataScope; scoped; scoped = scoped.parent) if (scoped.immutableRuntimeGeneration) runtimeCaches.add(scoped.cache);
	else caches.add(scoped.cache);
	for (const cache of caches) if (cache.kind === "operation" && !runtimeCaches.has(cache)) invalidatePluginCacheMetadata(cache);
}
function isScopedSnapshotInCurrentCache(scoped) {
	if (!scoped.immutableRuntimeGeneration && scoped.metadata !== scoped.cache.metadata) return false;
	const cache = getScopedPluginCache();
	return cache?.kind !== "operation" || scoped.cache === cache;
}
const NEEDS_PREPARED_POLICY = Symbol("needs-prepared-policy");
function resolveCompatiblePluginMetadataSnapshot(candidate, params, options = {}) {
	const snapshot = candidate.snapshot;
	if (!snapshot) return;
	if (candidate.immutableRuntimeGeneration) return snapshot;
	const env = params.env ?? process.env;
	if (candidate.envFingerprint !== resolvePluginMetadataEnvFingerprint(env)) return;
	const requestedPluginIds = normalizePluginIdScope(params.pluginIds ?? params.pluginIdScope?.resolve({ index: snapshot.index }));
	const snapshotPluginIds = normalizePluginIdScope(snapshot.pluginIds);
	if (requestedPluginIds !== void 0 && serializePluginIdScope(snapshotPluginIds) !== serializePluginIdScope(requestedPluginIds)) return;
	if (snapshotPluginIds !== void 0 && requestedPluginIds === void 0 && params.allowScopedSnapshot !== true) return;
	const requestedWorkspaceDir = params.workspaceDir ?? (params.allowWorkspaceScopedSnapshot === true || options.scopedOwnerContext === true ? snapshot.workspaceDir : void 0);
	if (snapshot.workspaceDir !== void 0 && requestedWorkspaceDir === void 0) return;
	if (requestedWorkspaceDir !== void 0 && (snapshot.workspaceDir ?? "") !== (requestedWorkspaceDir ?? "")) return;
	const canReuseCachedConfig = Boolean(params.config && candidate.hasConfigIdentity?.(params.config));
	if (canReuseCachedConfig && params.requireDefaultDiscoveryContext !== true) return snapshot;
	if (params.config && !canReuseCachedConfig && params.allowSynchronousPolicyRead === false) return NEEDS_PREPARED_POLICY;
	const requestedPolicyHash = params.config && !canReuseCachedConfig ? resolveInstalledPluginIndexPolicyHash(params.config, params.env) : void 0;
	if (requestedPolicyHash && snapshot.policyHash !== requestedPolicyHash) {
		if (!candidate.compatiblePolicyHashes?.includes(requestedPolicyHash)) return;
	}
	if (params.config && !canReuseCachedConfig) {
		const requestedConfigFingerprint = resolvePluginControlPlaneFingerprint({
			config: params.config,
			env,
			index: snapshot.index,
			policyHash: requestedPolicyHash,
			workspaceDir: requestedWorkspaceDir
		});
		if (!(candidate.configFingerprint === requestedConfigFingerprint || snapshot.configFingerprint === requestedConfigFingerprint || Boolean(candidate.compatibleConfigFingerprints?.includes(requestedConfigFingerprint)))) return;
	}
	if (params.requireDefaultDiscoveryContext === true && options.scopedOwnerContext !== true && candidate.defaultDiscoveryCompatible !== true) return;
	return snapshot;
}
function getCurrentPluginMetadataSnapshot(params = {}) {
	for (let scoped = getPluginExecutionFrame()?.metadataScope; scoped; scoped = scoped.parent) {
		if (!isScopedSnapshotInCurrentCache(scoped)) continue;
		const compatibleScoped = resolveCompatiblePluginMetadataSnapshot(scoped, params, { scopedOwnerContext: true });
		if (compatibleScoped === NEEDS_PREPARED_POLICY) return;
		if (compatibleScoped) return compatibleScoped;
	}
	const scopedCache = getScopedPluginCache();
	if (scopedCache && scopedCache !== getProcessPluginCache()) return;
	const state = getCurrentPluginMetadataSnapshotState();
	const compatible = resolveCompatiblePluginMetadataSnapshot({
		...state,
		hasConfigIdentity: (config) => currentPluginMetadataConfigIdentityCache.has(config),
		immutableRuntimeGeneration: state.owner === "gateway"
	}, params);
	return compatible === NEEDS_PREPARED_POLICY ? void 0 : compatible;
}
registerPluginMetadataSnapshotReaders({
	adoptCurrentPluginMetadataSnapshotIfAbsent,
	getCurrentPluginMetadataSnapshot
});
registerPluginMetadataProcessMemoLifecycleClear(revokeCurrentPluginMetadataSnapshotScopes, { owner: "operation" });
//#endregion
//#region src/utils/run-with-concurrency.ts
/** Runs async tasks with bounded concurrency while preserving result indexes. */
async function runTasksWithConcurrency(params) {
	const { tasks, limit, onTaskError, throwOnError = false } = params;
	const errorMode = params.errorMode ?? "continue";
	if (tasks.length === 0) return {
		results: [],
		firstError: void 0,
		hasError: false
	};
	const resolvedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(Math.floor(limit), tasks.length)) : tasks.length;
	const results = Array.from({ length: tasks.length });
	let firstError = void 0;
	let hasError = false;
	return new Promise((resolve, reject) => {
		const runOne = async (task, index) => {
			if (errorMode === "stop" && hasError) return;
			try {
				results[index] = await task();
			} catch (error) {
				if (!hasError) {
					firstError = error;
					hasError = true;
				}
				let rejectionError = error;
				try {
					onTaskError?.(error, index);
				} catch (callbackError) {
					rejectionError = callbackError;
				}
				if (throwOnError) reject(rejectionError);
			}
		};
		const mapper = AsyncLocalStorage.bind((task, index) => Promise.resolve().then(() => runOne(task, index)));
		pMap(tasks.slice(), mapper, { concurrency: resolvedLimit }).then(() => resolve({
			results,
			firstError,
			hasError
		}), reject);
	});
}
//#endregion
//#region src/secrets/exec-provider-path-validation.ts
/** Checks the same command-path trust boundary before validation, writes, and execution. */
async function assertSecureExecCommandPath(params) {
	const commandPath = resolveUserPath(params.command);
	if (!commandPath) throw new Error(`${params.label} must be an absolute path.`);
	const stat = await safeStat(commandPath);
	if (!stat.ok) throw new Error(`${params.label} is not readable: ${commandPath}`);
	if (stat.isDir) throw new Error(`${params.label} must be a file: ${commandPath}`);
	if (stat.isSymlink) throw new Error(`${params.label} must not be a symlink: ${commandPath}`);
	if (params.trustedDirs && params.trustedDirs.length > 0) {
		if (!params.trustedDirs.map((entry) => resolveUserPath(entry)).some((dir) => isPathInside(dir, commandPath))) throw new Error(`${params.label} is outside trustedDirs: ${commandPath}`);
	}
	const perms = await inspectPathPermissions(commandPath);
	if (!perms.ok) throw new Error(`${params.label} permissions could not be verified: ${commandPath}`);
	if (perms.worldWritable || perms.groupWritable) throw new Error(`${params.label} permissions are too open: ${commandPath}`);
	if (process.platform === "win32" && perms.source === "unknown") throw new FsSafeError("permission-unverified", `${params.label} ACL verification unavailable on Windows for ${commandPath}. Move the command to a path whose ACLs OpenClaw can verify; there is no provider-level bypass.`);
	if (process.platform !== "win32" && typeof process.getuid === "function" && stat.uid != null) {
		const uid = process.getuid();
		if (stat.uid !== uid) throw new Error(`${params.label} must be owned by the current user (uid=${uid}): ${commandPath}`);
	}
	return commandPath;
}
//#endregion
//#region src/secrets/json-pointer.ts
/** JSON Pointer token helpers for file-backed secret refs. */
function failOrUndefined(params) {
	if (params.onMissing === "throw") throw new Error(params.message);
}
function decodeJsonPointerToken(token) {
	return token.replace(/~1/g, "/").replace(/~0/g, "~");
}
/**
* Reads a value from a JSON-like document using an absolute JSON Pointer.
* Missing segments throw by default; `onMissing: "undefined"` is for optional probes.
*/
function readJsonPointer(root, pointer, options = {}) {
	const onMissing = options.onMissing ?? "throw";
	if (!pointer.startsWith("/")) return failOrUndefined({
		onMissing,
		message: "File-backed secret ids must be absolute JSON pointers (for example: \"/providers/openai/apiKey\")."
	});
	const tokens = pointer.slice(1).split("/").map((token) => decodeJsonPointerToken(token));
	let current = root;
	for (const token of tokens) {
		if (Array.isArray(current)) {
			const index = parseConfigPathArrayIndex(token);
			if (index === void 0 || index >= current.length) return failOrUndefined({
				onMissing,
				message: `JSON pointer segment "${token}" is out of bounds.`
			});
			current = current[index];
			continue;
		}
		if (!isRecord(current) || !Object.hasOwn(current, token)) return failOrUndefined({
			onMissing,
			message: `JSON pointer segment "${token}" does not exist.`
		});
		current = current[token];
	}
	return current;
}
//#endregion
//#region src/plugins/manifest-owner-policy.ts
/** Resolves whether a manifest owner plugin is effectively activated. */
function isActivatedManifestOwner(params) {
	return resolveEffectivePluginActivationState({
		id: params.plugin.id,
		origin: params.plugin.origin,
		channelIds: params.plugin.channels,
		config: params.normalizedConfig,
		rootConfig: params.rootConfig,
		enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin)
	}).activated;
}
//#endregion
//#region src/secrets/provider-integrations.ts
/** Materializes trusted plugin secret-provider integrations into exec provider configs. */
const NODE_COMMAND_PLACEHOLDER = "${node}";
function resolvePluginRelativePath(value, pluginRoot) {
	const resolved = path.resolve(pluginRoot, value);
	return isPathInside$2(pluginRoot, resolved) ? resolved : void 0;
}
function resolveArg(arg, pluginRoot) {
	if (!arg.startsWith("./") && !arg.startsWith("../")) return arg;
	return resolvePluginRelativePath(arg, pluginRoot);
}
function withNodeCommandTrustedDir(command, pluginRoot) {
	return command === NODE_COMMAND_PLACEHOLDER ? [.../* @__PURE__ */ new Set([path.dirname(process.execPath), pluginRoot])] : [pluginRoot];
}
function isSecurePosixPathStat(stat) {
	if (process.platform === "win32") return true;
	if ((stat.mode & 18) !== 0) return false;
	if (typeof process.getuid !== "function" || typeof stat.uid !== "number") return true;
	const uid = process.getuid();
	return stat.uid === uid || stat.uid === 0;
}
function resolveNodeEntrypointArg(params) {
	const entrypoint = params.integration.args?.[0];
	if (!entrypoint?.startsWith("./")) return;
	const resolved = resolvePluginRelativePath(entrypoint, params.pluginRoot);
	if (!resolved) return;
	try {
		const opened = openRootFileSync({
			absolutePath: resolved,
			rootPath: params.pluginRoot,
			boundaryLabel: "plugin root",
			rejectHardlinks: params.rejectHardlinks,
			symlinks: process.platform === "win32" ? "follow-parents-within-root" : "reject"
		});
		if (!opened.ok) return;
		try {
			if (!isSecurePosixPathStat(opened.stat)) return;
			if (process.platform !== "win32") for (let directory = path.dirname(opened.path);; directory = path.dirname(directory)) {
				const stat = fsSync.lstatSync(directory);
				if (!stat.isDirectory() || !isSecurePosixPathStat(stat)) return;
				if (directory === opened.rootRealPath) break;
			}
			return opened.path;
		} finally {
			fsSync.closeSync(opened.fd);
		}
	} catch {
		return;
	}
}
function materializeExecProviderConfig(integration, record, env) {
	const pluginRoot = record.rootDir;
	if (integration.command !== NODE_COMMAND_PLACEHOLDER) return;
	const nodeEntrypoint = resolveNodeEntrypointArg({
		integration,
		pluginRoot,
		rejectHardlinks: shouldRejectHardlinkedPluginFiles({
			origin: record.origin,
			rootDir: pluginRoot,
			env
		})
	});
	if (!nodeEntrypoint) return;
	const args = integration.args?.map((arg, index) => nodeEntrypoint && index === 0 ? nodeEntrypoint : resolveArg(arg, pluginRoot)).filter((arg) => arg !== void 0);
	if (integration.args && args?.length !== integration.args.length) return;
	const trustedDirs = withNodeCommandTrustedDir(integration.command, pluginRoot);
	return {
		source: "exec",
		command: process.execPath,
		...args ? { args } : {},
		...integration.timeoutMs !== void 0 ? { timeoutMs: integration.timeoutMs } : {},
		...integration.noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs: integration.noOutputTimeoutMs } : {},
		...integration.maxOutputBytes !== void 0 ? { maxOutputBytes: integration.maxOutputBytes } : {},
		...integration.jsonOnly === false ? { jsonOnly: false } : {},
		...integration.env ? { env: integration.env } : {},
		...integration.passEnv ? { passEnv: integration.passEnv } : {},
		trustedDirs
	};
}
function canExposeSecretProviderIntegrations(params) {
	if (params.record.origin !== "bundled" && params.record.origin !== "global") return false;
	return isActivatedManifestOwner({
		plugin: params.record,
		normalizedConfig: params.normalizedConfig,
		rootConfig: params.config
	});
}
/** Narrows a secret provider config to the plugin-integration exec shape. */
function isPluginIntegrationSecretProviderConfig(value) {
	return typeof value === "object" && value !== null && "source" in value && value.source === "exec" && "pluginIntegration" in value && typeof value.pluginIntegration === "object" && value.pluginIntegration !== null && "pluginId" in value.pluginIntegration && typeof value.pluginIntegration.pluginId === "string" && value.pluginIntegration.pluginId.trim().length > 0 && "integrationId" in value.pluginIntegration && typeof value.pluginIntegration.integrationId === "string" && value.pluginIntegration.integrationId.trim().length > 0;
}
/** Materializes an active trusted plugin secret-provider integration into an exec provider. */
/** Resolves a trusted plugin secret-provider integration into executable provider config. */
function resolveSecretProviderIntegrationConfig(params) {
	const config = params.config ?? {};
	const normalizedConfig = normalizePluginsConfig(config.plugins);
	const env = params.env ?? process.env;
	const { pluginId, integrationId } = params.providerConfig.pluginIntegration;
	if (!isValidSecretProviderAlias(params.providerAlias)) return {
		ok: false,
		reason: `provider alias "${params.providerAlias}" is invalid`
	};
	const record = params.manifestRegistry.plugins.find((candidate) => candidate.id === pluginId);
	if (!record) return {
		ok: false,
		reason: `plugin "${pluginId}" is not installed`
	};
	if (!canExposeSecretProviderIntegrations({
		record,
		normalizedConfig,
		config
	})) return {
		ok: false,
		reason: `plugin "${pluginId}" is not active or is not from a trusted install origin`
	};
	const integration = record.secretProviderIntegrations?.[integrationId];
	if (!integration) return {
		ok: false,
		reason: `plugin "${record.id}" does not declare secret provider integration "${integrationId}"`
	};
	const materialized = materializeExecProviderConfig(integration, record, env);
	if (!materialized) return {
		ok: false,
		reason: `plugin "${record.id}" integration "${integrationId}" could not be materialized`
	};
	return {
		ok: true,
		providerConfig: materialized
	};
}
//#endregion
//#region src/secrets/sentinel.ts
const SECRET_SENTINEL_SOURCE = "oc-sent-v2\\.[A-Za-z0-9_-]+\\.end";
11 + Math.ceil(262288 / 3) + 4;
new RegExp(SECRET_SENTINEL_SOURCE, "g");
const secretSentinelKeys = resolveGlobalSingleton(Symbol.for("openclaw.secretSentinel.keys"), () => ({ keys: randomBytes(64) })).keys;
secretSentinelKeys.subarray(0, 32);
secretSentinelKeys.subarray(32);
//#endregion
//#region src/secrets/store/secret-store-sqlite.ts
function isMissingSecretStoreTableError(error) {
	return error instanceof Error && hasErrnoCode(error, "ERR_SQLITE_ERROR") && error.message === "no such table: secret_store_entries";
}
//#endregion
//#region src/secrets/store/secret-store-validation-error.ts
var SecretStoreValidationError = class extends Error {
	constructor(code, message) {
		super(message);
		this.code = code;
		this.name = "SecretStoreValidationError";
	}
};
const SECRET_STORE_VALUE_MAX_BYTES = 65536;
//#endregion
//#region src/secrets/store/secret-store-validation.ts
function normalizeScope(_scope) {
	return {
		scopeKind: "team",
		scopeId: ""
	};
}
function assertSecretStoreEnvName(name) {
	if (!ENV_SECRET_REF_ID_RE$1.test(name)) throw new SecretStoreValidationError("SECRET_STORE_INVALID_NAME", `Secret store name must match ${String(ENV_SECRET_REF_ID_RE$1)}.`);
}
createSubsystemLogger("secrets/store");
function readSecretStoreValue(params) {
	try {
		assertSecretStoreEnvName(params.name);
		const { scopeKind, scopeId } = normalizeScope(params.scope);
		const row = withExistingOpenClawStateDatabaseReadOnly(({ db: sqlite }) => {
			const db = getNodeSqliteKysely(sqlite);
			return executeSqliteQueryTakeFirstSync(sqlite, db.selectFrom("secret_store_entries").select(["value", "kind"]).where("scope_kind", "=", scopeKind).where("scope_id", "=", scopeId).where("name", "=", params.name).where("deleted_at_ms", "is", null));
		}, params.database ?? {});
		if (!row) return err({
			code: "SECRET_STORE_NOT_FOUND",
			message: `Secret store entry "${params.name}" was not found.`
		});
		if (row.kind === "secret") registerSecretValueForRedaction(row.value);
		return ok(row.value);
	} catch (error) {
		if (isMissingSecretStoreTableError(error)) return err({
			code: "SECRET_STORE_NOT_FOUND",
			message: `Secret store entry "${params.name}" was not found.`
		});
		if (error instanceof SecretStoreValidationError) return err({
			code: "SECRET_STORE_INVALID_NAME",
			message: error.message
		});
		return err({
			code: "SECRET_STORE_UNAVAILABLE",
			message: "Secret store database is unavailable.",
			cause: error
		});
	}
}
//#endregion
//#region src/secrets/resolve-store.ts
const STORE_SECRET_REF_BATCH_MAX_BYTES = 512 * SECRET_STORE_VALUE_MAX_BYTES;
function resolveStoreRefs(params) {
	const resolved = /* @__PURE__ */ new Map();
	let resolvedBytes = 0;
	for (const ref of params.refs) {
		const result = readSecretStoreValue({
			scope: { kind: "team" },
			name: ref.id,
			database: params.database
		});
		if (!result.ok) {
			if (result.error.code === "SECRET_STORE_NOT_FOUND") {
				params.onRefError(refResolutionError({
					code: "SECRET_REF_NOT_FOUND",
					source: "store",
					provider: params.providerName,
					refId: ref.id,
					message: result.error.message
				}));
				continue;
			}
			if (result.error.code === "SECRET_STORE_INVALID_NAME") {
				params.onRefError(refResolutionError({
					code: "SECRET_REF_INVALID",
					source: "store",
					provider: params.providerName,
					refId: ref.id,
					message: result.error.message
				}));
				continue;
			}
			throw providerResolutionError({
				code: "SECRET_PROVIDER_UNAVAILABLE",
				source: "store",
				provider: params.providerName,
				message: result.error.message,
				cause: result.error.cause
			});
		}
		resolvedBytes += Buffer.byteLength(result.value, "utf8");
		if (resolvedBytes > STORE_SECRET_REF_BATCH_MAX_BYTES) throw providerResolutionError({
			code: "SECRET_PROVIDER_INVALID",
			source: "store",
			provider: params.providerName,
			message: `Store provider "${params.providerName}" exceeded its ${STORE_SECRET_REF_BATCH_MAX_BYTES}-byte batch limit.`
		});
		resolved.set(ref.id, result.value);
	}
	return resolved;
}
//#endregion
//#region src/secrets/shared.ts
/** Shared parsing helpers for secrets migration/runtime code. */
/**
* Narrows to strings that contain non-whitespace content.
*/
function isNonEmptyString(value) {
	return typeof value === "string" && value.trim().length > 0;
}
/**
* Normalizes numeric config to a positive integer, falling back when the input is not finite.
*/
function normalizePositiveInt(value, fallback) {
	if (typeof value === "number" && Number.isFinite(value)) return Math.max(1, Math.floor(value));
	return Math.max(1, Math.floor(fallback));
}
/**
* Normalizes timer values with the shared timeout coercion rules used by secret providers.
*/
function normalizePositiveTimerMs(value, fallback) {
	return resolvePositiveTimerTimeoutMs(value, fallback);
}
//#endregion
//#region src/secrets/resolve.ts
/** Resolves SecretRef values from env, file, exec, and store secret providers. */
const DEFAULT_PROVIDER_CONCURRENCY = 4;
const DEFAULT_MAX_REFS_PER_PROVIDER = 512;
const DEFAULT_MAX_BATCH_BYTES = 262144;
const DEFAULT_FILE_MAX_BYTES = 1048576;
const DEFAULT_FILE_TIMEOUT_MS = 5e3;
const DEFAULT_EXEC_TIMEOUT_MS = 5e3;
const DEFAULT_EXEC_MAX_OUTPUT_BYTES = 1048576;
const SAFE_EXEC_ERROR_CODES = /* @__PURE__ */ new Set(["AMBIGUOUS_DUPLICATE_KEY", "NOT_FOUND"]);
function throwUnknownProviderResolutionError(params) {
	if (isSecretResolutionError(params.err)) throw params.err;
	throw providerResolutionError({
		...process.platform === "win32" && (params.source === "file" || params.source === "exec") && params.err instanceof FsSafeError && params.err.code === "permission-unverified" ? { code: "SECRET_PROVIDER_PATH_SECURITY_UNVERIFIABLE" } : {},
		source: params.source,
		provider: params.provider,
		message: formatErrorMessage(params.err),
		cause: params.err
	});
}
function resolveConfiguredProvider(params) {
	const { ref, config } = params;
	const providerConfig = config.secrets?.providers?.[ref.provider];
	if (isBuiltInDefaultSecretProviderRef(config, ref)) {
		if (ref.source === "env") return { source: "env" };
		if (ref.source === "store") return { source: "store" };
	}
	if (!providerConfig) throw providerResolutionError({
		code: "SECRET_PROVIDER_NOT_CONFIGURED",
		source: ref.source,
		provider: ref.provider,
		message: `Secret provider "${ref.provider}" is not configured (ref: ${ref.source}:${ref.provider}:${ref.id}).`
	});
	const configuredSource = resolveSecretRefProviderSourceMismatch(config, ref);
	if (configuredSource) throw providerResolutionError({
		code: "SECRET_PROVIDER_INVALID",
		source: ref.source,
		provider: ref.provider,
		message: `Secret provider "${ref.provider}" has source "${configuredSource}" but ref requests "${ref.source}".`
	});
	if (isPluginIntegrationSecretProviderConfig(providerConfig)) {
		const resolved = resolveSecretProviderIntegrationConfig({
			manifestRegistry: params.manifestRegistry ?? getCurrentPluginMetadataSnapshot({
				config,
				env: params.env,
				allowWorkspaceScopedSnapshot: true
			})?.manifestRegistry ?? loadPluginManifestRegistryCore({
				config,
				env: params.env
			}),
			providerAlias: ref.provider,
			providerConfig,
			config,
			env: params.env
		});
		if (!resolved.ok) throw providerResolutionError({
			source: ref.source,
			provider: ref.provider,
			message: `Secret provider "${ref.provider}" plugin integration is unavailable: ${resolved.reason}.`
		});
		return resolved.providerConfig;
	}
	return providerConfig;
}
async function readFileProviderPayload(params) {
	const cacheKey = params.providerName;
	const cache = params.cache;
	const read = async () => {
		const filePath = resolveUserPath(params.providerConfig.path);
		const timeoutMs = normalizePositiveTimerMs(params.providerConfig.timeoutMs, DEFAULT_FILE_TIMEOUT_MS);
		const maxBytes = normalizePositiveInt(params.providerConfig.maxBytes, DEFAULT_FILE_MAX_BYTES);
		try {
			const { buffer: payload } = await readSecureFile({
				filePath,
				label: `secrets.providers.${params.providerName}.path`,
				io: {
					maxBytes,
					timeoutMs
				},
				permissions: { allowInsecure: false }
			});
			const text = payload.toString("utf8").replace(/^\uFEFF/, "");
			if (params.providerConfig.mode === "singleValue") return text.replace(/\r?\n$/, "");
			const parsed = JSON.parse(text);
			if (!isRecord(parsed)) throw new Error(`File provider "${params.providerName}" payload is not a JSON object.`);
			return parsed;
		} catch (error) {
			if (error instanceof FsSafeError && error.code === "timeout") throw new Error(`File provider "${params.providerName}" timed out after ${timeoutMs}ms.`, { cause: error });
			throw error;
		}
	};
	if (!cache) return await read();
	cache.filePayloadByProvider ??= /* @__PURE__ */ new Map();
	return await getOrCreatePromise(cache.filePayloadByProvider, cacheKey, read);
}
async function resolveEnvRefs(params) {
	const resolved = /* @__PURE__ */ new Map();
	const allowlist = params.providerConfig.allowlist ? new Set(params.providerConfig.allowlist) : null;
	for (const ref of params.refs) {
		if (allowlist && !allowlist.has(ref.id)) {
			params.onRefError(refResolutionError({
				code: "SECRET_REF_POLICY_DENIED",
				source: "env",
				provider: params.providerName,
				refId: ref.id,
				message: `Environment variable "${ref.id}" is not allowlisted in secrets.providers.${params.providerName}.allowlist.`
			}));
			continue;
		}
		const envValue = params.env[ref.id];
		if (!isNonEmptyString(envValue)) {
			params.onRefError(refResolutionError({
				code: "SECRET_REF_NOT_FOUND",
				source: "env",
				provider: params.providerName,
				refId: ref.id,
				message: `Environment variable "${ref.id}" is missing or empty.`
			}));
			continue;
		}
		resolved.set(ref.id, envValue);
	}
	return resolved;
}
async function resolveFileRefs(params) {
	const payload = await readFileProviderPayload({
		providerName: params.providerName,
		providerConfig: params.providerConfig,
		cache: params.cache
	});
	const mode = params.providerConfig.mode ?? "json";
	const resolved = /* @__PURE__ */ new Map();
	if (mode === "singleValue") {
		for (const ref of params.refs) {
			if (ref.id !== "value") {
				params.onRefError(refResolutionError({
					code: "SECRET_REF_INVALID",
					source: "file",
					provider: params.providerName,
					refId: ref.id,
					message: `singleValue file provider "${params.providerName}" expects ref id "${SINGLE_VALUE_FILE_REF_ID$1}".`
				}));
				continue;
			}
			resolved.set(ref.id, payload);
		}
		return resolved;
	}
	for (const ref of params.refs) try {
		resolved.set(ref.id, readJsonPointer(payload, ref.id, { onMissing: "throw" }));
	} catch (err) {
		params.onRefError(refResolutionError({
			code: "SECRET_REF_NOT_FOUND",
			source: "file",
			provider: params.providerName,
			refId: ref.id,
			message: formatErrorMessage(err),
			cause: err
		}));
	}
	return resolved;
}
function parseExecValues(params) {
	const trimmed = params.stdout.trim();
	if (!trimmed) throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" returned empty stdout.`
	});
	let parsed;
	try {
		parsed = JSON.parse(trimmed);
	} catch {
		if (!params.jsonOnly && params.ids.length === 1) return { [expectDefined(params.ids[0], "ids entry at 0")]: trimmed };
		throw providerResolutionError({
			source: "exec",
			provider: params.providerName,
			message: `Exec provider "${params.providerName}" returned invalid JSON.`
		});
	}
	if (!isRecord(parsed)) {
		if (!params.jsonOnly && params.ids.length === 1 && typeof parsed === "string") return { [expectDefined(params.ids[0], "ids entry at 0")]: parsed };
		throw providerResolutionError({
			source: "exec",
			provider: params.providerName,
			message: `Exec provider "${params.providerName}" response must be an object.`
		});
	}
	if (parsed.protocolVersion !== 1) throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" protocolVersion must be 1.`
	});
	const responseValues = parsed.values;
	if (!isRecord(responseValues)) throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" response missing "values".`
	});
	const responseErrors = isRecord(parsed.errors) ? parsed.errors : null;
	const out = {};
	for (const id of params.ids) {
		if (responseErrors && Object.hasOwn(responseErrors, id)) {
			const entry = responseErrors[id];
			const code = isRecord(entry) && typeof entry.code === "string" ? entry.code : null;
			const safeCode = code && SAFE_EXEC_ERROR_CODES.has(code) ? code : null;
			params.onRefError(refResolutionError({
				code: safeCode === "NOT_FOUND" ? "SECRET_REF_NOT_FOUND" : "SECRET_REF_PROVIDER_ERROR",
				source: "exec",
				provider: params.providerName,
				refId: id,
				message: `Exec provider "${params.providerName}" failed for id "${id}"${safeCode ? ` (${safeCode})` : ""}.`
			}));
			continue;
		}
		if (!Object.hasOwn(responseValues, id)) {
			params.onRefError(refResolutionError({
				code: "SECRET_REF_NOT_FOUND",
				source: "exec",
				provider: params.providerName,
				refId: id,
				message: `Exec provider "${params.providerName}" response missing id "${id}".`
			}));
			continue;
		}
		out[id] = responseValues[id];
	}
	return out;
}
async function resolveExecRefs(params) {
	const ids = uniqueStrings(params.refs.map((ref) => ref.id));
	const secureCommandPath = await assertSecureExecCommandPath({
		command: params.providerConfig.command,
		label: `secrets.providers.${params.providerName}.command`,
		trustedDirs: params.providerConfig.trustedDirs
	});
	const input = JSON.stringify({
		protocolVersion: 1,
		provider: params.providerName,
		ids
	});
	if (Buffer.byteLength(input, "utf8") > DEFAULT_MAX_BATCH_BYTES) throw providerResolutionError({
		code: "SECRET_PROVIDER_INVALID",
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" request exceeded maxBatchBytes (${DEFAULT_MAX_BATCH_BYTES}).`
	});
	const childEnv = {};
	for (const key of params.providerConfig.passEnv ?? []) {
		const value = params.env[key];
		if (value !== void 0) childEnv[key] = value;
	}
	for (const [key, value] of Object.entries(params.providerConfig.env ?? {})) childEnv[key] = value;
	const timeoutMs = normalizePositiveTimerMs(params.providerConfig.timeoutMs, DEFAULT_EXEC_TIMEOUT_MS);
	const noOutputTimeoutMs = normalizePositiveTimerMs(params.providerConfig.noOutputTimeoutMs, timeoutMs);
	const maxOutputBytes = normalizePositiveInt(params.providerConfig.maxOutputBytes, DEFAULT_EXEC_MAX_OUTPUT_BYTES);
	const jsonOnly = params.providerConfig.jsonOnly ?? true;
	const result = await runCommandWithTimeout([secureCommandPath, ...params.providerConfig.args ?? []], {
		baseEnv: {},
		cwd: path.dirname(secureCommandPath),
		env: childEnv,
		input,
		killProcessTree: true,
		maxCombinedOutputBytes: maxOutputBytes,
		maxOutputBytes,
		noOutputTimeoutMs,
		outputCapture: "head",
		terminateOnOutputLimit: true,
		timeoutMs
	});
	if (result.termination === "timeout") throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" timed out after ${timeoutMs}ms.`
	});
	if (result.termination === "no-output-timeout") throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" produced no output for ${noOutputTimeoutMs}ms.`
	});
	if (result.outputLimitExceeded) throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider output exceeded maxOutputBytes (${maxOutputBytes}).`
	});
	if (result.code !== 0) throw providerResolutionError({
		source: "exec",
		provider: params.providerName,
		message: `Exec provider "${params.providerName}" exited with code ${String(result.code)}.`
	});
	const values = parseExecValues({
		providerName: params.providerName,
		ids,
		stdout: result.stdout,
		jsonOnly,
		onRefError: params.onRefError
	});
	return new Map(Object.entries(values));
}
async function resolveProviderRefs(params) {
	try {
		if (params.providerConfig.source === "env") return await resolveEnvRefs({
			refs: params.refs,
			providerName: params.providerName,
			onRefError: params.onRefError,
			providerConfig: params.providerConfig,
			env: params.options.env ?? process.env
		});
		if (params.providerConfig.source === "file") return await resolveFileRefs({
			refs: params.refs,
			providerName: params.providerName,
			onRefError: params.onRefError,
			providerConfig: params.providerConfig,
			cache: params.options.cache
		});
		if (params.providerConfig.source === "store") return resolveStoreRefs({
			refs: params.refs,
			providerName: params.providerName,
			onRefError: params.onRefError,
			database: { env: params.options.env ?? process.env }
		});
		if (params.providerConfig.source === "exec") {
			if (isPluginIntegrationSecretProviderConfig(params.providerConfig)) throw providerResolutionError({
				source: params.source,
				provider: params.providerName,
				message: `Secret provider "${params.providerName}" plugin integration was not materialized before exec resolution.`
			});
			return await resolveExecRefs({
				refs: params.refs,
				providerName: params.providerName,
				onRefError: params.onRefError,
				providerConfig: params.providerConfig,
				env: params.options.env ?? process.env
			});
		}
		throw providerResolutionError({
			source: params.source,
			provider: params.providerName,
			message: `Unsupported secret provider source "${String(params.providerConfig.source)}".`
		});
	} catch (err) {
		return throwUnknownProviderResolutionError({
			source: params.source,
			provider: params.providerName,
			err
		});
	}
}
function createProviderResolutionTasks(params) {
	return params.groups.map((group) => async () => {
		const refFailures = /* @__PURE__ */ new Map();
		const onRefError = (error) => {
			if (params.errorMode === "stop") throw error;
			refFailures.set(error.refId, error);
		};
		if (group.refs.length > DEFAULT_MAX_REFS_PER_PROVIDER) throw providerResolutionError({
			code: "SECRET_PROVIDER_INVALID",
			source: group.source,
			provider: group.providerName,
			message: `Secret provider "${group.providerName}" exceeded maxRefsPerProvider (${DEFAULT_MAX_REFS_PER_PROVIDER}).`
		});
		const providerConfig = resolveConfiguredProvider({
			ref: expectDefined(group.refs[0], "refs entry at 0"),
			config: params.options.config,
			env: params.options.env ?? process.env,
			manifestRegistry: params.options.manifestRegistry
		});
		const values = await resolveProviderRefs({
			refs: group.refs,
			source: group.source,
			providerName: group.providerName,
			providerConfig,
			options: params.options,
			onRefError
		});
		for (const ref of group.refs) {
			if (refFailures.has(ref.id)) continue;
			if (!values.has(ref.id)) {
				onRefError(refResolutionError({
					code: "SECRET_REF_PROVIDER_CONTRACT",
					source: group.source,
					provider: group.providerName,
					refId: ref.id,
					message: `Secret provider "${group.providerName}" did not return id "${ref.id}".`
				}));
				continue;
			}
			if (isRedactedSecretValue(values.get(ref.id))) onRefError(refResolutionError({
				code: "SECRET_REF_REDACTED_VALUE",
				source: group.source,
				provider: group.providerName,
				refId: ref.id,
				message: `Secret reference "${group.source}:${group.providerName}:${ref.id}" resolves to a redaction placeholder. Run openclaw doctor --fix to repair a store-backed Gateway token; supply a real credential for other secrets.`
			}));
		}
		return {
			group,
			values,
			refFailures
		};
	});
}
async function resolveSecretRefProviderGroups(params) {
	const groups = normalizeAndGroupSecretRefs(params.refs);
	const errorsByIndex = /* @__PURE__ */ new Map();
	const taskResults = await runTasksWithConcurrency({
		tasks: createProviderResolutionTasks({
			groups,
			options: params.options,
			errorMode: params.errorMode
		}),
		limit: DEFAULT_PROVIDER_CONCURRENCY,
		errorMode: params.errorMode,
		onTaskError: (error, index) => {
			errorsByIndex.set(index, error);
		}
	});
	const resolved = /* @__PURE__ */ new Map();
	const failures = [];
	for (const result of taskResults.results) {
		if (!result) continue;
		for (const ref of result.group.refs) {
			const error = result.refFailures.get(ref.id);
			if (error) failures.push({
				group: result.group,
				error
			});
			else resolved.set(secretRefKey(ref), result.values.get(ref.id));
		}
	}
	for (const [index, group] of groups.entries()) if (errorsByIndex.has(index)) failures.push({
		group,
		error: errorsByIndex.get(index)
	});
	return {
		resolved,
		failures,
		hasError: taskResults.hasError,
		firstError: taskResults.firstError
	};
}
/** Resolves a batch of SecretRefs, grouped by provider for bounded provider concurrency. */
async function resolveSecretRefValues(refs, options) {
	const result = await resolveSecretRefProviderGroups({
		refs,
		options,
		errorMode: "stop"
	});
	if (result.hasError) throw result.firstError;
	return result.resolved;
}
/** Resolves one SecretRef, using the optional shared runtime cache. */
async function resolveSecretRefValue(ref, options) {
	const cache = options.cache;
	const key = secretRefKey(ref);
	const resolve = async () => {
		const resolved = await resolveSecretRefValues([ref], options);
		if (!resolved.has(key)) throw refResolutionError({
			code: "SECRET_REF_PROVIDER_CONTRACT",
			source: ref.source,
			provider: ref.provider,
			refId: ref.id,
			message: `Secret reference "${key}" resolved to no value.`
		});
		return resolved.get(key);
	};
	if (!cache) return await resolve();
	cache.resolvedByRefKey ??= /* @__PURE__ */ new Map();
	return await getOrCreatePromise(cache.resolvedByRefKey, key, resolve);
}
/** Resolves one SecretRef and requires a non-empty string result. */
async function resolveSecretRefString(ref, options) {
	const resolved = await resolveSecretRefValue(ref, options);
	if (!isNonEmptyString(resolved)) throw new Error(`Secret reference "${ref.source}:${ref.provider}:${ref.id}" resolved to a non-string or empty value.`);
	return resolved;
}
//#endregion
//#region src/gateway/resolve-configured-secret-input-string.ts
function buildUnresolvedReason(params) {
	if (params.style === "generic") return `${params.path} SecretRef is unresolved (${params.refLabel}).`;
	if (params.kind === "non-string") return `${params.path} SecretRef resolved to a non-string value.`;
	if (params.kind === "empty") return `${params.path} SecretRef resolved to an empty value.`;
	return `${params.path} SecretRef is unresolved (${params.refLabel}).`;
}
async function resolveConfiguredSecretInput(params) {
	const style = params.unresolvedReasonStyle ?? "generic";
	let configPath = params.path;
	if (typeof params.value === "string" && getConfigResolutionFacts(params.config) !== null) try {
		configPath = formatConcreteConfigPath(tokenizeConcreteConfigPath(configPath).tokens, params.config);
	} catch {}
	const ref = resolveConfigSecretRef({
		config: params.config,
		path: configPath,
		value: params.value,
		defaults: params.config.secrets?.defaults
	});
	if (!ref) return {
		refConfigured: false,
		value: normalizeOptionalString(params.value)
	};
	const refLabel = `${ref.source}:${ref.provider}:${ref.id}`;
	try {
		const resolvedValue = (await resolveSecretRefValues([ref], {
			config: params.config,
			env: params.env,
			...params.manifestRegistry ? { manifestRegistry: params.manifestRegistry } : {}
		})).get(secretRefKey(ref));
		const trimmed = normalizeOptionalString(resolvedValue);
		if (!trimmed) return {
			refConfigured: true,
			unresolvedRefReason: buildUnresolvedReason({
				path: params.path,
				style,
				kind: typeof resolvedValue === "string" ? "empty" : "non-string",
				refLabel
			})
		};
		return {
			refConfigured: true,
			value: trimmed
		};
	} catch (error) {
		const redactedValue = isSecretResolutionError(error) && error.code === "SECRET_REF_REDACTED_VALUE";
		const operatorDiagnostic = style === "detailed" || redactedValue ? describeSecretResolutionOperatorDiagnostic(error) : void 0;
		const operatorRecovery = style === "detailed" || redactedValue ? describeSecretResolutionOperatorRecovery(error) : void 0;
		const unresolvedReason = buildUnresolvedReason({
			path: params.path,
			style,
			kind: "unresolved",
			refLabel
		});
		const operatorDetail = [operatorDiagnostic, operatorRecovery].filter(Boolean).join(". ");
		return {
			refConfigured: true,
			...redactedValue ? { unresolvedRefCode: "SECRET_REF_REDACTED_VALUE" } : {},
			unresolvedRefReason: operatorDetail ? `${unresolvedReason} ${operatorDetail}.` : unresolvedReason
		};
	}
}
async function resolveConfiguredSecretInputWithFallback(params) {
	const resolved = await resolveConfiguredSecretInput(params);
	const configValue = !resolved.refConfigured ? resolved.value : void 0;
	if (configValue) return {
		value: configValue,
		source: "config",
		secretRefConfigured: false
	};
	if (!resolved.refConfigured) {
		const fallback = normalizeOptionalString(params.readFallback?.());
		if (fallback) return {
			value: fallback,
			source: "fallback",
			secretRefConfigured: false
		};
		return { secretRefConfigured: false };
	}
	if (resolved.value) return {
		value: resolved.value,
		source: "secretRef",
		secretRefConfigured: true
	};
	return {
		unresolvedRefReason: resolved.unresolvedRefReason,
		...resolved.unresolvedRefCode ? { unresolvedRefCode: resolved.unresolvedRefCode } : {},
		secretRefConfigured: true
	};
}
//#endregion
//#region src/gateway/secret-input-paths.ts
/** Stable scan order for Gateway secret-ref credential selection. */
const ALL_GATEWAY_SECRET_INPUT_PATHS = [
	"gateway.auth.token",
	"gateway.auth.password",
	"gateway.remote.token",
	"gateway.remote.password"
];
/** Narrow an arbitrary error/config path to one of the supported Gateway secret inputs. */
function isSupportedGatewaySecretInputPath(path) {
	return ALL_GATEWAY_SECRET_INPUT_PATHS.includes(path);
}
/** Read a Gateway secret input without assuming whether it is plaintext, a ref, or absent. */
function readGatewaySecretInputValue(config, path) {
	if (path === "gateway.auth.token") return config.gateway?.auth?.token;
	if (path === "gateway.auth.password") return config.gateway?.auth?.password;
	if (path === "gateway.remote.token") return config.gateway?.remote?.token;
	return config.gateway?.remote?.password;
}
/** Replace one Gateway secret input and consume its pending authored provenance atomically. */
function assignResolvedGatewaySecretInput(params) {
	const { config, path, value } = params;
	const target = path === "gateway.auth.token" || path === "gateway.auth.password" ? config.gateway?.auth : config.gateway?.remote;
	if (target) {
		target[isTokenGatewaySecretInputPath(path) ? "token" : "password"] = value;
		copyConfigResolutionFactsExcept(config, config, [path]);
	}
}
/** Distinguish token paths from password paths for auth-mode precedence checks. */
function isTokenGatewaySecretInputPath(path) {
	return path === "gateway.auth.token" || path === "gateway.remote.token";
}
//#endregion
//#region src/gateway/auth-surface-resolution.ts
function createGatewayCredentialResolver(params) {
	return async (path) => {
		const resolved = await resolveConfiguredSecretInputWithFallback({
			config: params.config,
			env: params.env,
			value: readGatewaySecretInputValue(params.config, path),
			path,
			unresolvedReasonStyle: "detailed"
		});
		if (resolved.unresolvedRefReason) params.diagnostics.push({
			message: resolved.unresolvedRefReason,
			code: resolved.unresolvedRefCode
		});
		return resolved;
	};
}
function withDiagnostics(diagnostics, result) {
	return diagnostics.length > 0 ? {
		...result,
		diagnostics: diagnostics.map(({ message }) => message),
		...diagnostics.some(({ code }) => code === "SECRET_REF_REDACTED_VALUE") ? { warningCode: "SECRET_REF_REDACTED_VALUE" } : {}
	} : result;
}
/** Resolves best-effort credentials for non-mutating local/remote gateway probes. */
async function resolveGatewayProbeSurfaceAuth(params) {
	const env = params.env ?? process.env;
	const diagnostics = [];
	const resolveCredential = createGatewayCredentialResolver({
		config: params.config,
		env,
		diagnostics
	});
	const authMode = params.config.gateway?.auth?.mode;
	if (params.surface === "remote") {
		const remoteToken = await resolveCredential("gateway.remote.token");
		const remotePassword = remoteToken.value ? {
			value: void 0,
			secretRefConfigured: false
		} : await resolveCredential("gateway.remote.password");
		const envToken = trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
		const envPassword = trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
		const hasConfiguredAuth = Boolean(remoteToken.value || remotePassword.value);
		const allowEnvAuth = !hasConfiguredAuth && diagnostics.length === 0;
		return withDiagnostics(diagnostics, {
			token: remoteToken.value ?? (allowEnvAuth ? envToken : void 0),
			password: remotePassword.value ?? (allowEnvAuth ? envPassword : void 0),
			...hasConfiguredAuth ? { source: "config" } : allowEnvAuth && (envToken || envPassword) && { source: "env" }
		});
	}
	if (authMode === "none" || authMode === "trusted-proxy") return {};
	const envToken = trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
	const envPassword = trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
	if (authMode === "token" || authMode === "password") {
		const credential = await resolveCredential(`gateway.auth.${authMode}`);
		if (credential.value) return withDiagnostics(diagnostics, {
			[authMode]: credential.value,
			source: "config"
		});
		const envCredential = authMode === "token" ? envToken : envPassword;
		return !credential.secretRefConfigured && envCredential ? {
			[authMode]: envCredential,
			source: "env"
		} : withDiagnostics(diagnostics, {});
	}
	const token = await resolveCredential("gateway.auth.token");
	if (token.value) return withDiagnostics(diagnostics, {
		token: token.value,
		source: "config"
	});
	if (token.secretRefConfigured) return withDiagnostics(diagnostics, {});
	const password = await resolveCredential("gateway.auth.password");
	if (password.secretRefConfigured) return withDiagnostics(diagnostics, password.value ? {
		password: password.value,
		source: "config"
	} : {});
	if (envToken) return {
		token: envToken,
		source: "env"
	};
	if (envPassword) return withDiagnostics(diagnostics, {
		password: envPassword,
		source: "env"
	});
	return withDiagnostics(diagnostics, {
		token: token.value,
		password: password.value,
		...password.value && { source: "config" }
	});
}
/** Resolves credentials for client paths that must either authenticate or explain the failure. */
async function resolveGatewayInteractiveSurfaceAuth(params) {
	const env = params.env ?? process.env;
	const resolveCredential = createGatewayCredentialResolver({
		config: params.config,
		env,
		diagnostics: []
	});
	const explicitToken = trimToUndefined(params.explicitAuth?.token);
	const explicitPassword = trimToUndefined(params.explicitAuth?.password);
	const credentialPlan = createGatewayCredentialPlan({
		config: params.config,
		env
	});
	const authMode = params.config.gateway?.auth?.mode;
	const hasActiveSecretRef = params.surface === "remote" ? credentialPlan.remoteToken.hasSecretRef || credentialPlan.remotePassword.hasSecretRef : credentialPlan.localTokenCanWin && credentialPlan.localToken.hasSecretRef || (authMode === "password" || authMode === void 0) && credentialPlan.localPassword.hasSecretRef;
	if ((explicitToken || explicitPassword) && hasActiveSecretRef) return {
		token: explicitToken,
		password: explicitPassword
	};
	const envToken = params.suppressEnvAuthFallback ? void 0 : trimToUndefined(env.OPENCLAW_GATEWAY_TOKEN);
	const envPassword = params.suppressEnvAuthFallback ? void 0 : trimToUndefined(env.OPENCLAW_GATEWAY_PASSWORD);
	if (params.surface === "remote") {
		const remoteToken = explicitToken ? {
			value: explicitToken,
			secretRefConfigured: false
		} : await resolveCredential("gateway.remote.token");
		if (remoteToken.value && (remoteToken.secretRefConfigured || credentialPlan.remotePassword.hasSecretRef)) return {
			token: remoteToken.value,
			password: void 0
		};
		const remotePassword = explicitPassword ? {
			value: explicitPassword,
			secretRefConfigured: false
		} : await resolveCredential("gateway.remote.password");
		const secretRefConfigured = remoteToken.secretRefConfigured || remotePassword.secretRefConfigured;
		const token = remoteToken.value ?? (secretRefConfigured ? void 0 : envToken);
		const password = explicitPassword ?? (secretRefConfigured ? remotePassword.value : envPassword ?? remotePassword.value);
		return token || password ? {
			token,
			password
		} : { failureReason: remoteToken.unresolvedRefReason ?? remotePassword.unresolvedRefReason ?? "Missing gateway auth credentials." };
	}
	if (authMode === "none" || authMode === "trusted-proxy") return {
		token: explicitToken ?? envToken,
		password: explicitPassword ?? envPassword
	};
	const shouldUsePassword = authMode === "password" || authMode !== "token" && (Boolean(explicitPassword ?? envPassword) && !credentialPlan.localToken.hasSecretRef || credentialPlan.localPassword.configured && !credentialPlan.localToken.configured);
	const credentialKind = shouldUsePassword ? "password" : "token";
	const explicitCredential = shouldUsePassword ? explicitPassword : explicitToken;
	const envCredential = shouldUsePassword ? envPassword : envToken;
	const credential = explicitCredential ? {
		value: explicitCredential,
		secretRefConfigured: false
	} : await resolveCredential(`gateway.auth.${credentialKind}`);
	const value = credential.value ?? (credential.secretRefConfigured ? void 0 : envCredential);
	return {
		token: shouldUsePassword ? credential.secretRefConfigured ? void 0 : explicitToken ?? envToken : value,
		password: shouldUsePassword ? value : credential.secretRefConfigured ? void 0 : explicitPassword ?? envPassword,
		failureReason: value ? void 0 : credential.unresolvedRefReason ?? `Missing gateway auth ${credentialKind}.`
	};
}
//#endregion
//#region packages/gateway-client/src/client-address-utils.ts
function normalizeGatewayErrorText(value) {
	return typeof value === "string" ? value.trim().toLowerCase() : "";
}
function isSensitiveUrlQueryParamName(key) {
	return /(?:token|password|secret|key|auth|credential)/iu.test(key);
}
function isGatewayClientStoppedError(err) {
	const message = err instanceof Error ? err.message : String(err);
	return message === "gateway client stopped" || message === "Error: gateway client stopped";
}
function formatGatewayClientErrorForLog(err) {
	return String(err).replace(/\/\/([^@/?#\s]+)@/g, "//***:***@").replace(/(Authorization:\s*Bearer\s+)[^\s]+/giu, "$1***").replace(/([?&])([^=&\s]+)=([^&#\s"'<>)]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=***` : match);
}
const SHA256_HEX_FINGERPRINT = /^[a-fA-F0-9]{64}$/u;
const SHA256_COLON_FINGERPRINT = /^(?:[a-fA-F0-9]{2}:){31}[a-fA-F0-9]{2}$/u;
function normalizeTlsFingerprint(fingerprint) {
	const value = (fingerprint ?? "").trim().replace(/^sha256:/iu, "");
	if (SHA256_HEX_FINGERPRINT.test(value)) return value.toLowerCase();
	return SHA256_COLON_FINGERPRINT.test(value) ? value.replaceAll(":", "").toLowerCase() : "";
}
function requireTlsFingerprint(fingerprint) {
	const normalized = normalizeTlsFingerprint(fingerprint);
	if (!normalized) throw new Error("Invalid TLS fingerprint; expected a SHA-256 certificate fingerprint.");
	return normalized;
}
function parseHostForAddressChecks(host) {
	if (!host) return null;
	const normalizedHost = host.toLowerCase().trim();
	const canonicalHost = normalizedHost.replace(/\.+$/, "");
	if (canonicalHost === "localhost") return {
		isLocalhost: true,
		unbracketedHost: canonicalHost
	};
	return {
		isLocalhost: false,
		unbracketedHost: normalizedHost.startsWith("[") && normalizedHost.endsWith("]") ? normalizedHost.slice(1, -1) : normalizedHost
	};
}
function parseGatewayIpAddress(host) {
	const normalized = normalizeIpAddress(host);
	return normalized ? parseCanonicalIpAddress(normalized) : void 0;
}
//#endregion
//#region packages/gateway-client/src/gateway-origin-scope.ts
function normalizeGatewayScope(gatewayUrl, includeSearch) {
	const trimmed = gatewayUrl.trim();
	if (!trimmed) return "default";
	try {
		const browserLocation = globalThis.location;
		const base = browserLocation ? `${browserLocation.protocol}//${browserLocation.host}${browserLocation.pathname || "/"}` : void 0;
		const parsed = base ? new URL(trimmed, base) : new URL(trimmed);
		const pathname = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
		return `${parsed.protocol}//${parsed.host}${pathname}${includeSearch ? parsed.search : ""}`;
	} catch {
		return trimmed;
	}
}
/** Normalizes the gateway URL scope used for origin-bound device tokens. */
function gatewayOriginScope(gatewayUrl) {
	return normalizeGatewayScope(gatewayUrl, false);
}
//#endregion
//#region src/gateway/connection-details.ts
/** Resolve the forwarded Gateway port independently of the local tunnel port. */
function resolveGatewaySshRemotePort(config, gatewayUrl = config.gateway?.remote?.url) {
	if (config.gateway?.remote?.remotePort !== void 0) return config.gateway.remote.remotePort;
	const url = gatewayUrl?.trim();
	if (url && URL.canParse(url)) {
		const port = new URL(url.replace(/^wss?:/iu, "openclaw-ssh:")).port;
		if (port) return Number(port);
	}
	return resolveGatewayPort(config);
}
/** Select credential ownership independently of an SSH tunnel's reusable local port. */
function resolveGatewayDeviceAuthRoute(params) {
	if (!params.remote) return { bound: false };
	const url = params.url.trim();
	const deviceAuthScope = gatewayOriginScope(url);
	if (!URL.canParse(url)) return {
		deviceAuthScope,
		bound: false
	};
	const parsed = new URL(url);
	if (!isLoopbackHost(parsed.hostname)) return {
		deviceAuthScope,
		bound: true
	};
	const remote = params.config.gateway?.remote;
	const configuredTarget = normalizeOptionalString(remote?.sshTarget);
	const identity = normalizeOptionalString(remote?.sshIdentity);
	const configuredSshRoute = params.configuredRemote && configuredTarget && remote?.transport !== "direct" && remote?.url?.trim() === url ? {
		target: configuredTarget,
		remotePort: resolveGatewaySshRemotePort(params.config, url),
		...identity ? { identity } : {},
		...remote.sshHostKeyPolicy ? { hostKeyPolicy: remote.sshHostKeyPolicy } : {}
	} : void 0;
	const sshRoute = params.sshRoute ?? configuredSshRoute;
	if (sshRoute) {
		const sshTunnel = {
			...sshRoute,
			target: sshRoute.target.trim()
		};
		const route = `${sshTunnel.target}:gateway-port:${sshTunnel.remotePort}`;
		return {
			deviceAuthScope: `remote:ssh:${createHash("sha256").update(route).digest("hex")}`,
			sshTunnel,
			bound: true
		};
	}
	const fingerprint = normalizeTlsFingerprint(params.tlsFingerprint);
	if (parsed.protocol === "wss:" && fingerprint) return {
		deviceAuthScope: `remote:tls:${createHash("sha256").update(`${deviceAuthScope}:tls-sha256:${fingerprint}`).digest("hex")}`,
		bound: true
	};
	return {
		deviceAuthScope,
		bound: false
	};
}
/** Redact one Gateway URL before it crosses an operator-visible diagnostic boundary. */
function projectGatewayUrlForDiagnostics(url) {
	return redactSensitiveUrlLikeString(url);
}
/** Build gateway target details and reject unsafe remote plaintext websocket URLs. */
function buildGatewayConnectionDetailsWithResolvers(options = {}, resolvers = {}) {
	const config = options.config ?? resolvers.getRuntimeConfig?.() ?? {};
	const configPath = options.configPath ?? resolvers.resolveConfigPath?.(process.env) ?? resolveConfigPath(process.env);
	const isRemoteMode = options.localPortOverride === void 0 && config.gateway?.mode === "remote";
	const remote = isRemoteMode ? config.gateway?.remote : void 0;
	const tlsEnabled = config.gateway?.tls?.enabled === true;
	const localPort = options.localPortOverride ?? resolvers.resolveGatewayPort?.(config, process.env) ?? resolveGatewayPort(config);
	const bindMode = config.gateway?.bind ?? "loopback";
	const localUrl = `${tlsEnabled ? "wss" : "ws"}://127.0.0.1:${localPort}`;
	const cliUrlOverride = normalizeOptionalString(options.url);
	const serviceUrl = normalizeOptionalString(options.serviceTargetUrl);
	const envUrlOverride = cliUrlOverride || serviceUrl || options.ignoreEnvUrlOverride || options.localPortOverride !== void 0 ? void 0 : normalizeOptionalString(process.env.OPENCLAW_GATEWAY_URL);
	const urlOverride = cliUrlOverride ?? envUrlOverride;
	const remoteUrl = normalizeOptionalString(remote?.url);
	const remoteMisconfigured = isRemoteMode && !urlOverride && !serviceUrl && !remoteUrl;
	const urlSourceHint = options.urlSource ?? (cliUrlOverride ? "cli" : envUrlOverride ? "env" : void 0);
	const url = urlOverride || serviceUrl || remoteUrl || localUrl;
	const displayUrl = redactSensitiveUrlLikeString(url);
	const urlSource = urlOverride ? urlSourceHint === "env" ? "env OPENCLAW_GATEWAY_URL" : "cli --url" : serviceUrl ? "service target" : remoteUrl ? "config gateway.remote.url" : remoteMisconfigured ? "missing gateway.remote.url (fallback local)" : "local loopback";
	const bindDetail = !urlOverride && (serviceUrl || !remoteUrl) ? `Bind: ${bindMode}` : void 0;
	const remoteFallbackNote = remoteMisconfigured ? "Warn: gateway.mode=remote but gateway.remote.url is missing; set gateway.remote.url or switch gateway.mode=local." : void 0;
	const allowPrivateWs = process.env.OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
	if (!isSecureWebSocketUrl$1(url, { allowPrivateWs })) throw new Error([
		`SECURITY ERROR: Gateway URL "${displayUrl}" uses plaintext ws:// to a non-loopback address.`,
		"Both credentials and chat data would be exposed to network interception.",
		`Source: ${urlSource}`,
		`Config: ${configPath}`,
		"Fix: Use wss:// for remote gateway URLs.",
		"Safe remote access defaults:",
		"- keep gateway.bind=loopback and use an SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host)",
		"- or use Tailscale Serve/Funnel for HTTPS remote access",
		allowPrivateWs ? void 0 : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1",
		"Doctor: openclaw doctor --fix",
		"Docs: https://docs.openclaw.ai/gateway/remote"
	].join("\n"));
	return {
		url,
		urlSource,
		bindDetail,
		remoteFallbackNote,
		message: [
			`Gateway target: ${displayUrl}`,
			`Source: ${urlSource}`,
			`Config: ${configPath}`,
			bindDetail,
			remoteFallbackNote
		].filter(Boolean).join("\n")
	};
}
//#endregion
//#region src/gateway/control-ui-shared.ts
/** Normalizes a Control UI base path to either "" or a leading-slash path without trailing slash. */
function normalizeControlUiBasePath(basePath) {
	const value = basePath?.trim() ?? "";
	if (!value || value === "/") return "";
	const withSlash = value.startsWith("/") ? value : `/${value}`;
	return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
}
//#endregion
//#region src/secrets/resolve-secret-input-string.ts
/**
* Resolves a config value that may be either an inline string or a SecretRef object.
*
* Plugin and gateway callers can override normalization and convert SecretRef resolution errors
* into surface-specific failures without duplicating provider lookup behavior.
*/
async function materializeSecretInput(params) {
	const normalize = params.normalize ?? normalizeSecretInputString;
	const { ref } = resolveSecretInputRef({
		value: params.value,
		defaults: params.defaults ?? params.config.secrets?.defaults
	});
	if (!ref) return normalize(params.value);
	let resolved;
	try {
		resolved = await resolveSecretRefString(ref, {
			config: params.config,
			env: params.env
		});
	} catch (error) {
		if (params.onResolveRefError) return params.onResolveRefError(error, ref);
		throw error;
	}
	return normalize(resolved);
}
//#endregion
//#region src/gateway/credentials-secret-inputs.ts
async function resolveConfiguredGatewaySecretInput(params) {
	const configuredValue = readGatewaySecretInputValue(params.config, params.path);
	const ref = resolveConfigSecretRef({
		config: params.config,
		path: params.path,
		value: configuredValue,
		defaults: params.config.secrets?.defaults
	});
	const value = await materializeSecretInput({
		config: params.config,
		value: ref ?? configuredValue,
		env: params.env,
		normalize: trimToUndefined,
		onResolveRefError: (error) => {
			if (isSecretResolutionError(error) && error.code === "SECRET_REF_REDACTED_VALUE") throw error;
			throw new GatewaySecretRefUnavailableError(params.path);
		}
	});
	if (!value) throw new Error(`${params.path} resolved to an empty or non-string value.`);
	return value;
}
function hasConfiguredGatewaySecretRef(config, path) {
	return Boolean(resolveConfigSecretRef({
		config,
		path,
		value: readGatewaySecretInputValue(config, path),
		defaults: config.secrets?.defaults
	}));
}
function resolveGatewayCredentialsFromConfigOptions(params) {
	const { cfg, env, options } = params;
	return {
		cfg,
		env,
		explicitAuth: options.explicitAuth,
		urlOverride: options.urlOverride,
		urlOverrideSource: options.urlOverrideSource,
		modeOverride: options.modeOverride,
		localPrecedence: options.localPrecedence,
		remoteTokenPrecedence: options.remoteTokenPrecedence,
		remotePasswordPrecedence: options.remotePasswordPrecedence ?? "env-first",
		remoteTokenFallback: options.remoteTokenFallback,
		remotePasswordFallback: options.remotePasswordFallback
	};
}
function localAuthModeAllowsGatewaySecretInputPath(params) {
	const { authMode, path } = params;
	if (authMode === "none") return false;
	if (authMode === "trusted-proxy") return !isTokenGatewaySecretInputPath(path);
	if (authMode === "token") return isTokenGatewaySecretInputPath(path);
	if (authMode === "password") return !isTokenGatewaySecretInputPath(path);
	return true;
}
function canGatewaySecretInputPathWin(params) {
	if (!hasConfiguredGatewaySecretRef(params.config, params.path)) return false;
	const mode = params.options.modeOverride ?? (params.config.gateway?.mode === "remote" ? "remote" : "local");
	if (mode === "local" && !localAuthModeAllowsGatewaySecretInputPath({
		authMode: params.config.gateway?.auth?.mode,
		path: params.path
	})) return false;
	const sentinel = `__OPENCLAW_GATEWAY_SECRET_REF_PROBE_${params.path.replaceAll(".", "_")}__`;
	const probeConfig = cloneConfigWithResolutionFacts(params.config);
	for (const candidatePath of ALL_GATEWAY_SECRET_INPUT_PATHS) {
		if (!hasConfiguredGatewaySecretRef(probeConfig, candidatePath)) continue;
		assignResolvedGatewaySecretInput({
			config: probeConfig,
			path: candidatePath,
			value: void 0
		});
	}
	assignResolvedGatewaySecretInput({
		config: probeConfig,
		path: params.path,
		value: sentinel
	});
	try {
		const resolved = resolveGatewayCredentialsFromConfig(resolveGatewayCredentialsFromConfigOptions({
			cfg: probeConfig,
			env: params.env,
			options: params.options
		}));
		const authMode = params.config.gateway?.auth?.mode;
		const tokenCanWin = resolved.token === sentinel && (mode === "local" && authMode === "token" || !resolved.password);
		const passwordCanWin = resolved.password === sentinel && (mode === "local" && (authMode === "password" || authMode === "trusted-proxy") || !resolved.token);
		return tokenCanWin || passwordCanWin;
	} catch {
		return false;
	}
}
async function resolvePreferredGatewaySecretInputs(params) {
	let nextConfig = params.config;
	for (const path of ALL_GATEWAY_SECRET_INPUT_PATHS) {
		if (!canGatewaySecretInputPathWin({
			options: params.options,
			env: params.env,
			config: nextConfig,
			path
		})) continue;
		if (nextConfig === params.config) nextConfig = cloneConfigWithResolutionFacts(params.config);
		try {
			const resolvedValue = await resolveConfiguredGatewaySecretInput({
				config: nextConfig,
				path,
				env: params.env
			});
			assignResolvedGatewaySecretInput({
				config: nextConfig,
				path,
				value: resolvedValue
			});
		} catch (error) {
			if (isSecretResolutionError(error) && error.code === "SECRET_REF_REDACTED_VALUE") throw error;
			continue;
		}
	}
	return nextConfig;
}
/** Resolve only secret refs that can win, then select Gateway credentials. */
async function resolveGatewayCredentialsFromConfigWithSecretInputs(params) {
	let resolvedConfig = await resolvePreferredGatewaySecretInputs({
		options: params.options,
		env: params.env,
		config: params.options.config
	});
	const resolvedPaths = /* @__PURE__ */ new Set();
	for (;;) try {
		return resolveGatewayCredentialsFromConfig(resolveGatewayCredentialsFromConfigOptions({
			cfg: resolvedConfig,
			env: params.env,
			options: params.options
		}));
	} catch (error) {
		if (!(error instanceof GatewaySecretRefUnavailableError)) throw error;
		const path = error.path;
		if (!isSupportedGatewaySecretInputPath(path) || resolvedPaths.has(path)) throw error;
		if (resolvedConfig === params.options.config) resolvedConfig = cloneConfigWithResolutionFacts(params.options.config);
		const resolvedValue = await resolveConfiguredGatewaySecretInput({
			config: resolvedConfig,
			path,
			env: params.env
		});
		assignResolvedGatewaySecretInput({
			config: resolvedConfig,
			path,
			value: resolvedValue
		});
		resolvedPaths.add(path);
	}
}
/** Resolve Gateway credentials after materializing winning configured secret refs. */
async function resolveGatewayCredentialsWithSecretInputs(params) {
	const options = {
		...params,
		explicitAuth: resolveExplicitGatewayAuth(params.explicitAuth)
	};
	if (options.explicitAuth.token || options.explicitAuth.password) return {
		token: options.explicitAuth.token,
		password: options.explicitAuth.password
	};
	return await resolveGatewayCredentialsFromConfigWithSecretInputs({
		options,
		env: params.env ?? process.env
	});
}
//#endregion
//#region packages/net-policy/src/url-protocol.ts
function parseUrl(value) {
	if (value instanceof URL) return value;
	try {
		return new URL(value);
	} catch {
		return null;
	}
}
function isHttpUrl(value) {
	const url = parseUrl(value);
	return url?.protocol === "http:" || url?.protocol === "https:";
}
function isWebSocketUrl(value) {
	const url = parseUrl(value);
	return url?.protocol === "ws:" || url?.protocol === "wss:";
}
function isWssUrl(value) {
	return parseUrl(value)?.protocol === "wss:";
}
//#endregion
//#region src/infra/tls/gateway.ts
const MAX_TLS_LEAF_FILE_BYTES = 65536;
async function readTlsLeafFile(filePath) {
	const file = await fsPromises.open(filePath, "r");
	try {
		return (await readFileDescriptorBounded$1(file.fd, MAX_TLS_LEAF_FILE_BYTES)).toString("utf8");
	} finally {
		await file.close();
	}
}
function resolveGatewayTlsCertPath(certPath) {
	return resolveUserPath(typeof certPath === "string" && certPath.trim() ? certPath : path.join(CONFIG_DIR, "gateway", "tls", "gateway-cert.pem"));
}
/** Read only public certificate bytes. Inspection never provisions or requires server secrets. */
async function inspectGatewayTlsCertificate(cfg) {
	if (cfg?.enabled !== true) return err("gateway tls is disabled");
	try {
		const cert = await readTlsLeafFile(resolveGatewayTlsCertPath(cfg.certPath));
		const fingerprintSha256 = normalizeTlsFingerprint(new X509Certificate(cert).fingerprint256);
		return fingerprintSha256 ? ok({
			cert,
			fingerprintSha256
		}) : err("gateway tls: unable to compute certificate fingerprint");
	} catch (error) {
		return err(`gateway tls: failed to load cert (${String(error)})`);
	}
}
//#endregion
//#region src/gateway/tls-fingerprint.ts
/** Resolve the certificate pin for one already-selected Gateway target. */
async function resolveGatewayConnectionTlsFingerprint(params) {
	const explicitTlsFingerprint = params.explicitTlsFingerprint ? requireTlsFingerprint(params.explicitTlsFingerprint) : void 0;
	if (explicitTlsFingerprint) return explicitTlsFingerprint;
	const remoteTlsFingerprint = params.config.gateway?.mode === "remote" && (params.urlSource === "config gateway.remote.url" || params.urlSource === "env OPENCLAW_GATEWAY_URL") ? params.config.gateway.remote?.tlsFingerprint ? requireTlsFingerprint(params.config.gateway.remote.tlsFingerprint) : void 0 : void 0;
	if (remoteTlsFingerprint) return remoteTlsFingerprint;
	if (!isWssUrl(params.url)) return;
	if (!(params.urlSource === "local loopback" || params.urlSource === "missing gateway.remote.url (fallback local)") || params.config.gateway?.tls?.enabled !== true) return;
	const certificate = await inspectGatewayTlsCertificate(params.config.gateway.tls);
	return certificate.ok ? certificate.value.fingerprintSha256 : void 0;
}
//#endregion
//#region src/gateway/client-bootstrap.ts
/**
* Maps connection-detail source labels to the override kinds that affect auth fallback.
*/
function resolveGatewayUrlOverrideSource(urlSource) {
	if (urlSource === "cli --url") return "cli";
	if (urlSource === "env OPENCLAW_GATEWAY_URL") return "env";
}
var GatewayExplicitAuthRequiredError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "GatewayExplicitAuthRequiredError";
	}
};
async function ensureExplicitGatewayAuth(params) {
	if (!params.urlOverride || !params.urlOverrideSource) return;
	if (params.explicitAuth?.token || params.explicitAuth?.password) return;
	if (params.urlOverrideSource === "env" && (params.resolvedAuth?.token || params.resolvedAuth?.password)) return;
	if (params.deviceAuthScope && await params.allowStoredOriginAuth?.(params.deviceAuthScope) === true) return;
	const sourceHint = params.urlOverrideSource === "env" ? "Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD alongside OPENCLAW_GATEWAY_URL; config credentials are intentionally not reused." : "For the default local or SSH-tunneled Gateway, remove --url to use the configured target.";
	throw new GatewayExplicitAuthRequiredError([
		"gateway url override requires explicit credentials",
		params.errorHint,
		sourceHint,
		params.configPath ? `Config: ${params.configPath}` : void 0
	].filter(Boolean).join("\n"));
}
function appendControlUiBasePath(url, basePath) {
	return `${url}${normalizeControlUiBasePath(basePath)}`;
}
function resolveExactConfiguredGatewayTarget(params) {
	const candidates = [];
	if (params.config.gateway?.mode === "remote") {
		const remoteUrl = trimToUndefined(params.config.gateway.remote?.url);
		if (remoteUrl) candidates.push({
			target: remoteUrl,
			identity: {
				authSurface: "remote",
				tlsSource: "config gateway.remote.url"
			}
		});
	} else {
		const localGateway = {
			...params.config.gateway,
			mode: "local"
		};
		delete localGateway.remote;
		const localUrl = params.buildConnectionDetails({
			config: {
				...params.config,
				gateway: localGateway
			},
			ignoreEnvUrlOverride: true,
			...params.localPortOverride !== void 0 ? { localPortOverride: params.localPortOverride } : {}
		}).url;
		const basePath = params.config.gateway?.controlUi?.basePath ?? "";
		candidates.push({
			target: appendControlUiBasePath(localUrl, basePath),
			identity: {
				authSurface: "local",
				tlsSource: "local loopback"
			}
		});
		const publicOrigin = resolveGatewayPublicOrigin(params.config);
		if (publicOrigin) candidates.push({
			target: appendControlUiBasePath(publicOrigin.replace(/^https:/u, "wss:").replace(/^http:/u, "ws:"), basePath),
			identity: { authSurface: "local" }
		});
	}
	return candidates.find(({ target }) => target === params.explicitUrl)?.identity;
}
/** Resolve the only URL overrides allowed to displace configured Gateway targets. */
function resolveGatewayUrlOverride(params) {
	const cliUrl = trimToUndefined(params.gatewayUrl);
	if (cliUrl) return {
		url: cliUrl,
		source: "cli"
	};
	if (params.ignoreEnvUrlOverride || params.localPortOverride !== void 0) return {};
	const envUrl = trimToUndefined((params.env ?? process.env).OPENCLAW_GATEWAY_URL);
	return envUrl ? {
		url: envUrl,
		source: "env"
	} : {};
}
/**
* Resolves the URL, auth material, and handshake tuning needed to start a GatewayClient.
*/
async function resolveGatewayClientBootstrap(params) {
	const env = params.env ?? process.env;
	const explicitAuth = resolveExplicitGatewayAuth(params.explicitAuth);
	const urlOverride = resolveGatewayUrlOverride({
		gatewayUrl: params.gatewayUrl,
		env,
		ignoreEnvUrlOverride: params.ignoreEnvUrlOverride,
		localPortOverride: params.localPortOverride
	});
	const buildConnectionDetails = params.buildConnectionDetails ?? buildGatewayConnectionDetailsWithResolvers;
	const connection = buildConnectionDetails({
		config: params.config,
		url: urlOverride.url,
		...params.configPath ? { configPath: params.configPath } : {},
		...urlOverride.source ? { urlSource: urlOverride.source } : {},
		ignoreEnvUrlOverride: true,
		...params.localPortOverride !== void 0 ? { localPortOverride: params.localPortOverride } : {},
		...params.serviceTargetUrl ? { serviceTargetUrl: params.serviceTargetUrl } : {}
	});
	const detectedUrlOverrideSource = resolveGatewayUrlOverrideSource(connection.urlSource);
	const urlOverrideSource = urlOverride.source ?? detectedUrlOverrideSource;
	const configuredTarget = params.allowConfiguredAuthForExactTarget && urlOverrideSource === "cli" ? resolveExactConfiguredGatewayTarget({
		buildConnectionDetails,
		config: params.config,
		explicitUrl: connection.url,
		...params.localPortOverride !== void 0 ? { localPortOverride: params.localPortOverride } : {}
	}) : void 0;
	const tlsUrlSource = configuredTarget?.tlsSource ?? connection.urlSource;
	const tlsFingerprint = await resolveGatewayConnectionTlsFingerprint({
		config: params.config,
		url: connection.url,
		urlSource: tlsUrlSource,
		explicitTlsFingerprint: params.explicitTlsFingerprint
	});
	const surface = configuredTarget?.authSurface ?? params.modeOverride ?? (params.localPortOverride !== void 0 ? "local" : params.config.gateway?.mode === "remote" ? "remote" : "local");
	let auth;
	if (params.skipImplicitAuth) auth = explicitAuth;
	else if (urlOverrideSource && !configuredTarget) auth = params.suppressEnvAuthFallback ? explicitAuth : await resolveGatewayCredentialsWithSecretInputs({
		config: params.config,
		explicitAuth,
		env,
		urlOverride: connection.url,
		urlOverrideSource,
		modeOverride: params.modeOverride
	});
	else if (params.authPolicy === "probe") auth = await resolveGatewayProbeSurfaceAuth({
		config: params.config,
		env,
		surface
	});
	else if (params.authPolicy === "interactive") auth = await resolveGatewayInteractiveSurfaceAuth({
		config: params.config,
		env,
		explicitAuth,
		suppressEnvAuthFallback: params.suppressEnvAuthFallback,
		surface
	});
	else auth = await resolveGatewayCredentialsWithSecretInputs({
		config: params.config,
		explicitAuth,
		env,
		urlOverride: urlOverrideSource ? connection.url : void 0,
		urlOverrideSource,
		modeOverride: surface
	});
	const { deviceAuthScope, sshTunnel } = resolveGatewayDeviceAuthRoute({
		config: params.config,
		url: connection.url,
		remote: Boolean(urlOverrideSource || connection.urlSource === "config gateway.remote.url"),
		configuredRemote: !urlOverrideSource && connection.urlSource === "config gateway.remote.url" || configuredTarget?.authSurface === "remote",
		tlsFingerprint
	});
	if (params.overrideAuthErrorHint && !configuredTarget) await ensureExplicitGatewayAuth({
		urlOverride: urlOverrideSource ? connection.url : void 0,
		urlOverrideSource,
		explicitAuth,
		resolvedAuth: auth,
		deviceAuthScope,
		allowStoredOriginAuth: params.allowStoredOriginAuth,
		errorHint: params.overrideAuthErrorHint ?? "Fix: pass --token or --password with --url.",
		configPath: params.configPath
	});
	return {
		url: connection.url,
		urlSource: connection.urlSource,
		connectionDetails: connection,
		...urlOverrideSource ? { urlOverrideSource } : {},
		...deviceAuthScope ? { deviceAuthScope } : {},
		...sshTunnel ? { sshTunnel } : {},
		...auth.failureReason ? { authFailureReason: auth.failureReason } : {},
		...tlsFingerprint ? { tlsFingerprint } : {},
		auth: {
			token: auth.token,
			password: auth.password
		}
	};
}
const GATEWAY_STARTUP_RETRY_MIN_MS = 100;
const GATEWAY_STARTUP_RETRY_MAX_MS = 2e3;
function isGatewayStartupUnavailableDetails(details) {
	return typeof details === "object" && details !== null && details.reason === "startup-sidecars";
}
/** Detects the structured retryable error emitted while startup sidecars are pending. */
function isRetryableGatewayStartupUnavailableError(error) {
	if (!error || typeof error !== "object") return false;
	const shaped = error;
	return (shaped.gatewayCode ?? shaped.code) === "UNAVAILABLE" && shaped.retryable === true && isGatewayStartupUnavailableDetails(shaped.details);
}
/** Resolves a bounded retry-after delay from a startup-unavailable error. */
function resolveGatewayStartupRetryAfterMs(error) {
	if (!isRetryableGatewayStartupUnavailableError(error)) return null;
	const retryAfterMs = error.retryAfterMs;
	return Math.min(Math.max(Math.floor(typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) ? retryAfterMs : 500), GATEWAY_STARTUP_RETRY_MIN_MS), GATEWAY_STARTUP_RETRY_MAX_MS);
}
//#endregion
//#region packages/gateway-client/src/client-device-auth.ts
/** Owns pending host storage work for one client across connection generations. */
var GatewayClientDeviceAuth = class {
	constructor(storage) {
		this.storage = storage;
		this.operations = /* @__PURE__ */ new Set();
	}
	load(params) {
		const load = () => {
			params.signal?.throwIfAborted();
			params.assertCurrent?.();
			return this.track(this.storage.loadDeviceAuthToken(params));
		};
		return this.operations.size > 0 ? this.settle().then(load) : load();
	}
	store(params, onStored, onFailed) {
		const failed = (error) => {
			const failure = error instanceof Error ? error : new Error(String(error));
			let reported = false;
			try {
				reported = onFailed(failure);
			} catch {}
			if (!reported) this.unreportedPersistenceFailure ??= failure;
			throw failure;
		};
		params.signal?.throwIfAborted();
		params.assertCurrent?.();
		const stored = this.storage.storeDeviceAuthToken(params);
		return stored instanceof Promise ? this.track(stored.then(onStored).catch(failed)) : onStored();
	}
	clear(params, observation, canClear) {
		const clear = () => {
			if (!canClear()) return;
			const knownTokens = [observation.token, observation.receiptToken].filter((token) => typeof token === "string");
			const expectedTokens = [...new Set(knownTokens)];
			if (observation.token === void 0 && expectedTokens.length === 0) expectedTokens.push(void 0);
			const clearToken = (expectedToken) => {
				params.signal?.throwIfAborted();
				params.assertCurrent?.();
				const cleared = this.storage.clearDeviceAuthToken({
					...params,
					...expectedToken === void 0 ? {} : { expectedToken }
				});
				return cleared instanceof Promise ? cleared.then(() => {}) : void 0;
			};
			let pending;
			for (const expectedToken of expectedTokens) pending = pending ? pending.then(() => clearToken(expectedToken)) : clearToken(expectedToken);
			return pending;
		};
		return this.track(observation.persistence ? observation.persistence.then(clear, clear) : clear());
	}
	async settle() {
		await Promise.allSettled(this.operations);
	}
	async drain() {
		await this.settle();
		const failure = this.unreportedPersistenceFailure;
		this.unreportedPersistenceFailure = void 0;
		if (failure) throw failure;
	}
	track(operation) {
		if (!(operation instanceof Promise)) return operation;
		this.operations.add(operation);
		operation.then(() => this.operations.delete(operation), () => this.operations.delete(operation));
		return operation;
	}
};
//#endregion
//#region packages/gateway-client/src/client-upgrade-error.ts
const MAX_UPGRADE_ERROR_BODY_BYTES = 2048;
const UPGRADE_ERROR_BODY_TIMEOUT_MS = 1e3;
async function readUpgradeErrorBody(response) {
	return await new Promise((resolve) => {
		const chunks = [];
		let totalBytes = 0;
		let settled = false;
		const finish = () => {
			if (settled) return;
			settled = true;
			clearTimeout(timer);
			response.off("data", onData);
			response.off("end", finish);
			response.off("error", finish);
			response.off("aborted", finish);
			resolve(Buffer.concat(chunks, totalBytes).toString("utf8").replace(/\s+/gu, " ").trim());
		};
		const stop = () => {
			finish();
			response.destroy();
		};
		const onData = (chunk) => {
			const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
			const remaining = MAX_UPGRADE_ERROR_BODY_BYTES - totalBytes;
			if (remaining > 0) {
				const prefix = buffer.subarray(0, remaining);
				chunks.push(prefix);
				totalBytes += prefix.byteLength;
			}
			if (buffer.byteLength >= remaining) stop();
		};
		const timer = setTimeout(stop, UPGRADE_ERROR_BODY_TIMEOUT_MS);
		timer.unref?.();
		response.on("data", onData);
		response.once("end", finish);
		response.once("error", finish);
		response.once("aborted", finish);
	});
}
//#endregion
//#region packages/gateway-client/src/connect-auth.ts
function normalized(value) {
	return typeof value === "string" ? value.trim() || void 0 : void 0;
}
function selectGatewayConnectAuth(params) {
	const authToken = normalized(params.token);
	const bootstrapToken = normalized(params.bootstrapToken);
	const explicitDeviceToken = normalized(params.deviceToken);
	const authPassword = normalized(params.password);
	const storedToken = normalized(params.storedToken);
	const stored = {
		storedToken,
		storedScopes: params.storedScopes
	};
	if (params.preferBootstrapToken && bootstrapToken) return {
		authBootstrapToken: bootstrapToken,
		signatureToken: bootstrapToken,
		...stored
	};
	const useRetryToken = params.pendingDeviceTokenRetry === true && !explicitDeviceToken && Boolean(authToken && storedToken && params.trustedDeviceTokenRetry);
	const resolvedDeviceToken = explicitDeviceToken ?? (useRetryToken || !(authToken || authPassword) && (!bootstrapToken || storedToken) ? storedToken : void 0);
	const usingStoredDeviceToken = Boolean(resolvedDeviceToken && !explicitDeviceToken && storedToken) && resolvedDeviceToken === storedToken;
	const selectedToken = authToken ?? resolvedDeviceToken;
	const authBootstrapToken = !authToken && !resolvedDeviceToken && !authPassword ? bootstrapToken : void 0;
	return {
		authToken,
		authBootstrapToken,
		authDeviceToken: useRetryToken ? storedToken : void 0,
		authPassword,
		authApprovalRuntimeToken: normalized(params.approvalRuntimeToken),
		authAgentRuntimeIdentityToken: normalized(params.agentRuntimeIdentityToken),
		signatureToken: selectedToken ?? authBootstrapToken,
		resolvedDeviceToken,
		usingStoredDeviceToken,
		...stored
	};
}
function buildGatewayConnectAuth(selected) {
	const auth = {
		token: selected.authToken,
		bootstrapToken: selected.authBootstrapToken,
		deviceToken: selected.authDeviceToken ?? selected.resolvedDeviceToken,
		password: selected.authPassword,
		approvalRuntimeToken: selected.authApprovalRuntimeToken,
		agentRuntimeIdentityToken: selected.authAgentRuntimeIdentityToken
	};
	return Object.values(auth).some(Boolean) ? auth : void 0;
}
function resolveGatewayConnectScopes(params) {
	return params.requestedScopes ?? (params.usingStoredDeviceToken && params.storedScopes?.length ? params.storedScopes : [...params.defaultScopes]);
}
function shouldRetryGatewayWithDeviceToken(params) {
	if (params.retryBudgetUsed || params.currentDeviceToken || !params.explicitToken || !params.storedToken || !params.trustedEndpoint) return false;
	const advice = readConnectErrorRecoveryAdvice(params.errorDetails);
	return params.canRetryWithDeviceTokenHint === true || advice.canRetryWithDeviceToken === true || advice.recommendedNextStep === "retry_with_device_token" || readConnectErrorDetailCode(params.errorDetails) === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH;
}
//#endregion
//#region packages/gateway-client/src/device-auth.ts
function normalizeDeviceMetadataForAuth(value) {
	if (typeof value !== "string") return "";
	return value.trim().replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32));
}
function buildDeviceAuthPayloadV3(params) {
	const scopes = params.scopes.join(",");
	const token = params.token ?? "";
	const platform = normalizeDeviceMetadataForAuth(params.platform);
	const deviceFamily = normalizeDeviceMetadataForAuth(params.deviceFamily);
	return [
		"v3",
		params.deviceId,
		params.clientId,
		params.clientMode,
		params.role,
		scopes,
		String(params.signedAtMs),
		token,
		params.nonce,
		platform,
		deviceFamily
	].join("|");
}
//#endregion
//#region packages/gateway-client/src/model-catalog-connect.ts
const MODEL_CATALOG_SNAPSHOT = "model-catalog-snapshot";
/** A requested snapshot opts in only after the server advertises its connect field. */
function resolveModelCatalogConnect(params) {
	const modelCatalog = params.serverCapabilities.includes(MODEL_CATALOG_SNAPSHOT) ? params.modelCatalog : void 0;
	const caps = params.caps?.filter((cap) => cap !== MODEL_CATALOG_SNAPSHOT);
	if (modelCatalog === void 0) return { caps };
	return {
		modelCatalog,
		caps: [...caps ?? [], MODEL_CATALOG_SNAPSHOT]
	};
}
//#endregion
//#region packages/gateway-protocol/src/frame-guards.ts
function isNonNegativeInteger(value) {
	return typeof value === "number" && Number.isInteger(value) && value >= 0;
}
function isGatewayErrorShape(value) {
	if (!isRecord(value)) return false;
	if (!isNonEmptyProtocolString(value.code) || !isNonEmptyProtocolString(value.message)) return false;
	if (value.retryable !== void 0 && typeof value.retryable !== "boolean") return false;
	return value.retryAfterMs === void 0 || isNonNegativeInteger(value.retryAfterMs);
}
function isGatewayEventFrame(value) {
	if (!isRecord(value) || value.type !== "event" || !isNonEmptyProtocolString(value.event)) return false;
	return value.seq === void 0 || isNonNegativeInteger(value.seq);
}
function isGatewayResponseFrame(value) {
	if (!isRecord(value) || value.type !== "res" || !isNonEmptyProtocolString(value.id) || typeof value.ok !== "boolean") return false;
	return value.error === void 0 || isGatewayErrorShape(value.error);
}
//#endregion
//#region packages/gateway-client/src/event-listeners.ts
/** Subscription identity prevents old frames and disposers from reviving callbacks. */
var GatewayEventListeners = class {
	constructor() {
		this.listeners = /* @__PURE__ */ new Map();
	}
	add(listener) {
		const subscription = this.listeners.get(listener) ?? {};
		this.listeners.set(listener, subscription);
		return () => {
			if (this.listeners.get(listener) === subscription) this.listeners.delete(listener);
		};
	}
	snapshot() {
		return [...this.listeners];
	}
	isCurrent(listener, subscription) {
		return this.listeners.get(listener) === subscription;
	}
};
//#endregion
//#region packages/gateway-protocol/src/restart-unavailable.ts
/** Structured error reason used while the gateway drains for a suspension. */
const GATEWAY_SUSPEND_UNAVAILABLE_REASON = "gateway-suspending";
/** Identity reads wait for resume, with a bounded probe if that event is missed. */
const GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS = 6e4;
/** Detects the structured retryable error emitted while suspension refuses work. */
function isGatewaySuspendUnavailableError(error) {
	return hasUnavailableReason(error, GATEWAY_SUSPEND_UNAVAILABLE_REASON);
}
function hasUnavailableReason(error, reason) {
	if (!error || typeof error !== "object" || !("details" in error)) return false;
	const details = error.details;
	return typeof details === "object" && details !== null && "reason" in details && details.reason === reason;
}
//#endregion
//#region packages/gateway-client/src/protocol-request.ts
const gatewayResponseErrors = /* @__PURE__ */ new WeakSet();
var GatewayProtocolRequestError = class extends Error {
	constructor(error) {
		super(error.message ?? "request failed");
		this.name = "GatewayProtocolRequestError";
		this.code = error.code ?? "UNAVAILABLE";
		this.gatewayCode = this.code;
		this.details = error.details;
		this.retryable = error.retryable === true;
		this.retryAfterMs = error.retryAfterMs;
	}
};
/** Preserve response metadata after custom factories without adding it to error JSON. */
function retainGatewayResponsePayload(error, payload) {
	gatewayResponseErrors.add(error);
	Object.defineProperty(error, "responsePayload", {
		value: payload,
		enumerable: false,
		configurable: true
	});
}
/** A local transport deadline, distinct from a Gateway's authoritative rejection. */
var GatewayProtocolRequestTimeoutError = class extends Error {
	constructor(params, message = `gateway request timed out after ${params.timeoutMs}ms: ${params.method}`) {
		super(message);
		this.code = "CLIENT_TIMEOUT";
		this.name = "GatewayProtocolRequestTimeoutError";
		this.method = params.method;
		this.timeoutMs = params.timeoutMs;
		this.requestSent = params.requestSent;
	}
};
//#endregion
//#region packages/gateway-client/src/pending-request.ts
/** Owns request deadlines, correlation, settlement, and generation-scoped IDs. */
var GatewayPendingRequests = class {
	constructor(opts) {
		this.opts = opts;
		this.pending = /* @__PURE__ */ new Map();
		this.requestSequence = 0;
	}
	setSuspensionPhase(phase) {
		if (phase === "accepting") this.resumeIdentityRequests();
		else if (phase === "preparing" || phase === "draining" || phase === "prepared") this.pauseIdentityRequests();
	}
	pauseIdentityRequests(retryAfterMs = GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS) {
		clearTimeout(this.suspensionTimer);
		this.suspensionTimer = setTimeout(() => this.resumeIdentityRequests(), resolveSafeTimeoutDelayMs(Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS));
		this.suspensionTimer.unref?.();
	}
	resumeIdentityRequests() {
		clearTimeout(this.suspensionTimer);
		this.suspensionTimer = void 0;
		for (const pending of this.pending.values()) if (pending.waitingForResume) pending.resend?.();
	}
	get hasPending() {
		return this.pending.size > 0;
	}
	get hasUnboundedPending() {
		for (const pending of this.pending.values()) if (pending.unbounded) return true;
		return false;
	}
	request(sender, method, params, options) {
		let id;
		try {
			id = this.allocateRequestId();
		} catch (error) {
			return Promise.reject(error instanceof Error ? error : new Error(String(error)));
		}
		const requestedTimeoutMs = options?.timeoutMs === null ? void 0 : options?.timeoutMs ?? this.opts.requestTimeoutMs;
		const timeoutMs = typeof requestedTimeoutMs === "number" && Number.isFinite(requestedTimeoutMs) ? resolveSafeTimeoutDelayMs(requestedTimeoutMs, { minMs: 0 }) : void 0;
		return new Promise((resolve, reject) => {
			let timeout;
			let requestSent = false;
			const pending = {
				resolve: (value) => resolve(value),
				reject,
				expectFinal: options?.expectFinal === true,
				acceptedNotified: false,
				onAccepted: options?.onAccepted,
				unbounded: timeoutMs === void 0,
				method,
				startedAtMs: this.opts.nowMs()
			};
			const cleanup = () => {
				if (timeout !== void 0) clearTimeout(timeout);
				options?.signal?.removeEventListener("abort", onAbort);
			};
			const retire = (errorCode) => {
				if (this.pending.get(id) !== pending) return false;
				this.pending.delete(id);
				cleanup();
				this.finishTiming(id, pending, false, errorCode);
				return true;
			};
			const onAbort = () => {
				if (!retire("CLIENT_ABORTED")) return;
				reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
			};
			if (options?.signal?.aborted) {
				reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
				return;
			}
			pending.cleanup = cleanup;
			if (timeoutMs !== void 0) {
				timeout = setTimeout(() => {
					if (!retire("CLIENT_TIMEOUT")) return;
					reject(this.opts.createRequestTimeoutError?.(method, timeoutMs, requestSent) ?? new GatewayProtocolRequestTimeoutError({
						method,
						timeoutMs,
						requestSent
					}));
				}, timeoutMs);
				timeout.unref?.();
			}
			options?.signal?.addEventListener("abort", onAbort, { once: true });
			this.pending.set(id, pending);
			try {
				const frame = JSON.stringify({
					type: "req",
					id,
					method,
					params
				});
				const send = () => {
					if (this.pending.get(id) !== pending) return;
					pending.waitingForResume = Boolean(pending.resend && this.suspensionTimer);
					if (pending.waitingForResume) return;
					try {
						sender.send(frame);
						if (this.pending.get(id) !== pending) return;
						requestSent = true;
						this.invoke("sent", () => options?.onSent?.(id));
					} catch (error) {
						if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
					}
				};
				if (method === "agent.identity.get") pending.resend = send;
				send();
			} catch (error) {
				if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
			}
		});
	}
	handleResponse(frame) {
		const pending = this.pending.get(frame.id);
		if (!pending) return;
		const status = frame.payload?.status;
		if (frame.ok && pending.expectFinal && status === "accepted") {
			if (!pending.acceptedNotified) {
				pending.acceptedNotified = true;
				this.invoke("accepted", () => pending.onAccepted?.(frame.payload));
			}
			return;
		}
		if (!frame.ok && pending.resend && !pending.acceptedNotified && frame.error?.code === "UNAVAILABLE" && frame.error.retryable === true && isGatewaySuspendUnavailableError(frame.error)) {
			this.pauseIdentityRequests(frame.error.retryAfterMs);
			pending.waitingForResume = true;
			return;
		}
		this.pending.delete(frame.id);
		pending.cleanup?.();
		if (frame.ok) {
			this.finishTiming(frame.id, pending, true);
			pending.resolve(frame.payload);
			return;
		}
		this.finishTiming(frame.id, pending, false, frame.error?.code);
		const error = this.opts.createRequestError?.(frame.error ?? {}) ?? new GatewayProtocolRequestError(frame.error ?? {});
		retainGatewayResponsePayload(error, frame.payload);
		pending.reject(error);
	}
	flush(error) {
		clearTimeout(this.suspensionTimer);
		this.suspensionTimer = void 0;
		const retired = this.pending;
		this.pending = /* @__PURE__ */ new Map();
		this.requestSequence = 0;
		for (const [id, pending] of retired) {
			pending.cleanup?.();
			this.finishTiming(id, pending, false, "CLIENT_CLOSED");
			pending.reject(error);
		}
	}
	allocateRequestId() {
		this.requestSequence += 1;
		return `${this.requestSequence}:${this.opts.createRequestId()}`;
	}
	finishTiming(id, pending, ok, errorCode) {
		const endedAtMs = this.opts.nowMs();
		this.invoke("request timing", () => this.opts.onTiming?.({
			id,
			method: pending.method,
			ok,
			durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
			startedAtMs: pending.startedAtMs,
			endedAtMs,
			errorCode
		}));
	}
	invoke(label, callback) {
		try {
			callback();
		} catch (error) {
			this.opts.onCallbackError?.(label, error);
		}
	}
};
//#endregion
//#region packages/gateway-client/src/protocol-client.ts
/**
* Browser-safe gateway wire client. Environment adapters own transport and auth
* policy; this class owns the single socket/handshake/reconnect/frame state machine.
*/
var GatewayProtocolClient = class {
	constructor(opts) {
		this.opts = opts;
		this.socket = null;
		this.listeners = new GatewayEventListeners();
		this.stopped = true;
		this.generation = 0;
		this.connectionAbort = null;
		this.lastSeq = null;
		this.connectNonce = null;
		this.serverCapabilities = [];
		this.connectSent = false;
		this.connectRequestSent = false;
		this.handshakeTimer = null;
		this.reconnectSignal = null;
		this.socketOpened = false;
		this.helloReceived = false;
		this.connectTiming = null;
		this.reconnectSupervisor = new RetrySupervisor({
			initialMs: opts.reconnect.initialMs,
			maxMs: opts.reconnect.maxMs,
			factor: opts.reconnect.multiplier,
			jitter: 0
		});
		this.requests = new GatewayPendingRequests({
			createRequestId: opts.createRequestId,
			createRequestError: opts.createRequestError,
			createRequestTimeoutError: opts.createRequestTimeoutError,
			createRequestAbortError: opts.createRequestAbortError,
			requestTimeoutMs: opts.requestTimeoutMs,
			nowMs: () => this.nowMs(),
			onTiming: opts.onRequestTiming,
			onCallbackError: opts.onCallbackError
		});
	}
	get connected() {
		return this.socket?.isOpen() ?? false;
	}
	get hasPendingRequests() {
		return this.requests.hasPending;
	}
	get connecting() {
		return this.connectSent && !this.helloReceived;
	}
	get hasUnboundedPendingRequests() {
		return this.requests.hasUnboundedPending;
	}
	start() {
		if (this.socket || this.reconnectSignal) return;
		this.stopped = false;
		this.reconnectSupervisor.cancel();
		this.connect();
	}
	stop() {
		this.stopped = true;
		this.connectionAbort?.abort();
		this.clearHandshakeTimer();
		this.reconnectSignal = null;
		this.reconnectSupervisor.reset();
		const socket = this.socket;
		if (socket && this.opts.notifyStoppedClose) this.stoppedSocket = {
			socket,
			context: this.closeContext()
		};
		this.socket = null;
		this.connectFailure = void 0;
		this.connectTiming = null;
		this.requests.flush(/* @__PURE__ */ new Error("gateway client stopped"));
		socket?.close();
	}
	request(method, params, options) {
		const socket = this.socket;
		if (!socket?.isOpen()) return Promise.reject(/* @__PURE__ */ new Error("gateway not connected"));
		if (typeof method !== "string" || method.length === 0) return Promise.reject(/* @__PURE__ */ new Error("invalid request frame: method must be a non-empty string"));
		return this.requests.request(socket, method, params, options);
	}
	addEventListener(listener) {
		return this.listeners.add(listener);
	}
	closeSocket(code, reason) {
		this.connectionAbort?.abort();
		this.socket?.close(code, reason);
	}
	resetReconnectBackoff(initialMs) {
		this.reconnectSignal = null;
		this.reconnectSupervisor.reset(initialMs);
	}
	recordTiming(phase, generation, plan, detail) {
		const now = this.nowMs();
		const state = this.connectTiming;
		if (!state || state.generation !== generation) return;
		state.hasChallenge ||= phase === "challenge";
		state.usedFallback ||= phase === "fallback";
		this.invoke("connect timing", () => this.opts.onTiming?.({
			phase,
			generation,
			durationMs: Math.max(0, now - state.startedAtMs),
			phaseDurationMs: Math.max(0, now - state.lastAtMs),
			hasChallenge: state.hasChallenge,
			usedFallback: state.usedFallback,
			plan,
			detail
		}));
		state.lastAtMs = now;
		if (phase === "hello" || phase === "failed") this.connectTiming = null;
	}
	connect() {
		if (this.stopped) return;
		const generation = this.generation + 1;
		this.lastSeq = null;
		this.connectNonce = null;
		this.connectChallengeTs = void 0;
		this.serverCapabilities = [];
		this.connectSent = this.connectRequestSent = false;
		this.socketOpened = false;
		this.helloReceived = false;
		this.connectFailure = void 0;
		let socket;
		try {
			socket = this.opts.createSocket({
				open: () => this.handleOpen(socket, generation),
				message: (data) => this.handleMessage(socket, generation, data),
				close: (code, reason) => this.handleClose(socket, generation, code, reason),
				error: (error) => this.handleSocketError(socket, generation, error)
			});
		} catch (error) {
			const normalized = error instanceof Error ? error : new Error(String(error));
			this.opts.onSocketFactoryError?.(normalized);
			this.opts.onConnectError?.(normalized);
			if (this.opts.rethrowSocketFactoryError?.(normalized)) {
				if (this.generation > 0 && !this.stopped && !this.socket && !this.reconnectSignal) this.opts.onReconnectStopped?.(normalized);
				throw normalized;
			}
			if (this.opts.shouldRetrySocketFactoryError?.(normalized) && !this.stopped && !this.socket && !this.reconnectSignal) this.scheduleReconnect();
			else if (this.generation > 0 && !this.stopped && !this.socket && !this.reconnectSignal) this.opts.onReconnectStopped?.(normalized);
			return;
		}
		this.generation = generation;
		this.connectionAbort = new AbortController();
		this.socket = socket;
		const now = this.nowMs();
		this.connectTiming = {
			generation,
			startedAtMs: now,
			lastAtMs: now,
			hasChallenge: false,
			usedFallback: false
		};
	}
	handleOpen(socket, generation) {
		if (!this.isActive(socket, generation)) return;
		this.socketOpened = true;
		this.recordTiming("socket-open", generation);
		if (this.connectNonce) {
			this.sendConnect(socket, generation);
			return;
		}
		this.armHandshakeTimer(socket, generation);
	}
	armHandshakeTimer(socket, generation) {
		this.clearHandshakeTimer();
		const armedAt = Date.now();
		this.handshakeTimer = setTimeout(() => {
			this.handshakeTimer = null;
			if (!this.isActive(socket, generation) || this.connectSent || !socket.isOpen()) return;
			if (this.opts.handshake.mode === "fallback") {
				this.recordTiming("fallback", generation);
				this.sendConnect(socket, generation);
				return;
			}
			const elapsedMs = Date.now() - armedAt;
			const error = new Error(this.opts.handshake.timeoutMessage?.(elapsedMs) ?? `gateway connect challenge timeout after ${elapsedMs}ms`);
			this.opts.onConnectError?.(error);
			socket.close(1008, "connect challenge timeout");
		}, this.opts.handshake.timeoutMs);
		this.handshakeTimer.unref?.();
	}
	sendConnect(socket, generation) {
		if (!this.isActive(socket, generation) || !socket.isOpen() || this.connectSent) return;
		this.connectSent = true;
		this.clearHandshakeTimer();
		this.handshakeTimer = startGatewayConnectTimeout(() => {
			if (this.isActive(socket, generation) && !this.helloReceived) socket.close(4e3, "connect timeout");
		});
		let planOrPromise;
		try {
			planOrPromise = this.opts.buildConnectPlan({
				nonce: this.connectNonce,
				challengeTs: this.connectChallengeTs,
				serverCapabilities: this.serverCapabilities,
				generation,
				...this.connectAuthority(socket, generation)
			});
		} catch (error) {
			this.handleConnectPlanError(socket, generation, error);
			return;
		}
		if (planOrPromise instanceof Promise) {
			planOrPromise.then((plan) => this.sendConnectPlan(socket, generation, plan)).catch((error) => this.handleConnectPlanError(socket, generation, error));
			return;
		}
		this.sendConnectPlan(socket, generation, planOrPromise);
	}
	handleConnectPlanError(socket, generation, error) {
		if (!this.isConnectCurrent(socket, generation)) return;
		const normalized = error instanceof Error ? error : new Error(String(error));
		const outcome = this.opts.onConnectPlanError?.(normalized) ?? {
			closeCode: 1008,
			closeReason: "connect failed"
		};
		this.opts.onConnectError?.(outcome.error ?? normalized);
		if (outcome.stop) this.stopped = true;
		socket.close(outcome.closeCode, outcome.closeReason);
	}
	sendConnectPlan(socket, generation, plan) {
		if (!this.isConnectCurrent(socket, generation)) return;
		const context = {
			...this.connectAuthority(socket, generation),
			generation,
			nonce: this.connectNonce,
			challengeTs: this.connectChallengeTs,
			plan
		};
		this.recordTiming("connect-plan-ready", generation, plan);
		this.recordTiming("request-sent", generation, plan);
		this.connectRequestSent = true;
		this.request("connect", this.opts.buildConnectParams(plan)).then((hello) => {
			if (!this.isConnectCurrent(socket, generation)) return;
			this.helloReceived = true;
			this.requests.setSuspensionPhase(hello.snapshot?.suspension?.phase);
			this.clearHandshakeTimer();
			this.connectFailure = void 0;
			this.reconnectSupervisor.reset();
			this.recordTiming("hello", generation, plan);
			const publishHello = () => {
				if (!this.isConnectCurrent(socket, generation)) return;
				this.invoke("hello", () => this.opts.onHello?.(hello));
			};
			const accepted = this.opts.onConnectHello?.(hello, context);
			if (accepted instanceof Promise) return accepted.then(publishHello);
			return publishHello();
		}).catch((error) => {
			if (!this.isActive(socket, generation)) return;
			const requestError = error instanceof GatewayProtocolRequestError ? error : new GatewayProtocolRequestError({ message: String(error) });
			this.connectFailure = { error: requestError };
			const outcome = this.opts.onConnectFailure?.(requestError, context) ?? {
				closeCode: 1008,
				closeReason: "connect failed"
			};
			const applyFailure = (decision) => {
				if (!this.isActive(socket, generation)) return;
				this.connectFailure = {
					error: requestError,
					reconnectDelayMs: decision.reconnectDelayMs
				};
				if (decision.stop) this.stopped = true;
				this.connectionAbort?.abort();
				socket.close(decision.closeCode, decision.closeReason);
			};
			if (outcome instanceof Promise) return outcome.then(applyFailure);
			return applyFailure(outcome);
		}).catch((error) => {
			if (!this.isConnectCurrent(socket, generation)) return;
			this.opts.onConnectError?.(error instanceof Error ? error : new Error(String(error)));
			this.closeSocket(1008, "connect failed");
		});
	}
	handleMessage(socket, generation, raw) {
		if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
		let parsed;
		try {
			parsed = JSON.parse(raw);
		} catch (error) {
			this.opts.onParseError?.(error);
			return;
		}
		if (isGatewayEventFrame(parsed)) {
			this.opts.onActivity?.();
			if (parsed.event === "connect.challenge") {
				const payload = parsed.payload;
				const nonce = typeof payload?.nonce === "string" ? payload.nonce.trim() : "";
				if (!nonce) {
					if (this.opts.handshake.mode === "require-challenge") {
						const error = /* @__PURE__ */ new Error("gateway connect challenge missing nonce");
						this.opts.onConnectError?.(error);
						socket.close(1008, "connect challenge missing nonce");
					}
					return;
				}
				this.connectNonce = nonce;
				this.serverCapabilities = Array.isArray(payload?.capabilities) ? payload.capabilities.filter((value) => typeof value === "string") : [];
				const challengeTs = payload?.ts;
				this.connectChallengeTs = typeof challengeTs === "number" && Number.isSafeInteger(challengeTs) && challengeTs >= 0 ? challengeTs : null;
				this.recordTiming("challenge", generation);
				this.sendConnect(socket, generation);
				return;
			}
			const seq = typeof parsed.seq === "number" ? parsed.seq : null;
			if (seq !== null) {
				if (this.lastSeq !== null && seq > this.lastSeq + 1) {
					const expected = this.lastSeq + 1;
					const state = asRecord(parsed.payload).state;
					if (parsed.event === "chat" && (state === "final" || state === "error" || state === "aborted")) this.dispatchEvent(socket, generation, parsed);
					if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
					this.invoke("gap", () => this.opts.onGap?.({
						expected,
						received: seq
					}));
					if (this.isActive(socket, generation) && !this.connectionAbort?.signal.aborted) this.closeSocket(4e3, "event sequence gap");
					return;
				}
				this.lastSeq = seq;
			}
			this.dispatchEvent(socket, generation, parsed);
			return;
		}
		if (!isGatewayResponseFrame(parsed)) return;
		this.opts.onActivity?.();
		this.requests.handleResponse(parsed);
	}
	dispatchEvent(socket, generation, event) {
		const listeners = this.listeners.snapshot();
		if (event.event === "gateway.suspension" && typeof event.payload === "object" && event.payload !== null && "phase" in event.payload) this.requests.setSuspensionPhase(event.payload.phase);
		this.invoke("event", () => this.opts.onEvent?.(event));
		for (const [listener, subscription] of listeners) {
			if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
			if (this.listeners.isCurrent(listener, subscription)) this.invoke("event listener", () => listener(event));
		}
	}
	handleClose(socket, generation, code, reason) {
		if (this.socket !== socket) {
			if (this.stoppedSocket?.socket === socket) {
				const context = {
					...this.stoppedSocket.context,
					code,
					reason
				};
				this.stoppedSocket = void 0;
				this.invoke("close", () => this.opts.onClose?.(context, {
					retry: false,
					notify: true
				}));
			}
			return;
		}
		this.socket = null;
		this.connectionAbort?.abort();
		this.clearHandshakeTimer();
		const context = {
			...this.closeContext(),
			code,
			reason,
			generation
		};
		this.connectFailure = void 0;
		const decision = this.opts.resolveClose(context);
		this.requests.flush(decision.pendingError ?? context.connectFailure?.error ?? /* @__PURE__ */ new Error(`gateway closed (${code}): ${reason}`));
		this.invoke("close", () => this.opts.onClose?.(context, decision));
		if (decision.retry && !this.stopped && !this.socket && !this.reconnectSignal) {
			const error = context.connectFailure?.error;
			const retryAfterMs = error instanceof GatewayProtocolRequestError && error.retryable && error.retryAfterMs !== void 0 && Number.isFinite(error.retryAfterMs) && error.retryAfterMs > 0 ? error.retryAfterMs : void 0;
			this.scheduleReconnect(decision.reconnectDelayMs ?? context.connectFailure?.reconnectDelayMs, retryAfterMs);
		}
	}
	handleSocketError(socket, generation, error) {
		if (!this.isActive(socket, generation) || this.connectSent) return;
		this.connectFailure = { error };
		this.opts.onConnectError?.(error);
	}
	scheduleReconnect(overrideMs, minimumMs = 0) {
		if (overrideMs !== void 0) this.reconnectSupervisor.nextDelayOverrideMs = overrideMs;
		const retry = this.reconnectSupervisor.next();
		if (!retry) return;
		this.reconnectSignal = retry.signal;
		let delayMs = overrideMs;
		if (delayMs === void 0) {
			const base = Math.max(retry.delayMs, minimumMs);
			const ceiling = Math.max(this.opts.reconnect.maxMs, Math.min(Number.MAX_VALUE, minimumMs * 1.2));
			const lower = Math.max(minimumMs, Math.min(base, ceiling / 1.2));
			const upper = Math.min(base * 1.2, ceiling);
			delayMs = Math.ceil(lower + Math.random() * (upper - lower));
		}
		delayMs = resolveSleepDelayMs(delayMs);
		sleepWithAbort(delayMs, retry.signal).then(() => {
			if (this.reconnectSignal !== retry.signal) return;
			this.reconnectSignal = null;
			this.invoke("reconnect", () => this.connect());
		}, () => {
			if (this.reconnectSignal === retry.signal) this.reconnectSignal = null;
		});
		this.invoke("reconnect scheduled", () => this.opts.onReconnectScheduled?.(delayMs, retry.signal));
	}
	closeContext() {
		return {
			generation: this.generation,
			socketOpened: this.socketOpened,
			helloReceived: this.helloReceived,
			connectRequestSent: this.connectRequestSent,
			connectFailure: this.connectFailure
		};
	}
	isConnectCurrent(socket, generation) {
		return this.isActive(socket, generation) && socket.isOpen() && !this.connectionAbort?.signal.aborted;
	}
	connectAuthority(socket, generation) {
		const signal = this.connectionAbort?.signal;
		if (!signal) throw new Error("gateway connection authority is unavailable");
		return {
			signal,
			assertCurrent: () => {
				signal.throwIfAborted();
				if (!this.isConnectCurrent(socket, generation)) throw new Error("gateway connection retired");
			}
		};
	}
	isActive(socket, generation) {
		return !this.stopped && this.socket === socket && this.generation === generation;
	}
	nowMs() {
		return this.opts.nowMs?.() ?? Date.now();
	}
	clearHandshakeTimer() {
		this.handshakeTimer = clearGatewayConnectTimeout(this.handshakeTimer);
	}
	invoke(label, callback) {
		try {
			callback();
		} catch (error) {
			this.opts.onCallbackError?.(label, error);
		}
	}
};
//#endregion
//#region packages/gateway-client/src/reconnect-policy.ts
const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
	ConnectErrorDetailCodes.AUTH_TOKEN_MISSING,
	ConnectErrorDetailCodes.AUTH_BOOTSTRAP_TOKEN_INVALID,
	ConnectErrorDetailCodes.AUTH_PASSWORD_MISSING,
	ConnectErrorDetailCodes.AUTH_PASSWORD_MISMATCH,
	ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
	ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH,
	ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH,
	ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED,
	ConnectErrorDetailCodes.AUTH_VERIFIED_USER_REQUIRED,
	ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
	ConnectErrorDetailCodes.PAIRING_REQUIRED,
	ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
	ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED
]);
function shouldPauseGatewayReconnect(params) {
	const code = readConnectErrorDetailCode(params.details);
	if (!code) return false;
	const pairing = readPairingConnectErrorDetails(params.details);
	if (code === ConnectErrorDetailCodes.PAIRING_REQUIRED && (pairing?.pauseReconnect === false || pairing?.recommendedNextStep === "wait_then_retry")) return false;
	if (code === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH) return params.tokenMismatchIsTerminal === true && !params.deviceTokenRetryPending;
	if (code === ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED) return !params.deviceTokenRetryPending;
	return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
}
//#endregion
//#region packages/gateway-client/src/request-error.ts
var GatewayClientRequestError = class extends GatewayProtocolRequestError {
	constructor(error) {
		super({
			...error,
			message: formatConnectErrorMessage({
				message: error.message,
				details: error.details
			})
		});
		this.name = "GatewayClientRequestError";
	}
};
const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
function markGatewayConnectAssemblyError(error) {
	Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
		configurable: true,
		value: true
	});
	return error;
}
function isGatewayConnectAssemblyError(value) {
	return value instanceof Error && value[GATEWAY_CONNECT_ASSEMBLY_ERROR] === true;
}
//#endregion
//#region packages/gateway-client/src/websocket-data.ts
function rawDataToString(data, encoding = "utf8") {
	if (Array.isArray(data)) return Buffer$1.concat(data).toString(encoding);
	return data instanceof ArrayBuffer ? Buffer$1.from(data).toString(encoding) : data.toString(encoding);
}
//#endregion
//#region packages/gateway-client/src/websocket-transport.ts
const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
	"loopback",
	"private",
	"linkLocal",
	"carrierGradeNat"
]);
const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
	"loopback",
	"linkLocal",
	"uniqueLocal",
	"deprecatedSiteLocal"
]);
function isPrivateOrLoopbackIpAddress(address) {
	return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
}
function isGatewayLoopbackHost(host) {
	const parsed = parseHostForAddressChecks(host);
	return Boolean(parsed && (parsed.isLocalhost || isLoopbackIpAddress(parsed.unbracketedHost)));
}
function isPrivateOrLoopbackHost(host) {
	const parsed = parseHostForAddressChecks(host);
	if (!parsed) return false;
	if (parsed.isLocalhost) return true;
	const address = parseGatewayIpAddress(parsed.unbracketedHost);
	return Boolean(address && isPrivateOrLoopbackIpAddress(address));
}
function isTrustedPlaintextWebSocketHost(hostname) {
	if (isPrivateOrLoopbackHost(hostname)) return true;
	const normalized = hostname.toLowerCase().trim().replace(/\.+$/, "");
	return normalized.endsWith(".local") || normalized.endsWith(".ts.net");
}
function isSecureWebSocketUrl(rawUrl, options) {
	try {
		const url = new URL(rawUrl);
		const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
		if (protocol === "wss:") return true;
		if (protocol !== "ws:") return false;
		if (isGatewayLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
		if (options?.allowPrivateWs === true) {
			const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
			return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
		}
		return false;
	} catch {
		return false;
	}
}
var GatewayWebSocketTransportConfigurationError = class extends Error {};
var GatewayWebSocketTlsPinError = class extends Error {};
function resolveGatewayWebSocketTransport(params) {
	const usesTls = isWssUrl(params.url);
	if (params.tlsFingerprint && !usesTls) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint requires wss:// gateway url");
	const allowPrivateWs = (params.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
	if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) {
		let displayHost = params.url;
		try {
			displayHost = new URL(params.url).hostname || params.url;
		} catch {}
		throw new GatewayWebSocketTransportConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
	}
	const normalize = params.normalizeTlsFingerprint ?? normalizeTlsFingerprint;
	const expectedFingerprint = params.tlsFingerprint ? normalizeTlsFingerprint(params.tlsFingerprint) : void 0;
	if (params.tlsFingerprint && !expectedFingerprint) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint must be a SHA-256 fingerprint");
	const options = { ...params.options };
	if (usesTls && params.tlsServerName) {
		const peerName = params.tlsServerName;
		options.servername = isIP(peerName) ? "" : peerName;
		options.checkServerIdentity = (_hostname, certificate) => checkServerIdentity(peerName, certificate);
	}
	if (usesTls && expectedFingerprint) applyGatewayWebSocketTlsPin(options, expectedFingerprint, normalize);
	return { options };
}
function applyGatewayWebSocketTlsPin(options, expectedFingerprint, normalize = normalizeTlsFingerprint) {
	options.rejectUnauthorized = false;
	const headers = { ...options.headers };
	const deferredHeaders = Object.entries(headers).filter(([key]) => key.toLowerCase() === "expect");
	for (const [key] of deferredHeaders) delete headers[key];
	options.headers = headers;
	options.finishRequest = (request) => {
		request.once("socket", (socket) => {
			if (!(socket instanceof TLSSocket)) {
				request.destroy(new GatewayWebSocketTlsPinError("gateway tls fingerprint unavailable"));
				return;
			}
			const validatePin = () => {
				if (request.destroyed) return;
				const canonicalFingerprint = normalizeTlsFingerprint(socket.getPeerCertificate()?.fingerprint256);
				const fingerprint = canonicalFingerprint ? normalize(canonicalFingerprint) : "";
				if (!fingerprint || fingerprint !== normalize(expectedFingerprint)) {
					request.destroy(new GatewayWebSocketTlsPinError(fingerprint ? "gateway tls fingerprint mismatch" : "gateway tls fingerprint unavailable"));
					return;
				}
				for (const [key, value] of deferredHeaders) if (value !== void 0) request.setHeader(key, value);
				request.end();
			};
			if (socket.secureConnecting) {
				socket.once("secureConnect", validatePin);
				request.once("close", () => socket.off("secureConnect", validatePin));
			} else validatePin();
		});
	};
}
//#endregion
//#region packages/gateway-client/src/websocket.ts
const require = createRequire(import.meta.url);
const wsPackageRoot = path.dirname(require.resolve("ws/package.json"));
const WebSocket = require(path.join(wsPackageRoot, "lib/websocket.js"));
require(path.join(wsPackageRoot, "lib/websocket-server.js"));
require(path.join(wsPackageRoot, "lib/stream.js"));
//#endregion
//#region packages/gateway-client/src/client.ts
const DEFAULT_HOST_DEPS = {
	loadOrCreateDeviceIdentity: () => void 0,
	signDevicePayload: () => {
		throw new Error("GatewayClient device signature dependency is not configured");
	},
	publicKeyRawBase64UrlFromPem: () => {
		throw new Error("GatewayClient public key dependency is not configured");
	},
	loadDeviceAuthToken: () => null,
	storeDeviceAuthToken: () => {},
	clearDeviceAuthToken: () => {},
	beforeConnect: () => {},
	registerGatewayLoopbackBypass: () => void 0,
	logDebug: () => {},
	logError: () => {},
	redactForLog: (message) => message,
	normalizeTlsFingerprint
};
function resolveHostDeps(overrides) {
	return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
}
const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
const DEFAULT_CLIENT_VERSION = "0.0.0";
var GatewayClientRequestTimeoutError = class extends GatewayProtocolRequestTimeoutError {
	constructor(params) {
		super(params, `gateway request timeout for ${params.method}`);
		this.name = "GatewayClientRequestTimeoutError";
	}
};
var GatewayClientTransportPolicyError = class extends GatewayWebSocketTransportConfigurationError {};
const FORCE_STOP_TERMINATE_GRACE_MS = 250;
const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
const MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES = 1;
function resolveLegacyNodePlatform(platform) {
	switch (platform) {
		case "macos": return "darwin";
		case "windows": return "win32";
		default: return;
	}
}
var GatewayClient$1 = class {
	constructor(opts) {
		this.ws = null;
		this.stopped = false;
		this.useLegacyNodeProtocolEnvelope = false;
		this.nodeProtocolTransitionPending = false;
		this.pendingDeviceTokenRetry = false;
		this.deviceTokenRetryBudgetUsed = false;
		this.approvalRuntimeTokenCompatibilityDisabled = false;
		this.approvalRuntimeTokenRetryBudgetUsed = false;
		this.lastTick = null;
		this.tickIntervalMs = 3e4;
		this.tickTimer = null;
		this.pendingStop = null;
		this.transportValidated = false;
		this.suppressedTransientPreHelloCleanCloses = 0;
		this.deps = resolveHostDeps(opts.hostDeps);
		this.deviceAuth = new GatewayClientDeviceAuth(this.deps);
		this.opts = {
			...opts,
			deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
		};
		this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? opts.requestTimeoutMs : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
		const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
			env: this.opts.env,
			configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
		});
		this.protocol = new GatewayProtocolClient({
			createSocket: (handlers) => this.createSocket(handlers),
			createRequestId: randomUUID,
			createRequestError: (error) => new GatewayClientRequestError(error),
			createRequestTimeoutError: (method, timeoutMs, requestSent) => new GatewayClientRequestTimeoutError({
				method,
				timeoutMs,
				requestSent
			}),
			createRequestAbortError: createGatewayRequestAbortError$1,
			buildConnectPlan: ({ nonce, challengeTs, serverCapabilities, generation, ...authority }) => {
				if (!nonce) throw new Error("gateway connect challenge missing nonce");
				if (this.opts.deviceIdentity && challengeTs == null) throw new Error("gateway connect challenge timestamp invalid");
				const role = this.opts.role ?? "operator";
				const assemble = (storedAuth) => {
					authority.assertCurrent();
					const assembled = this.assembleConnectParams({
						role,
						nonce,
						signedAtMs: challengeTs ?? Date.now(),
						serverCapabilities,
						storedAuth
					});
					this.connectionStoredToken = {
						generation,
						token: assembled.storedToken ?? null
					};
					return assembled;
				};
				const storedAuth = this.opts.deviceIdentity ? this.deviceAuth.load({
					deviceId: this.opts.deviceIdentity.deviceId,
					role,
					env: this.opts.env,
					...authority
				}) : null;
				return storedAuth instanceof Promise ? storedAuth.then(assemble) : assemble(storedAuth);
			},
			buildConnectParams: (assembled) => assembled.params,
			onConnectPlanError: (error) => {
				this.stopped = true;
				const marked = markGatewayConnectAssemblyError(error);
				const msg = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
				if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(msg);
				else this.logError(msg);
				return {
					closeCode: 1008,
					closeReason: "connect failed",
					stop: true,
					error: marked
				};
			},
			onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan, context),
			onHello: (hello) => {
				this.opts.onHelloOk?.(hello);
			},
			onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
			resolveClose: (context) => this.resolveClose(context),
			onClose: (context, decision) => {
				if (this.tickTimer) {
					clearInterval(this.tickTimer);
					this.tickTimer = null;
				}
				if (decision.notify) this.opts.onClose?.(context.code, context.reason, this.closeInfo(context));
			},
			notifyStoppedClose: true,
			onConnectError: (error) => this.notifyConnectError(error),
			onReconnectStopped: (error) => this.notifyReconnectPaused({
				code: 1008,
				reason: error.message,
				detailCode: null
			}),
			onParseError: (error) => this.logDebug(`gateway client parse error: ${formatGatewayClientErrorForLog(error)}`),
			onEvent: (event) => this.opts.onEvent?.(event),
			onGap: (info) => this.opts.onGap?.(info),
			onRequestTiming: (timing) => this.opts.onRequestTiming?.(timing),
			onActivity: () => {
				this.lastTick = Date.now();
			},
			onCallbackError: (label, error) => this.logDebug(`gateway client ${label === "hello" ? "hello-ok" : label === "gap" ? "event" : label} handler error: ${formatGatewayClientErrorForLog(error)}`),
			handshake: {
				mode: "require-challenge",
				timeoutMs: connectChallengeTimeoutMs,
				timeoutMessage: (elapsedMs) => `gateway connect challenge timeout (waited ${elapsedMs}ms, limit ${connectChallengeTimeoutMs}ms)`
			},
			reconnect: {
				initialMs: 1e3,
				multiplier: 2,
				maxMs: 3e4
			},
			requestTimeoutMs: this.requestTimeoutMs,
			shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayWebSocketTransportConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
			rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
		});
	}
	/** Current transport state, including CLOSING before the close callback fires.
	* This is not authentication or readiness evidence on its own. */
	get connected() {
		return !this.stopped && this.protocol.connected;
	}
	getConnectionMetadata() {
		return {
			clientName: this.opts.clientName,
			hasDeviceIdentity: Boolean(this.opts.deviceIdentity),
			mode: this.opts.mode,
			preauthHandshakeTimeoutMs: this.opts.preauthHandshakeTimeoutMs
		};
	}
	updateNodeManifest(manifest) {
		this.opts = {
			...this.opts,
			caps: [...manifest.caps],
			commands: [...manifest.commands],
			computerUse: manifest.computerUse === void 0 ? void 0 : structuredClone(manifest.computerUse),
			workerRuns: manifest.workerRuns ? structuredClone(manifest.workerRuns) : void 0
		};
		if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
	}
	start() {
		if (this.stopped) return;
		this.protocol.start();
	}
	createSocket(handlers) {
		const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
		const configuredEdgeAuthHeaders = this.opts.edgeAuthHeaders;
		const edgeAuthHeaders = configuredEdgeAuthHeaders && Object.keys(configuredEdgeAuthHeaders).length > 0 ? configuredEdgeAuthHeaders : void 0;
		if (edgeAuthHeaders && new URL(url).protocol !== "wss:") throw new GatewayWebSocketTransportConfigurationError("edge auth headers require a wss:// Gateway URL");
		const handshakeTimeoutMs = resolvePreauthHandshakeTimeoutMs({
			env: this.opts.env,
			configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
		});
		const transport = resolveGatewayWebSocketTransport({
			url,
			tlsFingerprint: this.opts.tlsFingerprint,
			tlsServerName: this.opts.tlsServerName,
			env: this.opts.env,
			normalizeTlsFingerprint: this.deps.normalizeTlsFingerprint,
			options: {
				maxPayload: 26214400,
				handshakeTimeout: handshakeTimeoutMs,
				...this.opts.origin ? { origin: this.opts.origin } : {},
				...edgeAuthHeaders ? {
					followRedirects: false,
					headers: edgeAuthHeaders
				} : {}
			}
		});
		this.deps.beforeConnect();
		let ws;
		let unregisterGatewayLoopbackBypass;
		try {
			unregisterGatewayLoopbackBypass = this.deps.registerGatewayLoopbackBypass(url);
		} catch (error) {
			throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
		}
		try {
			ws = new WebSocket(url, transport.options);
			ws.binaryType = "nodebuffer";
		} catch (error) {
			throw error instanceof Error ? error : new Error(String(error));
		} finally {
			unregisterGatewayLoopbackBypass?.();
		}
		this.ws = ws;
		this.transportValidated = false;
		let upgradeError;
		ws.on("open", () => {
			this.transportValidated = true;
			handlers.open();
		});
		ws.on("message", (data) => handlers.message(rawDataToString(data)));
		ws.on("close", (code, reason) => {
			const reasonText = reason.toString();
			if (this.ws === ws) this.ws = null;
			this.resolvePendingStop(ws);
			handlers.close(code, reasonText);
		});
		ws.on("unexpected-response", (request, response) => {
			readUpgradeErrorBody(response).then((body) => {
				const statusCode = response.statusCode;
				let gatewayError;
				try {
					const parsed = JSON.parse(body);
					const parsedError = isRecord(parsed) ? parsed.error : void 0;
					gatewayError = isRecord(parsedError) ? parsedError : void 0;
				} catch {}
				const rawLocation = response.headers.location;
				const location = rawLocation ? redactSensitiveUrlLikeString(Array.isArray(rawLocation) ? rawLocation[0] ?? "" : rawLocation) : void 0;
				upgradeError = new GatewayClientRequestError({
					code: "UNAVAILABLE",
					message: `gateway rejected websocket upgrade (HTTP ${statusCode ?? "unknown"})${body ? `: ${body}` : ""}`,
					retryable: true,
					details: {
						reason: "websocket-upgrade-rejected",
						...statusCode === void 0 ? {} : { httpStatus: statusCode },
						...location ? { location } : {},
						...typeof gatewayError?.type === "string" ? {
							gatewayErrorType: gatewayError.type,
							...typeof gatewayError.message === "string" ? { gatewayErrorMessage: gatewayError.message } : {}
						} : {}
					}
				});
				handlers.error(upgradeError);
				request.destroy();
				ws.close();
			});
		});
		ws.on("error", (err) => {
			if (upgradeError) return;
			if (err.message === "Opening handshake has timed out") Object.assign(err, { code: "ETIMEDOUT" });
			this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
			handlers.error(err instanceof Error ? err : new Error(String(err)));
		});
		return {
			isOpen: () => ws.readyState === WebSocket.OPEN,
			send: (data) => ws.send(data),
			close: (code, reason) => ws.close(code, reason)
		};
	}
	stop() {
		this.beginStop();
	}
	async stopAndWait(opts) {
		const stopPromise = this.beginStop();
		const timeoutMs = opts?.timeoutMs === void 0 ? STOP_AND_WAIT_TIMEOUT_MS : resolveSafeTimeoutDelayMs(opts.timeoutMs);
		let timeout = null;
		try {
			await Promise.race([stopPromise, new Promise((_, reject) => {
				timeout = setTimeout(() => {
					reject(/* @__PURE__ */ new Error(`gateway client stop timed out after ${timeoutMs}ms`));
				}, timeoutMs);
				timeout.unref?.();
			})]);
		} finally {
			if (timeout) clearTimeout(timeout);
			await this.deviceAuth.drain();
		}
	}
	beginStop() {
		this.stopped = true;
		this.pendingDeviceTokenRetry = false;
		this.deviceTokenRetryBudgetUsed = false;
		if (this.tickTimer) {
			clearInterval(this.tickTimer);
			this.tickTimer = null;
		}
		if (this.pendingStop) return this.pendingStop.promise;
		const ws = this.ws;
		this.ws = null;
		if (ws) {
			const pendingStop = this.createPendingStop(ws);
			const forceTerminateTimer = setTimeout(() => {
				try {
					ws.terminate();
				} finally {
					this.resolvePendingStop(ws);
				}
			}, FORCE_STOP_TERMINATE_GRACE_MS);
			forceTerminateTimer.unref?.();
			pendingStop.terminateTimer = forceTerminateTimer;
			if (this.protocol.connecting) {
				const error = /* @__PURE__ */ new Error("gateway client stopped");
				this.notifyConnectError(error);
				this.logDebug(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
			}
			this.protocol.stop();
			return pendingStop.promise;
		}
		this.protocol.stop();
		return null;
	}
	createPendingStop(ws) {
		if (this.pendingStop?.ws === ws) return this.pendingStop;
		let resolve = () => {};
		const promise = new Promise((done) => {
			resolve = done;
		});
		this.pendingStop = {
			ws,
			promise,
			resolve
		};
		return this.pendingStop;
	}
	resolvePendingStop(ws) {
		if (this.pendingStop?.ws !== ws) return;
		const { resolve, terminateTimer } = this.pendingStop;
		if (terminateTimer) clearTimeout(terminateTimer);
		this.pendingStop = null;
		resolve();
	}
	logDebug(message) {
		this.deps.logDebug(this.deps.redactForLog(message));
	}
	logError(message) {
		this.deps.logError(this.deps.redactForLog(message));
	}
	assembleConnectParams(params) {
		const { role, nonce, signedAtMs } = params;
		const selectedAuth = this.selectConnectAuth(params.storedAuth);
		const { authDeviceToken, authApprovalRuntimeToken, authAgentRuntimeIdentityToken, signatureToken, resolvedDeviceToken, storedToken, storedScopes, usingStoredDeviceToken } = selectedAuth;
		if (this.pendingDeviceTokenRetry && authDeviceToken) this.pendingDeviceTokenRetry = false;
		const auth = buildGatewayConnectAuth(selectedAuth);
		const scopes = resolveGatewayConnectScopes({
			requestedScopes: this.opts.scopes,
			usingStoredDeviceToken,
			storedScopes,
			defaultScopes: ["operator.admin"]
		});
		const clientMode = this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND;
		const clientId = this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
		const isBuiltInNodeHost = role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE && clientId === GATEWAY_CLIENT_NAMES.NODE_HOST;
		const negotiatesNodeProtocol = this.shouldNegotiateLegacyNodeProtocol();
		const useLegacyNodeProtocolEnvelope = isBuiltInNodeHost && (this.useLegacyNodeProtocolEnvelope || this.opts.maxProtocol === 3 && (this.opts.minProtocol ?? 3) <= 3);
		const minProtocol = useLegacyNodeProtocolEnvelope ? 3 : negotiatesNodeProtocol ? 4 : this.opts.minProtocol ?? (clientMode === GATEWAY_CLIENT_MODES.PROBE ? 3 : role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE ? 3 : 4);
		const maxProtocol = useLegacyNodeProtocolEnvelope ? 3 : negotiatesNodeProtocol ? 4 : this.opts.maxProtocol ?? 4;
		const configuredPlatform = this.opts.platform ?? process.platform;
		const platform = useLegacyNodeProtocolEnvelope ? resolveLegacyNodePlatform(configuredPlatform) ?? configuredPlatform : configuredPlatform;
		const deviceFamily = useLegacyNodeProtocolEnvelope ? void 0 : this.opts.deviceFamily;
		return {
			params: {
				minProtocol,
				maxProtocol,
				client: {
					id: clientId,
					displayName: this.opts.clientDisplayName,
					version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
					buildId: this.opts.clientBuildId,
					platform,
					deviceFamily,
					modelIdentifier: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.modelIdentifier,
					mode: clientMode,
					instanceId: this.opts.instanceId
				},
				...resolveModelCatalogConnect({
					caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
					modelCatalog: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.modelCatalog,
					serverCapabilities: params.serverCapabilities
				}),
				commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
				computerUse: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.computerUse,
				workerRuns: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.workerRuns,
				permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
				pathEnv: this.opts.pathEnv,
				auth,
				role,
				scopes,
				device: this.buildDeviceConnectParams({
					nonce,
					role,
					scopes,
					signatureToken,
					signedAtMs,
					platform,
					deviceFamily,
					clientMode
				})
			},
			authApprovalRuntimeToken,
			authAgentRuntimeIdentityToken,
			resolvedDeviceToken,
			storedScopes,
			storedToken,
			usingStoredDeviceToken
		};
	}
	shouldNegotiateLegacyNodeProtocol() {
		if (this.opts.role !== "node" || this.opts.mode !== GATEWAY_CLIENT_MODES.NODE || this.opts.clientName !== GATEWAY_CLIENT_NAMES.NODE_HOST) return false;
		return (this.opts.minProtocol ?? 3) === 3 && (this.opts.maxProtocol ?? 4) === 4;
	}
	shouldRetryWithAlternateNodeProtocol(error) {
		if (!this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
		const detailCode = readConnectErrorDetailCode(error.details);
		return error.details?.expectedProtocol === (this.useLegacyNodeProtocolEnvelope ? 4 : 3) && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
	}
	buildDeviceConnectParams(params) {
		if (!this.opts.deviceIdentity) return;
		const { nonce, role, scopes, signatureToken, signedAtMs, platform, deviceFamily, clientMode } = params;
		const payload = buildDeviceAuthPayloadV3({
			deviceId: this.opts.deviceIdentity.deviceId,
			clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
			clientMode,
			role,
			scopes,
			signedAtMs,
			token: signatureToken ?? null,
			nonce,
			platform,
			deviceFamily
		});
		const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
		return {
			id: this.opts.deviceIdentity.deviceId,
			publicKey: this.deps.publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem),
			signature,
			signedAt: signedAtMs,
			nonce
		};
	}
	handleConnectHello(helloOk, assembled, authority) {
		authority.assertCurrent();
		const role = this.opts.role ?? "operator";
		const authInfo = helloOk.auth;
		const observation = this.connectionStoredToken;
		const persisted = () => {
			if (authInfo?.deviceToken && (authInfo.role ?? role) === role && observation) observation.token = authInfo.deviceToken;
			if (this.opts.preferBootstrapToken) {
				this.opts.token = void 0;
				this.opts.bootstrapToken = void 0;
				this.opts.password = void 0;
				this.opts.preferBootstrapToken = false;
			}
		};
		let stored = void 0;
		if (authInfo?.deviceToken && this.opts.deviceIdentity) {
			const tokenRole = authInfo.role ?? role;
			const scopes = tokenRole === role && authInfo.deviceToken === assembled.storedToken ? assembled.storedScopes ?? authInfo.scopes ?? [] : authInfo.scopes ?? [];
			if (tokenRole === role && observation) observation.receiptToken = authInfo.deviceToken;
			stored = this.deviceAuth.store({
				deviceId: this.opts.deviceIdentity.deviceId,
				role: tokenRole,
				token: authInfo.deviceToken,
				scopes,
				env: this.opts.env,
				...tokenRole === role ? { expectedToken: assembled.storedToken ?? null } : {}
			}, persisted, (error) => {
				assembled.persistenceFailed = true;
				const reported = this.notifyConnectError(error);
				this.logError(`gateway device token persistence failed: ${formatGatewayClientErrorForLog(error)}`);
				return reported;
			});
			if (observation && stored instanceof Promise) observation.persistence = stored;
		} else persisted();
		const complete = () => {
			if (authority.signal.aborted || !this.protocol.connected) return;
			authority.assertCurrent();
			this.completeConnectHello(helloOk);
		};
		return stored instanceof Promise ? stored.then(complete) : complete();
	}
	completeConnectHello(helloOk) {
		const reconnectWithCurrentNodeProtocol = this.useLegacyNodeProtocolEnvelope && this.shouldNegotiateLegacyNodeProtocol() && helloOk.protocol > 3;
		if (reconnectWithCurrentNodeProtocol) this.useLegacyNodeProtocolEnvelope = false;
		this.nodeProtocolTransitionPending = false;
		this.pendingDeviceTokenRetry = false;
		this.deviceTokenRetryBudgetUsed = false;
		this.suppressedTransientPreHelloCleanCloses = 0;
		this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
		if (reconnectWithCurrentNodeProtocol) {
			this.protocol.resetReconnectBackoff(250);
			this.protocol.closeSocket(1012, "gateway protocol upgraded");
			return;
		}
		this.lastTick = Date.now();
		this.startTickWatch();
	}
	handleConnectRequestFailure(error, assembled) {
		if (assembled.persistenceFailed) return {
			closeCode: 1008,
			closeReason: "connect failed"
		};
		const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
		const cleared = this.opts.deviceIdentity && assembled.usingStoredDeviceToken && detailCode === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH ? this.clearDeviceToken(this.opts.deviceIdentity.deviceId, { token: assembled.storedToken }) : void 0;
		const decision = this.resolveConnectRequestFailure(error, assembled);
		return cleared instanceof Promise ? cleared.then(() => decision) : decision;
	}
	resolveConnectRequestFailure(error, assembled) {
		if (this.shouldRetryWithAlternateNodeProtocol(error)) {
			const resetBackoff = !this.nodeProtocolTransitionPending;
			this.useLegacyNodeProtocolEnvelope = !this.useLegacyNodeProtocolEnvelope;
			this.nodeProtocolTransitionPending = true;
			if (resetBackoff) this.protocol.resetReconnectBackoff(250);
			this.logDebug(this.useLegacyNodeProtocolEnvelope ? "gateway rejected protocol v4; retrying node host with protocol v3" : "gateway rejected protocol v3; retrying node host with protocol v4");
			return {
				closeCode: 1008,
				closeReason: "connect retry"
			};
		}
		this.nodeProtocolTransitionPending = false;
		const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
		if (shouldRetryGatewayWithDeviceToken({
			retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
			currentDeviceToken: assembled.resolvedDeviceToken,
			explicitToken: this.opts.token?.trim() || void 0,
			storedToken: assembled.storedToken,
			trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
			errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
		})) {
			this.pendingDeviceTokenRetry = true;
			this.deviceTokenRetryBudgetUsed = true;
			this.protocol.resetReconnectBackoff(250);
		}
		const startupRetryAfterMs = resolveGatewayStartupRetryAfterMs(error);
		if (startupRetryAfterMs !== null) {
			this.logDebug(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
			return {
				closeCode: 1013,
				closeReason: "gateway starting",
				reconnectDelayMs: startupRetryAfterMs
			};
		}
		if (assembled.authAgentRuntimeIdentityToken && this.isUnsupportedConnectAuthField(error, "agentruntimeidentitytoken")) {
			const unsupportedIdentityError = /* @__PURE__ */ new Error("gateway rejected required agent runtime identity auth field; refusing to retry without it");
			this.stopped = true;
			this.notifyConnectError(unsupportedIdentityError);
			this.logError(`gateway connect failed: ${unsupportedIdentityError.message}`);
			return {
				closeCode: 1008,
				closeReason: "connect failed",
				stop: true
			};
		}
		if (!this.approvalRuntimeTokenRetryBudgetUsed && assembled.authApprovalRuntimeToken && this.isUnsupportedConnectAuthField(error, "approvalruntimetoken")) {
			this.approvalRuntimeTokenCompatibilityDisabled = true;
			this.approvalRuntimeTokenRetryBudgetUsed = true;
			this.protocol.resetReconnectBackoff(250);
			this.logDebug("gateway rejected approval runtime auth field; retrying without it");
			return {
				closeCode: 1008,
				closeReason: "connect retry"
			};
		}
		this.notifyConnectError(error);
		const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
		if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error) || detailCode === ConnectErrorDetailCodes.AUTH_RATE_LIMITED) this.logDebug(message);
		else this.logError(message);
		return {
			closeCode: 1008,
			closeReason: "connect failed"
		};
	}
	resolveClose(context) {
		const info = this.closeInfo(context);
		const detailCode = context.connectFailure?.error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(context.connectFailure.error.details) : null;
		const details = context.connectFailure?.error instanceof GatewayClientRequestError ? context.connectFailure.error.details : void 0;
		if (context.code === 1013 && context.connectFailure?.reconnectDelayMs !== void 0) return {
			retry: true,
			notify: this.opts.notifyOnStartupRetry === true,
			reconnectDelayMs: context.connectFailure.reconnectDelayMs
		};
		if (info.transientPreHelloCleanClose && this.suppressedTransientPreHelloCleanCloses < MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES) {
			this.suppressedTransientPreHelloCleanCloses += 1;
			return {
				retry: true,
				notify: true,
				pendingError: /* @__PURE__ */ new Error("gateway transient pre-hello clean close")
			};
		}
		if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
			const error = /* @__PURE__ */ new Error(`gateway closed (${context.code}): ${context.reason}`);
			this.notifyConnectError(error);
			this.logError(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
		}
		this.clearStaleDeviceTokenForClose(context);
		if (shouldPauseGatewayReconnect({
			details,
			deviceTokenRetryPending: this.pendingDeviceTokenRetry,
			tokenMismatchIsTerminal: true,
			protocolMismatchIsTerminal: !this.nodeProtocolTransitionPending,
			clientVersionMismatchIsTerminal: true
		})) {
			this.notifyReconnectPaused({
				code: context.code,
				reason: context.reason,
				detailCode
			});
			return {
				retry: false,
				notify: true
			};
		}
		return {
			retry: true,
			notify: true,
			reconnectDelayMs: context.connectFailure?.reconnectDelayMs
		};
	}
	closeInfo(context) {
		return {
			phase: context.helloReceived ? "post-hello" : "pre-hello",
			socketOpened: context.socketOpened,
			transportValidated: this.transportValidated,
			connectRequestSent: context.connectRequestSent,
			transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === "",
			...context.connectFailure?.error ? { connectError: context.connectFailure.error } : {}
		};
	}
	clearStaleDeviceTokenForClose(context) {
		const { code, reason, generation } = context;
		if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || !this.opts.deviceIdentity) return;
		const observation = this.connectionStoredToken?.generation === generation ? this.connectionStoredToken : { token: void 0 };
		this.clearDeviceToken(this.opts.deviceIdentity.deviceId, observation, () => !this.opts.token && !this.opts.password);
	}
	clearDeviceToken(deviceId, observation, canClear = () => true) {
		const failed = (error) => {
			this.logDebug(`failed clearing stale device-auth token for device ${deviceId}: ${String(error)}`);
		};
		const cleared = () => this.logDebug(`cleared stale device-auth token for device ${deviceId}`);
		try {
			const result = this.deviceAuth.clear({
				deviceId,
				role: this.opts.role ?? "operator",
				env: this.opts.env,
				assertCurrent: () => {
					if (this.stopped) throw new Error("gateway client stopped");
				}
			}, observation, canClear);
			return result instanceof Promise ? result.then(cleared, failed) : cleared();
		} catch (error) {
			failed(error);
		}
	}
	notifyConnectError(error) {
		if (!this.opts.onConnectError) return false;
		try {
			this.opts.onConnectError(error);
			return true;
		} catch (err) {
			this.logDebug(`gateway client connect error handler error: ${formatGatewayClientErrorForLog(err)}`);
			return false;
		}
	}
	notifyReconnectPaused(info) {
		try {
			this.opts.onReconnectPaused?.(info);
		} catch (err) {
			this.logDebug(`gateway client reconnect paused handler error: ${formatGatewayClientErrorForLog(err)}`);
		}
	}
	isUnsupportedConnectAuthField(error, field) {
		if (!(error instanceof GatewayClientRequestError) || error.gatewayCode !== "INVALID_REQUEST") return false;
		const message = normalizeGatewayErrorText(error.message);
		return message.includes("invalid connect params") && message.includes(field);
	}
	isTrustedDeviceRetryEndpoint() {
		const rawUrl = this.opts.url ?? "ws://127.0.0.1:18789";
		try {
			const parsed = new URL(rawUrl);
			const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
			if (isGatewayLoopbackHost(parsed.hostname)) return true;
			return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
		} catch {
			return false;
		}
	}
	selectConnectAuth(storedAuth) {
		return selectGatewayConnectAuth({
			token: this.opts.token,
			bootstrapToken: this.opts.bootstrapToken,
			preferBootstrapToken: this.opts.preferBootstrapToken,
			deviceToken: this.opts.deviceToken,
			password: this.opts.password,
			approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
			agentRuntimeIdentityToken: this.opts.agentRuntimeIdentityToken,
			storedToken: storedAuth?.token,
			storedScopes: storedAuth?.scopes,
			pendingDeviceTokenRetry: this.pendingDeviceTokenRetry,
			trustedDeviceTokenRetry: this.isTrustedDeviceRetryEndpoint()
		});
	}
	startTickWatch() {
		if (this.tickTimer) clearInterval(this.tickTimer);
		const rawMinInterval = this.opts.tickWatchMinIntervalMs;
		const minInterval = typeof rawMinInterval === "number" && Number.isFinite(rawMinInterval) ? Math.max(1, Math.min(3e4, rawMinInterval)) : 1e3;
		const interval = resolveSafeTimeoutDelayMs(Math.max(this.tickIntervalMs, minInterval));
		this.tickTimer = setInterval(() => {
			if (this.stopped) return;
			if (!this.lastTick) return;
			if (this.protocol.hasPendingRequests && !this.protocol.hasUnboundedPendingRequests) return;
			const gap = Date.now() - this.lastTick;
			const rawTimeoutMs = this.opts.tickWatchTimeoutMs;
			if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
		}, interval);
	}
	async request(method, params, opts) {
		const expectFinal = opts?.expectFinal === true;
		const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : expectFinal ? null : this.requestTimeoutMs;
		return this.protocol.request(method, params, {
			expectFinal,
			timeoutMs,
			signal: opts?.signal,
			onSent: opts?.onSent,
			onAccepted: opts?.onAccepted
		});
	}
};
function createGatewayRequestAbortError$1(method) {
	const err = /* @__PURE__ */ new Error(`gateway request aborted for ${method}`);
	err.name = "AbortError";
	return err;
}
//#endregion
//#region src/infra/net/proxy/proxy-lifecycle.ts
const PROXY_ENV_KEYS = [
	"http_proxy",
	"https_proxy",
	"HTTP_PROXY",
	"HTTPS_PROXY"
];
const NO_PROXY_ENV_KEYS = ["no_proxy", "NO_PROXY"];
const LOOPBACK_NO_PROXY = "127.0.0.1,localhost,localhost.,::1,[::1],127.0.0.0/8";
const managedLoopbackBypassPolicy = ({ url }) => getActiveManagedProxyLoopbackMode() === "gateway-only" && isLoopbackProxyUrl(url);
const PROXY_ACTIVE_KEYS = [
	"OPENCLAW_PROXY_ACTIVE",
	"OPENCLAW_PROXY_LOOPBACK_MODE",
	"OPENCLAW_PROXY_CA_FILE"
];
[
	...PROXY_ENV_KEYS,
	...NO_PROXY_ENV_KEYS,
	...PROXY_ACTIVE_KEYS
];
const MANAGED_PROXY_UNDICI_OPTIONS = Object.freeze({ allowH2: false });
function applyProxyEnv(proxyUrl, loopbackMode, proxyCaFile) {
	for (const key of PROXY_ENV_KEYS) process.env[key] = proxyUrl;
	process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
	process.env["OPENCLAW_PROXY_LOOPBACK_MODE"] = loopbackMode;
	if (proxyCaFile) process.env["OPENCLAW_PROXY_CA_FILE"] = proxyCaFile;
	else delete process.env["OPENCLAW_PROXY_CA_FILE"];
	for (const key of NO_PROXY_ENV_KEYS) process.env[key] = loopbackMode === "gateway-only" ? LOOPBACK_NO_PROXY : "";
}
/** Reinstalls Proxyline routing in child processes that inherited active proxy env. */
function ensureInheritedManagedProxyRoutingActive() {
	if (process.env["OPENCLAW_PROXY_ACTIVE"] !== "1") return;
	const proxyUrl = process.env["HTTP_PROXY"];
	if (!proxyUrl || !isHttpUrl(proxyUrl)) return;
	const proxyCaFile = resolveManagedProxyCaFileForUrl({
		proxyUrl,
		caFileOverride: process.env["OPENCLAW_PROXY_CA_FILE"]
	});
	const proxyTls = loadManagedProxyTlsOptionsSync(proxyCaFile);
	applyProxyEnv(proxyUrl, getActiveManagedProxyLoopbackMode() ?? "gateway-only", proxyCaFile);
	installGlobalProxy({
		mode: "managed",
		proxyUrl,
		...proxyTls ? { proxyTls } : {},
		ifActive: "reuse-compatible",
		bypassPolicy: managedLoopbackBypassPolicy,
		undici: MANAGED_PROXY_UNDICI_OPTIONS
	});
	forceResetGlobalDispatcher({ preserveProxylineManaged: true });
}
function isLoopbackProxyUrl(value) {
	try {
		const url = new URL(value);
		const hostname = url.hostname.toLowerCase().replace(/\.+$/, "");
		return (isHttpUrl(url) || isWebSocketUrl(url)) && (hostname === "localhost" || isLoopbackIpAddress(hostname));
	} catch {
		return false;
	}
}
function assertManagedProxyAllowsLoopback(url, surface) {
	if (isLoopbackProxyUrl(url) && getActiveManagedProxyLoopbackMode() === "block") throw new Error(`proxy: ${surface} connections are blocked by proxy.loopbackMode; run openclaw config set proxy.loopbackMode gateway-only to allow local runtime traffic.`);
}
function registerManagedProxyGatewayLoopbackBypass(url) {
	assertManagedProxyAllowsLoopback(url, "Gateway loopback control-plane");
}
//#endregion
//#region src/shared/gateway-client-platform.ts
function resolveGatewayClientPlatformIdentity(platform) {
	switch (platform) {
		case "darwin": return {
			platform: "macos",
			deviceFamily: "Mac"
		};
		case "win32": return {
			platform: "windows",
			deviceFamily: "Windows"
		};
		case "linux": return {
			platform,
			deviceFamily: "Linux"
		};
		default: return { platform };
	}
}
//#endregion
//#region src/gateway/client.ts
function createOpenClawGatewayClientHostDeps(overrides, deviceAuthScope, suppressStoredDeviceAuth = false, sharedStateMode, preparedDeviceAuth) {
	const readOnly = sharedStateMode === "read-only";
	const rotationFence = preparedDeviceAuth ? { expectedToken: preparedDeviceAuth.token } : void 0;
	let tokenObservation;
	const observe = (params) => {
		const deviceId = params.deviceId;
		const role = normalizeDeviceAuthRole(params.role);
		return (snapshot) => {
			tokenObservation = {
				deviceId,
				role,
				expectedToken: snapshot.expectedToken
			};
		};
	};
	const observedFor = (params) => tokenObservation?.deviceId === params.deviceId && tokenObservation.role === normalizeDeviceAuthRole(params.role) ? tokenObservation : void 0;
	const writeFence = (params) => {
		if (rotationFence) return rotationFence;
		const observed = observedFor(params);
		return observed ? { expectedToken: observed.expectedToken } : void 0;
	};
	const clearFence = (params) => {
		const expectedToken = rotationFence?.expectedToken ?? params.expectedToken;
		const raw = observedFor(params)?.expectedToken;
		return {
			...rotationFence,
			...typeof raw === "string" && raw !== expectedToken && raw.trim() === expectedToken ? { observedToken: raw } : {}
		};
	};
	const deviceAuthDeps = deviceAuthScope ? {
		loadDeviceAuthToken: async (params) => {
			if (readOnly) return suppressStoredDeviceAuth ? null : loadOriginDeviceTokenReadOnly({
				...params,
				gatewayScope: deviceAuthScope
			});
			const load = await loadOriginDeviceToken({
				...params,
				gatewayScope: deviceAuthScope,
				onSnapshot: observe(params)
			});
			return suppressStoredDeviceAuth ? null : load;
		},
		storeDeviceAuthToken: readOnly ? () => {} : (params) => storeOriginDeviceToken({
			...params,
			gatewayScope: deviceAuthScope,
			...writeFence(params)
		}),
		clearDeviceAuthToken: readOnly ? () => {} : (params) => clearOriginDeviceToken({
			...params,
			gatewayScope: deviceAuthScope,
			...clearFence(params)
		})
	} : readOnly ? {
		loadDeviceAuthToken: suppressStoredDeviceAuth ? () => null : loadDeviceAuthTokenReadOnly,
		storeDeviceAuthToken: () => {},
		clearDeviceAuthToken: () => {}
	} : {
		loadDeviceAuthToken: (params) => loadDeviceAuthToken({
			...params,
			onSnapshot: observe(params)
		}),
		storeDeviceAuthToken: (params) => storeDeviceAuthToken({
			...params,
			...writeFence(params)
		}),
		clearDeviceAuthToken: (params) => clearDeviceAuthToken({
			...params,
			...clearFence(params)
		})
	};
	const preparedDeviceAuthDeps = preparedDeviceAuth ? {
		...deviceAuthDeps,
		loadDeviceAuthToken: () => preparedDeviceAuth
	} : deviceAuthDeps;
	return {
		loadOrCreateDeviceIdentity,
		signDevicePayload,
		publicKeyRawBase64UrlFromPem,
		...preparedDeviceAuthDeps,
		beforeConnect: ensureInheritedManagedProxyRoutingActive,
		registerGatewayLoopbackBypass: registerManagedProxyGatewayLoopbackBypass,
		logDebug,
		logError,
		redactForLog: redactToolPayloadText,
		...overrides,
		...readOnly ? {
			loadOrCreateDeviceIdentity: () => loadDeviceIdentityIfPresent() ?? void 0,
			...preparedDeviceAuthDeps
		} : {}
	};
}
function shouldSuppressStoredDeviceAuth(opts) {
	return Boolean(opts.deviceAuthScope && (opts.token?.trim() || opts.password?.trim())) || !opts.deviceAuthScope && opts.sharedStateMode === "read-only" && Boolean(opts.password?.trim()) && !opts.token?.trim() && !opts.bootstrapToken?.trim() && !opts.deviceToken?.trim() && !opts.approvalRuntimeToken?.trim() && !opts.agentRuntimeIdentityToken?.trim() && !opts.preferBootstrapToken;
}
/** Prepare storage before the one-shot RPC budget; connection loads still observe current rows. */
async function prepareGatewayClientDeviceAuth(opts, signal) {
	signal?.throwIfAborted();
	if (opts.deviceIdentity === null || opts.preparedDeviceAuth || opts.sharedStateMode === "read-only" && shouldSuppressStoredDeviceAuth(opts)) return;
	try {
		if (Object.keys(opts.edgeAuthHeaders ?? {}).length && new URL(opts.url).protocol !== "wss:") return;
		resolveGatewayWebSocketTransport({
			url: opts.url,
			tlsFingerprint: opts.tlsFingerprint,
			env: opts.env,
			options: {}
		});
	} catch {
		return;
	}
	try {
		await prepareDeviceAuthStore({
			env: opts.env,
			signal,
			readOnly: opts.sharedStateMode === "read-only"
		});
	} catch (error) {
		throw markGatewayConnectAssemblyError(error instanceof Error ? error : new Error(String(error)));
	}
}
var GatewayClient = class {
	#client;
	#options;
	#tunnel;
	#starting;
	#stopping;
	#lifetime = new AbortController();
	constructor(opts) {
		if (opts.sshTunnel && !opts.deviceAuthScope) throw new Error("Gateway SSH route requires its own device-auth scope");
		if (opts.preparedSshTunnel && !opts.sshTunnel) throw new Error("Prepared Gateway SSH tunnel requires its selected route");
		this.#options = opts;
		this.#tunnel = opts.preparedSshTunnel;
		if (!opts.sshTunnel) this.#client = this.createClient(opts.url);
	}
	createClient(url, tlsServerName) {
		const opts = this.#options;
		const { deviceAuthScope, preparedDeviceAuth, sharedStateMode, sshTunnel, preparedSshTunnel: _preparedSshTunnel, ...baseOptions } = opts;
		const runtimeIdentity = resolveGatewayClientPlatformIdentity(process.platform);
		const suppressStoredAuth = shouldSuppressStoredDeviceAuth(opts);
		for (const value of Object.values(baseOptions.edgeAuthHeaders ?? {})) registerSecretValueForRedaction(value);
		const hostDeps = createOpenClawGatewayClientHostDeps(baseOptions.hostDeps, deviceAuthScope, suppressStoredAuth, sharedStateMode, preparedDeviceAuth);
		if (sshTunnel) {
			const beforeConnect = hostDeps.beforeConnect;
			hostDeps.beforeConnect = () => {
				beforeConnect?.();
				if (this.#lifetime.signal.aborted || !this.#tunnel?.isActive()) throw new Error("Gateway SSH tunnel is no longer active");
			};
		}
		return new GatewayClient$1({
			...baseOptions,
			url,
			...tlsServerName ? { tlsServerName } : {},
			clientVersion: baseOptions.clientVersion ?? VERSION,
			platform: baseOptions.platform ?? runtimeIdentity.platform,
			deviceFamily: baseOptions.deviceFamily ?? (baseOptions.platform === void 0 ? runtimeIdentity.deviceFamily : void 0),
			hostDeps
		});
	}
	start() {
		if (!this.#options.sshTunnel) {
			this.#client?.start();
			return;
		}
		if (this.#starting || this.#lifetime.signal.aborted) return;
		this.#starting = this.startSsh().catch((error) => {
			if (this.#lifetime.signal.aborted) return;
			this.stop();
			const failure = error instanceof Error ? error : new Error(String(error));
			this.notifySshClosed(failure);
		});
	}
	async startSsh() {
		const route = this.#options.sshTunnel;
		if (!route) return;
		const url = new URL(this.#options.url ?? "");
		if (!this.#tunnel) {
			const { startSshPortForward } = await import("./ssh-tunnel-dWwU1iun.mjs");
			this.#lifetime.signal.throwIfAborted();
			this.#tunnel = await startSshPortForward({
				...route,
				localPortPreferred: Number(url.port) || (url.protocol === "wss:" ? 443 : 80),
				timeoutMs: this.#options.preauthHandshakeTimeoutMs ?? 1e4,
				signal: this.#lifetime.signal
			});
		}
		if (this.#lifetime.signal.aborted) {
			await this.#tunnel.stop();
			return;
		}
		if (!this.#tunnel.isActive()) throw new Error("Gateway SSH tunnel closed before connection");
		this.#tunnel.closed.then(() => {
			if (!this.#lifetime.signal.aborted) {
				this.stop();
				this.notifySshClosed();
			}
		});
		const tlsServerName = url.protocol === "wss:" ? parseHostForAddressChecks(url.hostname)?.unbracketedHost : void 0;
		url.hostname = "127.0.0.1";
		url.port = String(this.#tunnel.localPort);
		this.#client = this.createClient(url.href, tlsServerName);
		this.#client.start();
	}
	notifySshClosed(error) {
		if (error) try {
			this.#options.onConnectError?.(error);
		} catch {
			logError("Gateway SSH connect-error callback failed");
		}
		try {
			this.#options.onClose?.(1006, error?.message ?? "Gateway SSH tunnel closed");
		} catch {
			logError("Gateway SSH close callback failed");
		}
	}
	stop() {
		if (!this.#options.sshTunnel) {
			this.#client?.stop();
			return;
		}
		this.stopAndWait().catch((error) => logError(String(error)));
	}
	stopAndWait(opts) {
		if (!this.#options.sshTunnel) return this.#client?.stopAndWait(opts) ?? Promise.resolve();
		this.#lifetime.abort();
		this.#client?.stop();
		return this.#stopping ??= (async () => {
			try {
				await this.#starting;
				await this.#client?.stopAndWait(opts);
			} finally {
				await this.#tunnel?.stop();
			}
		})();
	}
	request(method, params, opts) {
		return this.#client ? this.#client.request(method, params, opts) : Promise.reject(/* @__PURE__ */ new Error("Gateway SSH connection has not started"));
	}
	/** Current transport state, including CLOSING before the close callback fires.
	* This is not authentication or readiness evidence on its own. */
	get connected() {
		return this.#client?.connected ?? false;
	}
	getConnectionMetadata() {
		return this.#client?.getConnectionMetadata() ?? {
			clientName: this.#options.clientName,
			hasDeviceIdentity: Boolean(this.#options.deviceIdentity),
			mode: this.#options.mode,
			preauthHandshakeTimeoutMs: this.#options.preauthHandshakeTimeoutMs
		};
	}
	updateNodeManifest(manifest) {
		if (this.#client) this.#client.updateNodeManifest(manifest);
		else this.#options = {
			...this.#options,
			...manifest
		};
	}
};
//#endregion
//#region src/shared/gateway-edge-auth-headers.ts
const HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/u;
const TRANSPORT_OWNED_HEADERS = /* @__PURE__ */ new Set([
	"host",
	"connection",
	"upgrade",
	"content-length",
	"sec-websocket-key",
	"sec-websocket-version",
	"sec-websocket-protocol",
	"sec-websocket-extensions"
]);
function findEdgeAuthIssue(headers) {
	const entries = Object.entries(headers);
	if (entries.length === 0) return { message: "invalid gateway.remote.edgeAuth: header map must not be empty" };
	const originalNames = /* @__PURE__ */ new Map();
	for (const [headerName] of entries) {
		if (!HTTP_HEADER_NAME_PATTERN.test(headerName)) return {
			headerName,
			message: `invalid gateway.remote.edgeAuth header name: ${JSON.stringify(headerName)}`
		};
		const normalizedName = headerName.toLowerCase();
		if (TRANSPORT_OWNED_HEADERS.has(normalizedName)) return {
			headerName,
			message: `gateway.remote.edgeAuth cannot set transport-owned header "${headerName}"`
		};
		const originalName = originalNames.get(normalizedName);
		if (originalName) return {
			headerName,
			message: `gateway.remote.edgeAuth header names "${originalName}" and "${headerName}" differ only by case`
		};
		originalNames.set(normalizedName, headerName);
	}
	return null;
}
//#endregion
//#region src/gateway/edge-auth.ts
function normalizeEdgeAuthSecretInput(value, headerName) {
	const ref = coerceSecretRef(value);
	if (ref) return ref;
	const literal = normalizeSecretInputString(value);
	if (literal) return literal;
	throw new Error(`invalid gateway.remote.edgeAuth header "${headerName}": expected a non-empty SecretInput`);
}
function normalizeEdgeAuthHeadersConfig(value) {
	if (value === void 0 || value === null) return;
	if (!isRecord(value)) throw new Error("invalid gateway.remote.edgeAuth: expected a header map");
	const shapeIssue = findEdgeAuthIssue(value);
	if (shapeIssue) throw new Error(shapeIssue.message);
	const normalizedEntries = Object.entries(value).map(([headerName, input]) => {
		return [headerName, normalizeEdgeAuthSecretInput(input, headerName)];
	});
	return Object.fromEntries(normalizedEntries);
}
async function resolveEdgeAuthHeaders(params) {
	if (!params.value) return;
	let protocol;
	try {
		protocol = new URL(params.targetUrl).protocol;
	} catch {
		throw new Error("gateway.remote.edgeAuth requires a wss:// connection target");
	}
	if (protocol !== "wss:") throw new Error("gateway.remote.edgeAuth requires a wss:// connection target");
	const resolvedEntries = await Promise.all(Object.entries(params.value).map(async ([headerName, input]) => {
		const value = await materializeSecretInput({
			config: params.config,
			value: input,
			env: params.env
		});
		if (!value) throw new Error(`gateway.remote.edgeAuth header "${headerName}" resolved empty`);
		registerSecretValueForRedaction(value);
		return [headerName, value];
	}));
	return Object.freeze(Object.fromEntries(resolvedEntries));
}
function gatewayEdgeAuthValueForTarget(params) {
	const remote = params.config.gateway?.remote;
	if (!remote?.url || gatewayOriginScope(params.targetUrl) !== gatewayOriginScope(remote.url)) return;
	return remote.edgeAuth;
}
//#endregion
//#region src/gateway/explicit-connection-policy.ts
function hasExplicitGatewayConnectionAuth(auth) {
	return Boolean(trimToUndefined(auth?.token) || trimToUndefined(auth?.password));
}
function targetMayRequireConfiguredEdgeAuth(url) {
	try {
		const protocol = new URL(url).protocol;
		return protocol === "wss:" || protocol === "https:";
	} catch {
		return false;
	}
}
/**
* True when the caller fully addressed the Gateway with flags. Config is then
* consulted only for gateway.remote.edgeAuth, so a broken config must not block
* the connection: this is the historical recovery path for an invalid config.
*/
function isExplicitGatewayConnection(params) {
	return !params.config && Boolean(trimToUndefined(params.urlOverride)) && hasExplicitGatewayConnectionAuth(params.explicitAuth);
}
/** Returns true when url/auth flags are sufficient and loading OpenClaw config is unnecessary. */
function canSkipGatewayConfigLoad(params) {
	const urlOverride = trimToUndefined(params.urlOverride);
	if (!urlOverride || params.config || !hasExplicitGatewayConnectionAuth(params.explicitAuth)) return false;
	return !targetMayRequireConfiguredEdgeAuth(urlOverride);
}
//#endregion
//#region src/infra/node-commands.ts
const NODE_FS_LIST_DIR_COMMAND = "fs.listDir";
const NODE_TERMINAL_UPLOAD_COMMAND = "terminal.upload";
const NODE_BROWSER_PROXY_COMMANDS = ["browser.proxy", "browser.proxy.upload.v1"];
const NODE_ADMIN_ONLY_INVOKE_COMMANDS = [
	...NODE_BROWSER_PROXY_COMMANDS,
	NODE_FS_LIST_DIR_COMMAND,
	NODE_TERMINAL_UPLOAD_COMMAND
];
const NODE_ADMIN_ONLY_INVOKE_COMMAND_SET = new Set(NODE_ADMIN_ONLY_INVOKE_COMMANDS);
/** Returns true when direct node invocation crosses an admin-only host boundary. */
function isAdminOnlyNodeInvokeCommand(command) {
	return typeof command === "string" && NODE_ADMIN_ONLY_INVOKE_COMMAND_SET.has(command);
}
/** Returns true for every versioned Browser node proxy command. */
function isBrowserProxyNodeInvokeCommand(command) {
	return typeof command === "string" && NODE_BROWSER_PROXY_COMMANDS.includes(command);
}
//#endregion
//#region src/plugins/runtime-state-key.ts
/** Process-global symbol shared by every plugin registry runtime projection. */
const PLUGIN_REGISTRY_STATE = Symbol.for("openclaw.pluginRegistryState");
//#endregion
//#region src/plugins/runtime-state.ts
function getPluginRegistryState() {
	return globalThis[PLUGIN_REGISTRY_STATE];
}
//#endregion
//#region src/plugins/runtime/execution-frame.ts
const PluginRuntimeExecutionFrame = resolveGlobalSingleton(Symbol.for("openclaw.pluginRuntimeExecutionFrame"), () => class RuntimeFrame extends InvocationFrame {
	constructor(scopes, gatewayScope, generationRegistry) {
		super(scopes);
		this.gatewayScope = gatewayScope;
		this.generationRegistry = generationRegistry;
	}
	withScopes(scopes) {
		return new RuntimeFrame(scopes, this.gatewayScope, this.generationRegistry);
	}
});
function getPluginRuntimeExecutionFrame(frame = getPluginExecutionFrame()) {
	return frame instanceof PluginRuntimeExecutionFrame ? frame : void 0;
}
//#endregion
//#region src/plugins/runtime/gateway-request-scope.ts
function getPluginRuntimeGatewayRequestScope() {
	return getPluginRuntimeExecutionFrame()?.gatewayScope;
}
/** Reads registration/request/active registry precedence without initializing a cold runtime. */
function getPluginRegistryForContext() {
	const state = getPluginRegistryState();
	return state?.registrationContext?.registry ?? getPluginRuntimeGatewayRequestScope()?.pluginRegistry ?? state?.activeRegistry ?? null;
}
//#endregion
//#region src/shared/gateway-method-policy.ts
const RESERVED_ADMIN_GATEWAY_METHOD_PREFIXES = [
	"exec.approvals.",
	"config.",
	"wizard.",
	"update."
];
const RESERVED_ADMIN_GATEWAY_METHOD_SCOPE = "operator.admin";
/** Return whether a gateway method is reserved for operator admin calls. */
function isReservedAdminGatewayMethod(method) {
	return RESERVED_ADMIN_GATEWAY_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix));
}
/** Resolve the mandatory scope for reserved gateway methods. */
function resolveReservedGatewayMethodScope(method) {
	if (!isReservedAdminGatewayMethod(method)) return;
	return RESERVED_ADMIN_GATEWAY_METHOD_SCOPE;
}
//#endregion
//#region src/shared/session-method-scopes-base.ts
const SESSIONS_PATCH_WRITE_SCOPE_MUTATIONS = /* @__PURE__ */ new Set([
	"label",
	"autoLabel",
	"icon",
	"color",
	"category",
	"boardFace",
	"boardPresentation",
	"pinned",
	"archived",
	"unread",
	"model",
	"agentRuntime",
	"thinkingLevel",
	"fastMode",
	"permissionMode"
]);
const SESSIONS_PATCH_WRITE_SCOPE_ENVELOPE_FIELDS = /* @__PURE__ */ new Set([
	"key",
	"agentId",
	"expectedSessionId",
	"expectedLifecycleRevision",
	"expectedPermissionMode",
	"expectedMarkedUnreadAt"
]);
const SESSIONS_DELETE_WRITE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
	"key",
	"agentId",
	"deleteTranscript",
	"expectedSessionId",
	"archivedOnly"
]);
function resolveSessionsPatchRequiredScope(params) {
	if (!isRecord(params)) return "operator.write";
	if (params.permissionMode === "full" || Object.hasOwn(params, "sandboxMode")) return "operator.admin";
	return Object.keys(params).every((key) => SESSIONS_PATCH_WRITE_SCOPE_ENVELOPE_FIELDS.has(key) || SESSIONS_PATCH_WRITE_SCOPE_MUTATIONS.has(key)) ? "operator.write" : "operator.admin";
}
function resolveSessionsPatchManyRequiredScope(params) {
	if (!isRecord(params) || !isRecord(params.patch)) return "operator.write";
	if (params.patch.permissionMode === "full" || Object.hasOwn(params.patch, "sandboxMode")) return "operator.admin";
	return Object.keys(params.patch).every((key) => SESSIONS_PATCH_WRITE_SCOPE_MUTATIONS.has(key)) ? "operator.write" : "operator.admin";
}
function resolveSessionsCreateRequiredScope(params) {
	if (!isRecord(params)) return "operator.write";
	if (params.incognito === true || typeof params.key === "string" && isIncognitoSessionKey(params.key) || typeof params.parentSessionKey === "string" && isIncognitoSessionKey(params.parentSessionKey) || Object.hasOwn(params, "execNode") || Object.hasOwn(params, "toolOverrides") || params.permissionMode === "full") return "operator.admin";
	return "operator.write";
}
function resolveSessionsDeleteRequiredScope(params) {
	if (!isRecord(params) || params.archivedOnly !== true) return "operator.admin";
	return Object.keys(params).every((key) => SESSIONS_DELETE_WRITE_SCOPE_FIELDS.has(key)) ? "operator.write" : "operator.admin";
}
/** Browser-safe session mutation policy for methods without protocol validation. */
function resolveBaseSessionMutationRequiredScope(method, params) {
	if (method === "sessions.recover") return "operator.write";
	if (method === "sessions.create") return resolveSessionsCreateRequiredScope(params);
	if (method === "sessions.patch") return resolveSessionsPatchRequiredScope(params);
	if (method === "sessions.patchMany") return resolveSessionsPatchManyRequiredScope(params);
	if (method === "sessions.delete") return resolveSessionsDeleteRequiredScope(params);
}
//#endregion
//#region packages/normalization-core/src/json-schema.ts
/** Remove complete false-schema child groups immediately preceding their aggregate. */
function normalizeTypeBoxValidationErrors(errors) {
	const normalized = [];
	let consecutiveBooleanErrors = 0;
	for (const error of errors) {
		if (error.keyword === "boolean") {
			normalized.push(error);
			consecutiveBooleanErrors += 1;
			continue;
		}
		const properties = error.params?.additionalProperties;
		if (error.keyword === "additionalProperties" && typeof error.schemaPath === "string" && typeof error.instancePath === "string" && Array.isArray(properties) && properties.length > 0 && properties.length <= consecutiveBooleanErrors) {
			if (normalized.slice(-properties.length).every((child, index) => {
				const property = properties[index];
				return typeof property === "string" && child.schemaPath === `${error.schemaPath}/additionalProperties` && child.instancePath === `${error.instancePath}/${property.replace(/~/g, "~0").replace(/\//g, "~1")}`;
			})) normalized.length -= properties.length;
		}
		normalized.push(error);
		consecutiveBooleanErrors = 0;
	}
	return normalized;
}
//#endregion
//#region packages/gateway-protocol/src/protocol-validator.ts
/* @__NO_SIDE_EFFECTS__ */
function lazyCompile(schema, precheck) {
	let compiled;
	let errors = null;
	const validate = ((data) => {
		const precheckError = precheck?.(data);
		if (precheckError) {
			errors = [precheckError];
			return false;
		}
		const current = compiled ??= Compile(schema);
		const valid = current.Check(data);
		errors = valid ? null : normalizeTypeBoxValidationErrors([...current.Errors(data)]);
		return valid;
	});
	Object.defineProperties(validate, {
		errors: {
			configurable: true,
			enumerable: true,
			get: () => errors,
			set: (nextErrors) => {
				errors = nextErrors ?? null;
			}
		},
		schema: {
			configurable: true,
			enumerable: true,
			get: () => schema
		}
	});
	return validate;
}
//#endregion
//#region packages/gateway-protocol/src/schema/closed-object.ts
const identityKey = "~openclawClosedObjectIdentity";
function closedObject(properties) {
	const schema = Type.Object(properties, { additionalProperties: false });
	Object.defineProperty(schema, identityKey, { value: Symbol("closedObject") });
	return schema;
}
//#endregion
//#region packages/gateway-protocol/src/secret-ref-contract.ts
/** Canonical id for file secret providers that expose exactly one value. */
const SINGLE_VALUE_FILE_REF_ID = "value";
/** Shared alias grammar for env/file/exec/store secret provider names. */
const SECRET_PROVIDER_ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
/** JSON-schema fragment that rejects invalid JSON-pointer escape sequences. */
const FILE_SECRET_REF_ID_INVALID_ESCAPE_JSON_SCHEMA_PATTERN = "~(?:[^01]|$)";
/** JSON-schema pattern for exec secret ref ids, excluding dot-path traversal. */
const EXEC_SECRET_REF_ID_JSON_SCHEMA_PATTERN = "^(?!.*(?:^|/)\\.{1,2}(?:/|$))[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$";
//#endregion
//#region packages/gateway-protocol/src/schema/primitives.ts
/**
* Shared schema primitives reused by gateway protocol request/result schemas.
*
* Keep these schemas small and transport-oriented; feature-specific validation
* belongs in the owning schema module or runtime handler.
*/
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
const INPUT_PROVENANCE_KIND_VALUES = [
	"external_user",
	"inter_session",
	"internal_system"
];
const SESSION_LABEL_MAX_LENGTH = 512;
/** Non-empty string primitive for protocol fields that reject blank values. */
const NonEmptyString = Type.String({ minLength: 1 });
Type.String({
	minLength: 64,
	maxLength: 64,
	pattern: "^[a-fA-F0-9]{64}$"
});
Type.String({
	minLength: 1,
	maxLength: 128
});
Type.String({
	minLength: 1,
	maxLength: 512
});
Type.String({
	minLength: 1,
	maxLength: SESSION_LABEL_MAX_LENGTH
});
closedObject({
	kind: Type.String({ enum: [...INPUT_PROVENANCE_KIND_VALUES] }),
	originSessionId: Type.Optional(Type.String()),
	sourceSessionKey: Type.Optional(Type.String()),
	sourceChannel: Type.Optional(Type.String()),
	sourceTool: Type.Optional(Type.String()),
	sourceRole: Type.Optional(Type.Literal("subagent")),
	sourcePromptPrefix: Type.Optional(Type.String()),
	jobId: Type.Optional(Type.String()),
	runId: Type.Optional(Type.String())
});
Type.Enum(GATEWAY_CLIENT_IDS);
Type.Enum(GATEWAY_CLIENT_MODES);
const SecretProviderAliasString = Type.String({ pattern: SECRET_PROVIDER_ALIAS_PATTERN.source });
const EnvSecretRefSchema = closedObject({
	source: Type.Literal("env"),
	provider: SecretProviderAliasString,
	id: Type.String({ pattern: ENV_SECRET_REF_ID_RE.source })
});
const FileSecretRefIdSchema = Type.Unsafe({
	type: "string",
	anyOf: [{ const: SINGLE_VALUE_FILE_REF_ID }, { allOf: [{ pattern: "^/" }, { not: { pattern: FILE_SECRET_REF_ID_INVALID_ESCAPE_JSON_SCHEMA_PATTERN } }] }]
});
const FileSecretRefSchema = closedObject({
	source: Type.Literal("file"),
	provider: SecretProviderAliasString,
	id: FileSecretRefIdSchema
});
const ExecSecretRefSchema = closedObject({
	source: Type.Literal("exec"),
	provider: SecretProviderAliasString,
	id: Type.String({ pattern: EXEC_SECRET_REF_ID_JSON_SCHEMA_PATTERN })
});
const StoreSecretRefSchema = closedObject({
	source: Type.Literal("store"),
	provider: SecretProviderAliasString,
	id: Type.String({ pattern: ENV_SECRET_REF_ID_RE.source })
});
/** Structured secret reference accepted by config and channel protocol payloads. */
const SecretRefSchema = Type.Union([
	EnvSecretRefSchema,
	FileSecretRefSchema,
	ExecSecretRefSchema,
	StoreSecretRefSchema
]);
Type.Union([Type.String(), SecretRefSchema]);
const WORKER_PROTOCOL_MAX_PAYLOAD_BYTES = 65536;
const WorkerIdentifierSchema = Type.String({
	minLength: 1,
	maxLength: 256,
	pattern: "^\\S(?:.*\\S)?$"
});
const WorkerFrameIdSchema = Type.String({
	minLength: 1,
	maxLength: 128
});
const WorkerAdmissionFailureReasonSchema = Type.Union([
	Type.Literal("invalid-credential"),
	Type.Literal("credential-expired"),
	Type.Literal("environment-mismatch"),
	Type.Literal("environment-unavailable"),
	Type.Literal("bundle-mismatch"),
	Type.Literal("version-mismatch"),
	Type.Literal("session-mismatch"),
	Type.Literal("placement-mismatch"),
	Type.Literal("owner-epoch-mismatch"),
	Type.Literal("rpc-set-mismatch"),
	Type.Literal("protocol-features-mismatch")
]);
const WorkerProtocolCloseReasonSchema = Type.Union([
	WorkerAdmissionFailureReasonSchema,
	Type.Literal("admission-rejected"),
	Type.Literal("invalid-handshake"),
	Type.Literal("protocol-mismatch"),
	Type.Literal("gateway-unavailable"),
	Type.Literal("invalid-frame"),
	Type.Literal("slow-consumer"),
	Type.Literal("method-not-allowed"),
	Type.Literal("invalid-heartbeat"),
	Type.Literal("credential-replaced"),
	Type.Literal("gateway-shutdown")
]);
const WorkerErrorCodeSchema = Type.Union([Type.Literal("INVALID_REQUEST"), Type.Literal("UNAVAILABLE")]);
const WorkerErrorDetailsSchema = closedObject({ reason: WorkerProtocolCloseReasonSchema });
const WorkerErrorShapeSchema = closedObject({
	code: WorkerErrorCodeSchema,
	message: Type.String({
		minLength: 1,
		maxLength: 256
	}),
	details: WorkerErrorDetailsSchema,
	retryable: Type.Optional(Type.Boolean()),
	retryAfterMs: Type.Optional(Type.Integer({ minimum: 0 }))
});
closedObject({
	type: Type.Literal("res"),
	id: WorkerFrameIdSchema,
	ok: Type.Literal(false),
	error: WorkerErrorShapeSchema
});
closedObject({
	input: Type.Number({ minimum: 0 }),
	output: Type.Number({ minimum: 0 }),
	cacheRead: Type.Number({ minimum: 0 }),
	cacheWrite: Type.Number({ minimum: 0 }),
	contextUsage: Type.Optional(Type.Union([closedObject({
		state: Type.Literal("available"),
		promptTokens: Type.Number({ minimum: 0 }),
		totalTokens: Type.Number({ minimum: 0 })
	}), closedObject({ state: Type.Literal("unavailable") })])),
	totalTokens: Type.Number({ minimum: 0 }),
	cost: closedObject({
		input: Type.Number({ minimum: 0 }),
		output: Type.Number({ minimum: 0 }),
		cacheRead: Type.Number({ minimum: 0 }),
		cacheWrite: Type.Number({ minimum: 0 }),
		total: Type.Number({ minimum: 0 }),
		totalOrigin: Type.Optional(Type.Literal("provider-billed"))
	})
});
closedObject({
	type: WorkerIdentifierSchema,
	timestamp: Type.Integer({ minimum: 0 }),
	error: Type.Optional(closedObject({
		name: Type.Optional(Type.String({ maxLength: 256 })),
		message: Type.String({ maxLength: WORKER_PROTOCOL_MAX_PAYLOAD_BYTES }),
		stack: Type.Optional(Type.String({ maxLength: WORKER_PROTOCOL_MAX_PAYLOAD_BYTES })),
		code: Type.Optional(Type.Union([Type.String({ maxLength: 256 }), Type.Number()]))
	})),
	details: Type.Optional(Type.Record(Type.String({
		minLength: 1,
		maxLength: 256
	}), Type.Unknown()))
});
Type.String({ maxLength: WORKER_PROTOCOL_MAX_PAYLOAD_BYTES });
Type.Integer({
	minimum: 0,
	maximum: Number.MAX_SAFE_INTEGER
});
Type.Integer({
	minimum: 1,
	maximum: Number.MAX_SAFE_INTEGER
});
Type.Union([
	Type.Literal("local"),
	Type.Literal("requested"),
	Type.Literal("provisioning"),
	Type.Literal("syncing"),
	Type.Literal("starting"),
	Type.Literal("active"),
	Type.Literal("draining"),
	Type.Literal("reconciling"),
	Type.Literal("reclaimed"),
	Type.Literal("failed")
]);
const SessionPlacementTimingProperties = {
	generation: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	createdAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	updatedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	stateChangedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	})
};
const SessionPlacementOwnerEpochSchema = Type.Integer({
	minimum: 1,
	maximum: Number.MAX_SAFE_INTEGER
});
const WorkerBundleHashSchema = Type.String({
	minLength: 64,
	maxLength: 64,
	pattern: "^[a-f0-9]{64}$"
});
const SessionPlacementWorkspaceProperties = {
	workspaceBaseManifestRef: NonEmptyString,
	remoteWorkspaceDir: NonEmptyString
};
const SessionPlacementAckProperties = {
	lastTranscriptAckCursor: Type.Optional(Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	})),
	lastLiveEventAckCursor: Type.Optional(Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}))
};
const SessionPlacementDiskSpaceSchema = closedObject({
	status: Type.Union([
		Type.Literal("ok"),
		Type.Literal("warning"),
		Type.Literal("critical")
	]),
	availableBytes: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	totalBytes: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	observedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	})
});
const SessionPlacementWorkerRuntimeInstallSchema = closedObject({
	phase: Type.Union([Type.Literal("transferring"), Type.Literal("installing")]),
	transferredBytes: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	totalBytes: Type.Integer({
		minimum: 1,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	startedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	updatedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	})
});
const SessionPlacementRunnerSchema = closedObject({
	kind: Type.Literal("device"),
	status: Type.Union([Type.Literal("available"), Type.Literal("offline")]),
	deviceId: Type.Optional(WorkerIdentifierSchema)
});
const SessionPlacementDiskSpaceProperties = { diskSpace: Type.Optional(SessionPlacementDiskSpaceSchema) };
const WORKER_MACHINE_CLASS_MAX_LENGTH = 128;
const WORKER_OPERATING_SYSTEM_MAX_LENGTH = 64;
const WorkerMachineClassSchema = Type.String({
	minLength: 1,
	maxLength: WORKER_MACHINE_CLASS_MAX_LENGTH
});
const WorkerOperatingSystemIdSchema = Type.String({
	minLength: 1,
	maxLength: WORKER_OPERATING_SYSTEM_MAX_LENGTH
});
const SessionPlacementMachineSchema = closedObject({
	class: Type.Optional(WorkerMachineClassSchema),
	os: Type.Optional(WorkerOperatingSystemIdSchema),
	osLabel: Type.Optional(Type.String({
		minLength: 1,
		maxLength: WORKER_OPERATING_SYSTEM_MAX_LENGTH
	})),
	cpu: Type.Optional(Type.Integer({
		minimum: 1,
		maximum: 65536
	})),
	memoryGb: Type.Optional(Type.Integer({
		minimum: 1,
		maximum: 65536
	}))
});
const SessionPlacementIdentityProperties = {
	providerId: Type.Optional(NonEmptyString),
	profileId: Type.Optional(NonEmptyString),
	machine: Type.Optional(SessionPlacementMachineSchema)
};
const WorkspaceResultConflictSchema = closedObject({
	paths: Type.Array(NonEmptyString, {
		minItems: 1,
		maxItems: 256
	}),
	stagedResultRef: NonEmptyString,
	totalCount: Type.Optional(Type.Integer({
		minimum: 1,
		maximum: Number.MAX_SAFE_INTEGER
	}))
});
const SessionPlacementConflictProperties = { workspaceResultConflict: Type.Optional(WorkspaceResultConflictSchema) };
const SessionPlacementWorkspaceReconciliationProperties = { workspaceResultReconciling: Type.Optional(Type.Literal(true)) };
const TerminalSessionPlacementProperties = {
	...SessionPlacementIdentityProperties,
	environmentId: Type.Optional(NonEmptyString),
	activeOwnerEpoch: Type.Optional(SessionPlacementOwnerEpochSchema),
	workspaceBaseManifestRef: Type.Optional(NonEmptyString),
	remoteWorkspaceDir: Type.Optional(NonEmptyString),
	workerBundleHash: Type.Optional(WorkerBundleHashSchema),
	...SessionPlacementAckProperties,
	...SessionPlacementConflictProperties,
	terminalReason: Type.Optional(NonEmptyString),
	terminalAtMs: Type.Optional(Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}))
};
function createUnownedSessionPlacementSchema(state) {
	return closedObject({
		state: Type.Literal(state),
		...SessionPlacementTimingProperties
	});
}
function workerOwnedSessionPlacementProperties(state) {
	return {
		state: Type.Literal(state),
		...SessionPlacementTimingProperties,
		...SessionPlacementIdentityProperties,
		environmentId: NonEmptyString,
		activeOwnerEpoch: SessionPlacementOwnerEpochSchema,
		workerBundleHash: WorkerBundleHashSchema,
		...SessionPlacementWorkspaceProperties,
		...SessionPlacementAckProperties,
		...SessionPlacementConflictProperties,
		...SessionPlacementDiskSpaceProperties
	};
}
const LocalSessionPlacementSchema = createUnownedSessionPlacementSchema("local");
const RequestedSessionPlacementSchema = createUnownedSessionPlacementSchema("requested");
const ProvisioningSessionPlacementSchema = closedObject({
	state: Type.Literal("provisioning"),
	...SessionPlacementTimingProperties,
	...SessionPlacementIdentityProperties,
	environmentId: Type.Optional(NonEmptyString),
	workerRuntimeInstall: Type.Optional(SessionPlacementWorkerRuntimeInstallSchema)
});
const SyncingSessionPlacementSchema = closedObject({
	state: Type.Literal("syncing"),
	...SessionPlacementTimingProperties,
	...SessionPlacementIdentityProperties,
	environmentId: NonEmptyString,
	workerBundleHash: WorkerBundleHashSchema
});
const StartingSessionPlacementSchema = closedObject({
	state: Type.Literal("starting"),
	...SessionPlacementTimingProperties,
	...SessionPlacementIdentityProperties,
	environmentId: NonEmptyString,
	workerBundleHash: WorkerBundleHashSchema,
	...SessionPlacementWorkspaceProperties
});
const ActiveWorkerSessionPlacementSchema = closedObject({
	...workerOwnedSessionPlacementProperties("active"),
	...SessionPlacementWorkspaceReconciliationProperties,
	runner: Type.Optional(SessionPlacementRunnerSchema),
	workerRuntimeInstall: Type.Optional(SessionPlacementWorkerRuntimeInstallSchema)
});
const DrainingSessionPlacementSchema = closedObject({
	...workerOwnedSessionPlacementProperties("draining"),
	...SessionPlacementWorkspaceReconciliationProperties
});
const ReconcilingSessionPlacementSchema = closedObject({ ...workerOwnedSessionPlacementProperties("reconciling") });
const ReclaimedSessionPlacementSchema = closedObject({
	state: Type.Literal("reclaimed"),
	...SessionPlacementTimingProperties,
	...TerminalSessionPlacementProperties
});
const FailedSessionPlacementSchema = closedObject({
	state: Type.Literal("failed"),
	...SessionPlacementTimingProperties,
	...TerminalSessionPlacementProperties,
	recoveryError: NonEmptyString,
	recoveryAction: Type.Optional(Type.Enum(["restart", "stop-first"], { type: "string" })),
	retryOnSend: Type.Optional(Type.Literal(true))
});
Type.Union([
	LocalSessionPlacementSchema,
	RequestedSessionPlacementSchema,
	ProvisioningSessionPlacementSchema,
	SyncingSessionPlacementSchema,
	StartingSessionPlacementSchema,
	ActiveWorkerSessionPlacementSchema,
	DrainingSessionPlacementSchema,
	ReconcilingSessionPlacementSchema,
	ReclaimedSessionPlacementSchema,
	FailedSessionPlacementSchema
]);
/**
* Requests one-way dispatch to an explicit or automatically selected device (`operator.write`),
* an explicit profile (`operator.admin`), or an `operator.admin`-only
* `cloudWorkers.projectProfiles` lookup when no target is supplied. Target modes are exclusive.
* An absent, unmatched, or invalid mapping is rejected with `INVALID_REQUEST` instead of
* provisioning or falling back to another target.
*/
const SessionsDispatchParamsSchema = Type.Object({
	key: NonEmptyString,
	agentId: Type.Optional(NonEmptyString),
	profileId: Type.Optional(NonEmptyString),
	deviceId: Type.Optional(NonEmptyString),
	autoDevice: Type.Optional(Type.Literal(true)),
	machineClass: Type.Optional(WorkerMachineClassSchema),
	os: Type.Optional(WorkerOperatingSystemIdSchema)
}, {
	additionalProperties: false,
	oneOf: [
		{
			required: ["profileId"],
			not: { anyOf: [{ required: ["deviceId"] }, { required: ["autoDevice"] }] }
		},
		{
			required: ["deviceId"],
			not: { anyOf: [
				{ required: ["profileId"] },
				{ required: ["autoDevice"] },
				{ required: ["machineClass"] },
				{ required: ["os"] }
			] }
		},
		{
			required: ["autoDevice"],
			not: { anyOf: [
				{ required: ["profileId"] },
				{ required: ["deviceId"] },
				{ required: ["machineClass"] },
				{ required: ["os"] }
			] }
		},
		{ not: { anyOf: [
			{ required: ["profileId"] },
			{ required: ["deviceId"] },
			{ required: ["autoDevice"] },
			{ required: ["machineClass"] },
			{ required: ["os"] }
		] } }
	]
});
closedObject({
	ok: Type.Literal(true),
	key: NonEmptyString,
	sessionId: NonEmptyString,
	placement: ActiveWorkerSessionPlacementSchema
});
Type.Object({
	key: NonEmptyString,
	agentId: Type.Optional(NonEmptyString),
	recoverToGateway: Type.Optional(closedObject({ expectedGeneration: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}) }))
}, { additionalProperties: false });
/** Terminal placement returned after a worker reclaim operation. */
const SessionsReclaimResultPlacementSchema = Type.Union([LocalSessionPlacementSchema, ReclaimedSessionPlacementSchema]);
Type.Object({
	ok: Type.Literal(true),
	key: NonEmptyString,
	sessionId: NonEmptyString,
	placement: SessionsReclaimResultPlacementSchema
}, { additionalProperties: false });
/** Exact active source observed before a session placement move. */
const SessionMoveExpectedSourceSchema = closedObject({
	generation: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	environmentId: WorkerIdentifierSchema,
	ownerEpoch: SessionPlacementOwnerEpochSchema
});
/** Moves the session back to the Gateway without redispatching it. */
const SessionMoveGatewayTargetSchema = closedObject({ kind: Type.Literal("gateway") });
/** Moves the session to one configured cloud worker profile. */
const SessionMoveProfileTargetSchema = closedObject({
	kind: Type.Literal("profile"),
	profileId: WorkerIdentifierSchema,
	machineClass: Type.Optional(WorkerMachineClassSchema),
	os: Type.Optional(WorkerOperatingSystemIdSchema)
});
/** Moves the session to one paired device worker. */
const SessionMoveDeviceTargetSchema = closedObject({
	kind: Type.Literal("device"),
	deviceId: WorkerIdentifierSchema
});
/** Closed destination union for session placement moves. */
const SessionMoveTargetSchema = Type.Union([
	SessionMoveGatewayTargetSchema,
	SessionMoveProfileTargetSchema,
	SessionMoveDeviceTargetSchema
]);
closedObject({
	target: SessionMoveTargetSchema,
	updatedAtMs: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	}),
	error: Type.Optional(Type.String({
		minLength: 1,
		maxLength: 1024
	}))
});
const SessionsMoveTargetCorrelationSchema = Type.Union([Type.Object({ target: SessionMoveGatewayTargetSchema }), Type.Object({ target: Type.Union([SessionMoveProfileTargetSchema, SessionMoveDeviceTargetSchema]) }, { not: { required: ["abandonSource"] } })]);
/** Requests one exact-source placement move without replaying active work. */
const SessionsMoveParamsSchema = Type.Object({
	key: NonEmptyString,
	agentId: Type.Optional(NonEmptyString),
	expected: SessionMoveExpectedSourceSchema,
	target: SessionMoveTargetSchema,
	abandonSource: Type.Optional(Type.Literal(true))
}, {
	additionalProperties: false,
	allOf: [SessionsMoveTargetCorrelationSchema]
});
/** Bounded placement state returned by sessions.move. */
const SessionMovePlacementSchema = closedObject({
	state: Type.Union([Type.Literal("local"), Type.Literal("active")]),
	generation: Type.Integer({
		minimum: 0,
		maximum: Number.MAX_SAFE_INTEGER
	})
});
closedObject({
	ok: Type.Literal(true),
	key: NonEmptyString,
	sessionId: NonEmptyString,
	placement: SessionMovePlacementSchema
});
//#endregion
//#region packages/gateway-protocol/src/session-placement-validators.ts
const validateSessionsDispatchParams = /* @__PURE__ */ lazyCompile(SessionsDispatchParamsSchema);
const validateSessionsMoveParams = /* @__PURE__ */ lazyCompile(SessionsMoveParamsSchema);
//#endregion
//#region src/shared/session-method-scopes.ts
/** Returns the exact Gateway/CLI scope for params-aware session mutations. */
function resolveDynamicSessionMutationRequiredScope(method, params) {
	if (method === "sessions.dispatch") {
		if (!validateSessionsDispatchParams(params)) return "operator.write";
		return params.deviceId !== void 0 || params.autoDevice === true ? "operator.write" : "operator.admin";
	}
	if (method === "sessions.move") return validateSessionsMoveParams(params) && params.target.kind === "profile" ? "operator.admin" : "operator.write";
	return resolveBaseSessionMutationRequiredScope(method, params);
}
//#endregion
//#region src/gateway/agent-command-policy.ts
/** Commands routed through `agent` that mutate session lifecycle state. */
const AGENT_SESSION_RESET_COMMAND_RE = /^\/(new|reset)(?:\s+([\s\S]*))?$/i;
/** Returns true when an agent message requests a session reset. */
function isAgentSessionResetCommand(message) {
	return typeof message === "string" && AGENT_SESSION_RESET_COMMAND_RE.test(message);
}
//#endregion
//#region src/gateway/methods/core-descriptors.ts
const CONTROL_PLANE_WRITE = { controlPlaneWrite: true };
const CORE_GATEWAY_METHOD_SPECS = [
	[
		"health",
		"health",
		"operator.read",
		"<=2026.7"
	],
	[
		"diagnostics.stability",
		"diagnostics",
		"operator.read",
		"<=2026.7"
	],
	[
		"doctor.memory.status",
		"doctor",
		"operator.read",
		"<=2026.7"
	],
	[
		"doctor.memory.dreamDiary",
		"doctor",
		"operator.read",
		"<=2026.7"
	],
	[
		"doctor.memory.backfillDreamDiary",
		"doctor",
		"operator.write",
		"<=2026.7"
	],
	[
		"doctor.memory.resetDreamDiary",
		"doctor",
		"operator.write",
		"<=2026.7"
	],
	[
		"doctor.memory.resetGroundedShortTerm",
		"doctor",
		"operator.write",
		"<=2026.7"
	],
	[
		"doctor.memory.repairDreamingArtifacts",
		"doctor",
		"operator.write",
		"<=2026.7"
	],
	[
		"doctor.memory.dedupeDreamDiary",
		"doctor",
		"operator.write",
		"<=2026.7"
	],
	[
		"logs.tail",
		"logs",
		"operator.read",
		"<=2026.7"
	],
	[
		"channels.status",
		"channels",
		"operator.read",
		"<=2026.7"
	],
	[
		"channels.start",
		"channels",
		"operator.admin",
		"<=2026.7"
	],
	[
		"channels.stop",
		"channels",
		"operator.admin",
		"<=2026.7"
	],
	[
		"channels.logout",
		"channels",
		"operator.admin",
		"<=2026.7"
	],
	[
		"status",
		"health",
		"operator.read",
		"<=2026.7"
	],
	[
		"usage.status",
		"usage",
		"operator.read",
		"<=2026.7"
	],
	[
		"usage.cost",
		"usage",
		"operator.read",
		"<=2026.7"
	],
	[
		"tts.status",
		"tts",
		"operator.read",
		"<=2026.7"
	],
	[
		"tts.providers",
		"tts",
		"operator.read",
		"<=2026.7"
	],
	[
		"tts.personas",
		"tts",
		"operator.read",
		"<=2026.7"
	],
	[
		"tts.enable",
		"tts",
		"operator.write",
		"<=2026.7"
	],
	[
		"tts.disable",
		"tts",
		"operator.write",
		"<=2026.7"
	],
	[
		"tts.convert",
		"tts",
		"operator.write",
		"<=2026.7"
	],
	[
		"tts.setProvider",
		"tts",
		"operator.write",
		"<=2026.7"
	],
	[
		"tts.setPersona",
		"tts",
		"operator.write",
		"<=2026.7"
	],
	[
		"config.get",
		"config",
		"operator.read",
		"<=2026.7"
	],
	[
		"config.set",
		"config",
		"operator.admin",
		"<=2026.7"
	],
	[
		"config.apply",
		"config",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"config.patch",
		"config",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"config.schema",
		"config",
		"operator.read",
		"<=2026.7"
	],
	[
		"config.schema.lookup",
		"config",
		"operator.read",
		"<=2026.7"
	],
	[
		"exec.approvals.get",
		"exec-approvals",
		"operator.admin",
		"<=2026.7"
	],
	[
		"exec.approvals.set",
		"exec-approvals",
		"operator.admin",
		"<=2026.7"
	],
	[
		"exec.approvals.node.get",
		"exec-approvals",
		"operator.admin",
		"<=2026.7"
	],
	[
		"exec.approvals.node.set",
		"exec-approvals",
		"operator.admin",
		"<=2026.7"
	],
	[
		"exec.approval.get",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"exec.approval.list",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"exec.approval.request",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"exec.approval.waitDecision",
		null,
		"operator.approvals",
		"<=2026.7",
		{ lifetime: "observation" }
	],
	[
		"exec.approval.resolve",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"exec.approval.grants.list",
		null,
		"operator.approvals",
		"2026.8"
	],
	[
		"exec.approval.grants.revoke",
		null,
		"operator.approvals",
		"2026.8"
	],
	[
		"question.request",
		null,
		"operator.questions",
		"2026.7"
	],
	[
		"question.waitAnswer",
		null,
		"operator.questions",
		"2026.7",
		{ lifetime: "observation" }
	],
	[
		"question.resolve",
		null,
		"operator.questions",
		"2026.7"
	],
	[
		"question.get",
		null,
		"operator.questions",
		"2026.7"
	],
	[
		"question.list",
		null,
		"operator.questions",
		"2026.7"
	],
	[
		"plugin.approval.list",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"plugin.approval.request",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"plugin.approval.waitDecision",
		null,
		"operator.approvals",
		"<=2026.7",
		{ lifetime: "observation" }
	],
	[
		"plugin.approval.resolve",
		null,
		"operator.approvals",
		"<=2026.7"
	],
	[
		"plugins.uiDescriptors",
		"plugin-host-hooks",
		"operator.read",
		"<=2026.7"
	],
	[
		"plugins.sessionAction",
		"plugin-host-hooks",
		"dynamic",
		"<=2026.7"
	],
	[
		"openclaw.chat",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"openclaw.chat.history",
		"system-agent",
		"operator.admin",
		"2026.7"
	],
	[
		"openclaw.changes.list",
		"system-changes",
		"operator.admin",
		"<=2026.7"
	],
	[
		"openclaw.approval.list",
		"system-agent",
		"operator.approvals",
		"<=2026.7"
	],
	[
		"openclaw.setup.detect",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"openclaw.setup.activate",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"openclaw.setup.activate.start",
		"system-agent",
		"operator.admin",
		"2026.8"
	],
	[
		"openclaw.setup.auth.start",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"openclaw.setup.prepare.start",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"wizard.start",
		"wizard",
		"operator.admin",
		"<=2026.7"
	],
	[
		"wizard.next",
		"wizard",
		"operator.admin",
		"<=2026.7"
	],
	[
		"wizard.cancel",
		"wizard",
		"operator.admin",
		"<=2026.7"
	],
	[
		"wizard.status",
		"wizard",
		"operator.admin",
		"<=2026.7"
	],
	[
		"talk.catalog",
		"talk",
		"operator.read",
		"<=2026.7"
	],
	[
		"talk.config",
		"talk",
		"dynamic",
		"<=2026.7"
	],
	[
		"talk.client.create",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.client.transcript",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.client.close",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.client.toolCall",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.client.steer",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.create",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.appendAudio",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.cancelOutput",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.acknowledgeMark",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.submitToolResult",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.steer",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.session.close",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.speak",
		"talk",
		"operator.talk",
		"<=2026.7"
	],
	[
		"talk.mode",
		"talk-mode",
		"operator.talk",
		"<=2026.7"
	],
	[
		"commands.list",
		"commands",
		"operator.read",
		"<=2026.7"
	],
	[
		"models.list",
		"models",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"models.authStatus",
		"models-auth-status",
		"operator.read",
		"<=2026.7"
	],
	[
		"models.authLogout",
		"models-auth-status",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"tools.catalog",
		"tools-catalog",
		"operator.read",
		"<=2026.7"
	],
	[
		"tools.effective",
		"tools-effective",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"tools.invoke",
		"tools-invoke",
		"operator.write",
		"<=2026.7"
	],
	[
		"mcp.app.view",
		"mcp-app",
		"operator.read",
		"<=2026.7"
	],
	[
		"mcp.app.listTools",
		"mcp-app",
		"operator.read",
		"<=2026.7"
	],
	[
		"mcp.app.listResources",
		"mcp-app",
		"operator.read",
		"<=2026.7"
	],
	[
		"mcp.app.listResourceTemplates",
		"mcp-app",
		"operator.read",
		"<=2026.7"
	],
	[
		"mcp.app.readResource",
		"mcp-app",
		"operator.read",
		"<=2026.7"
	],
	[
		"mcp.app.callTool",
		"mcp-app",
		"operator.write",
		"<=2026.7"
	],
	[
		"mcp.app.updateModelContext",
		"mcp-app",
		"operator.write",
		"<=2026.7"
	],
	[
		"board.get",
		"board",
		"operator.read",
		"<=2026.7"
	],
	[
		"board.update",
		"board",
		"operator.write",
		"<=2026.7"
	],
	[
		"board.widget.put",
		"board",
		"operator.write",
		"<=2026.7"
	],
	[
		"board.widget.grant",
		"board",
		"operator.approvals",
		"<=2026.7"
	],
	[
		"board.widget.appView",
		"board",
		"operator.read",
		"2026.7"
	],
	[
		"board.event",
		"board",
		"operator.write",
		"<=2026.7"
	],
	[
		"audit.list",
		"audit",
		"operator.read",
		"2026.7"
	],
	[
		"audit.activity.list",
		"audit",
		"operator.read",
		"2026.7"
	],
	[
		"users.list",
		"users",
		"operator.read",
		"<=2026.7"
	],
	[
		"users.self",
		"users",
		"operator.read",
		"<=2026.7"
	],
	[
		"users.linkEmail",
		"users",
		"operator.admin",
		"<=2026.7"
	],
	[
		"users.setDisplayName",
		"users",
		"operator.write",
		"<=2026.7"
	],
	[
		"users.setAvatar",
		"users",
		"operator.write",
		"<=2026.7"
	],
	[
		"users.setRole",
		"users",
		"operator.admin",
		"2026.8"
	],
	[
		"users.listAuthLinks",
		"users",
		"operator.read",
		"2026.8"
	],
	[
		"users.listModelAccounts",
		"users",
		"operator.read",
		"2026.8"
	],
	[
		"users.selectModelAccount",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.linkAuthProfile",
		"users",
		"operator.admin",
		"2026.8"
	],
	[
		"users.unlinkAuthProfile",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.authConnect.start",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.authConnect.answer",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.authConnect.status",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.authConnect.cancel",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"users.authConnect.catalog",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"taskSuggestions.list",
		"task-suggestions",
		"operator.read",
		"<=2026.7"
	],
	[
		"taskSuggestions.create",
		"task-suggestions",
		"operator.write",
		"<=2026.7"
	],
	[
		"taskSuggestions.accept",
		"task-suggestions",
		"operator.admin",
		"<=2026.7"
	],
	[
		"taskSuggestions.dismiss",
		"task-suggestions",
		"operator.write",
		"<=2026.7"
	],
	[
		"environments.list",
		"environments",
		"dynamic",
		"2026.7"
	],
	[
		"environments.status",
		"environments",
		"operator.read",
		"2026.7"
	],
	[
		"worktrees.list",
		"worktrees",
		"operator.read",
		"2026.7"
	],
	[
		"worktrees.branches",
		"worktrees",
		"operator.write",
		"2026.7"
	],
	[
		"fs.listDir",
		"fs",
		"dynamic",
		"<=2026.7"
	],
	[
		"worktrees.create",
		"worktrees",
		"operator.write",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"worktrees.remove",
		"worktrees",
		"operator.admin",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"worktrees.restore",
		"worktrees",
		"operator.admin",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"worktrees.gc",
		"worktrees",
		"operator.admin",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"agents.list",
		"agents",
		"operator.read",
		"<=2026.7"
	],
	[
		"agents.create",
		"agents",
		"operator.admin",
		"<=2026.7"
	],
	[
		"agents.update",
		"agents",
		"operator.admin",
		"<=2026.7"
	],
	[
		"agents.delete",
		"agents",
		"operator.admin",
		"<=2026.7"
	],
	[
		"agents.files.list",
		"agents",
		"operator.read",
		"<=2026.7"
	],
	[
		"agents.files.get",
		"agents",
		"operator.read",
		"<=2026.7"
	],
	[
		"agents.files.set",
		"agents",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.files.list",
		"sessions-files",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.files.get",
		"sessions-files",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.files.set",
		"sessions-files",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.files.reveal",
		"sessions-files",
		"operator.admin",
		"<=2026.7"
	],
	[
		"artifacts.list",
		"artifacts",
		"operator.read",
		"<=2026.7"
	],
	[
		"artifacts.get",
		"artifacts",
		"operator.read",
		"<=2026.7"
	],
	[
		"artifacts.download",
		"artifacts",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.status",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.library.list",
		"skills",
		"operator.read",
		"2026.8"
	],
	[
		"skills.library.read",
		"skills",
		"operator.read",
		"2026.8"
	],
	[
		"skills.library.save",
		"skills",
		"operator.write",
		"2026.8"
	],
	[
		"skills.library.mutate",
		"skills",
		"operator.write",
		"2026.8"
	],
	[
		"skills.library.activate",
		"skills",
		"operator.write",
		"2026.8"
	],
	[
		"skills.library.import",
		"skills",
		"operator.write",
		"2026.8"
	],
	[
		"skills.library.upload",
		"skills",
		"operator.write",
		"2026.8"
	],
	[
		"skills.search",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.detail",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.securityVerdicts",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.skillCard",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.bins",
		"skills",
		"node",
		"<=2026.7"
	],
	[
		"skills.upload.begin",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.upload.chunk",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.upload.commit",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.install",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.update",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.curator.status",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.curator.pin",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.curator.unpin",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.curator.restore",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.list",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.proposals.inspect",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.proposals.historyStatus",
		"skills",
		"operator.read",
		"<=2026.7"
	],
	[
		"skills.proposals.historyScan",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.create",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.update",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.revise",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.requestRevision",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.apply",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.reject",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"skills.proposals.quarantine",
		"skills",
		"operator.admin",
		"<=2026.7"
	],
	[
		"update.status",
		"update",
		"operator.admin",
		"<=2026.7"
	],
	[
		"update.run",
		"update",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"voicewake.get",
		"voicewake",
		"operator.read",
		"<=2026.7"
	],
	[
		"voicewake.set",
		"voicewake",
		"operator.write",
		"<=2026.7"
	],
	[
		"secrets.reload",
		null,
		"operator.admin",
		"<=2026.7"
	],
	[
		"secrets.resolve",
		null,
		"operator.admin",
		"<=2026.7"
	],
	[
		"voicewake.routing.get",
		"voicewake-routing",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.list",
		"sessions-read",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"sessions.subscribe",
		"sessions-subscriptions",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"sessions.messages.subscribe",
		"sessions-subscriptions",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.messages.unsubscribe",
		"sessions-subscriptions",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.viewers.set",
		"sessions-subscriptions",
		"operator.read",
		"2026.7"
	],
	[
		"sessions.preview",
		"sessions-read",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.describe",
		"sessions-read",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.branches.list",
		"sessions-rewind",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.branches.switch",
		"sessions-rewind",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.rewind",
		"sessions-rewind",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.fork",
		"sessions-rewind",
		"operator.write",
		"<=2026.7"
	],
	[
		"sessions.create",
		"sessions-create",
		"dynamic",
		"<=2026.7",
		{ startup: true }
	],
	[
		"sessions.recover",
		"sessions-recover",
		"operator.write",
		"2026.8",
		{ startup: true }
	],
	[
		"sessions.send",
		"sessions-messaging",
		"operator.write",
		"<=2026.7",
		{ startup: true }
	],
	[
		"sessions.abort",
		"sessions-abort",
		"operator.write",
		"<=2026.7",
		{ startup: true }
	],
	[
		"sessions.patch",
		"sessions-mutations",
		"dynamic",
		"<=2026.7"
	],
	[
		"sessions.goal.update",
		"sessions-goal",
		"operator.write",
		"2026.8"
	],
	[
		"sessions.goal.clear",
		"sessions-goal",
		"operator.write",
		"2026.8"
	],
	[
		"sessions.pluginPatch",
		"sessions-mutations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.cleanup",
		"sessions-read",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.reset",
		"sessions-mutations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.delete",
		"sessions-delete",
		"dynamic",
		"<=2026.7"
	],
	[
		"sessions.compact",
		"sessions-compact",
		"operator.admin",
		"<=2026.7"
	],
	[
		"sessions.groups.list",
		"sessions-groups",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.groups.defaults",
		"sessions-groups",
		"operator.write",
		"2026.8"
	],
	[
		"sessions.groups.put",
		"sessions-groups",
		"operator.write",
		"<=2026.7"
	],
	[
		"sessions.groups.rename",
		"sessions-groups",
		"operator.write",
		"<=2026.7"
	],
	[
		"sessions.groups.update",
		"sessions-groups",
		"operator.write",
		"2026.8"
	],
	[
		"sessions.groups.delete",
		"sessions-groups",
		"operator.write",
		"<=2026.7"
	],
	[
		"last-heartbeat",
		"system",
		"operator.read",
		"<=2026.7"
	],
	[
		"set-heartbeats",
		"system",
		"operator.admin",
		"<=2026.7"
	],
	[
		"wake",
		"cron",
		"operator.write",
		"<=2026.7"
	],
	[
		"node.pair.list",
		"nodes",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"node.pair.approve",
		"nodes",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"node.pair.reject",
		"nodes",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"node.pair.remove",
		"nodes",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.list",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.approve",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.reject",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.remove",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.rename",
		"devices",
		"operator.pairing",
		"2026.7"
	],
	[
		"device.token.rotate",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.token.revoke",
		"devices",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"device.pair.setupCode",
		"device-pair-setup",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"device.pair.setupStatus",
		"device-pair-setup",
		"operator.admin",
		"2026.8",
		{ advertise: false }
	],
	[
		"node.rename",
		"nodes",
		"operator.pairing",
		"<=2026.7"
	],
	[
		"node.list",
		"nodes",
		"operator.read",
		"<=2026.7"
	],
	[
		"node.describe",
		"nodes",
		"operator.read",
		"<=2026.7"
	],
	[
		"node.pluginSurface.refresh",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.pluginTools.update",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.skills.update",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.runnerInventory.update",
		"nodes",
		"node",
		"2026.8",
		{ advertise: false }
	],
	[
		"node.pending.drain",
		"nodes-pending",
		"node",
		"<=2026.7"
	],
	[
		"node.pending.enqueue",
		"nodes-pending",
		"operator.write",
		"<=2026.7"
	],
	[
		"node.invoke",
		"nodes",
		"dynamic",
		"<=2026.7"
	],
	[
		"node.pending.pull",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.pending.ack",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.invoke.progress",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.invoke.result",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"node.event",
		"nodes",
		"node",
		"<=2026.7"
	],
	[
		"cron.get",
		"cron",
		"operator.read",
		"<=2026.7"
	],
	[
		"cron.list",
		"cron",
		"operator.read",
		"<=2026.7"
	],
	[
		"cron.status",
		"cron",
		"operator.read",
		"<=2026.7"
	],
	[
		"cron.scratch.get",
		"cron",
		"operator.admin",
		"2026.7"
	],
	[
		"cron.scratch.set",
		"cron",
		"operator.admin",
		"2026.7"
	],
	[
		"cron.add",
		"cron",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"cron.update",
		"cron",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"cron.remove",
		"cron",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"cron.run",
		"cron",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"cron.runs",
		"cron",
		"operator.read",
		"<=2026.7"
	],
	[
		"gateway.identity.get",
		"system",
		"operator.read",
		"<=2026.7"
	],
	[
		"gateway.restart.preflight",
		"restart",
		"operator.read",
		"<=2026.7",
		{ compatibilityRestored: true }
	],
	[
		"gateway.restart.request",
		"restart",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"system-presence",
		"system",
		"operator.read",
		"<=2026.7"
	],
	[
		"system-event",
		"system",
		"operator.admin",
		"<=2026.7"
	],
	[
		"message.action",
		"send",
		"operator.write",
		"<=2026.7"
	],
	[
		"conversations.send",
		"conversations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"conversations.turn",
		"conversations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"conversations.turn.cancel",
		"conversations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"send",
		"send",
		"operator.write",
		"<=2026.7"
	],
	[
		"agent",
		"agent",
		"dynamic",
		"<=2026.7",
		{ startup: true }
	],
	[
		"agent.identity.get",
		"agent-identity",
		"operator.read",
		"<=2026.7"
	],
	[
		"agent.wait",
		"agent",
		"operator.write",
		"<=2026.7",
		{
			startup: true,
			lifetime: "observation"
		}
	],
	[
		"chat.history",
		"chat",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"chat.startup",
		"chat",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"chat.metadata",
		"chat",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"chat.message.get",
		"chat",
		"operator.read",
		"<=2026.7",
		{ startup: true }
	],
	[
		"chat.abort",
		"chat-abort",
		"operator.write",
		"<=2026.7"
	],
	[
		"chat.send",
		"chat-send",
		"operator.write",
		"<=2026.7",
		{ startup: true }
	],
	[
		"terminal.open",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"terminal.input",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"terminal.resize",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"terminal.close",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"channels.pairing.list",
		"channel-pairing",
		"operator.pairing",
		"2026.7"
	],
	[
		"channels.pairing.approve",
		"channel-pairing",
		"dynamic",
		"2026.7"
	],
	[
		"channels.pairing.dismiss",
		"channel-pairing",
		"operator.pairing",
		"2026.7"
	],
	[
		"assistant.media.get",
		null,
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.get",
		"sessions-read",
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.resolve",
		"sessions-read",
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.usage",
		"usage",
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.usage.timeseries",
		"usage",
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.usage.logs",
		"usage",
		"operator.read",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"poll",
		"send",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"sessions.steer",
		"sessions-messaging",
		"operator.write",
		"<=2026.7",
		{
			advertise: false,
			description: "Deprecated alias for chat.send queueMode interrupt; removal per protocol deprecation policy."
		}
	],
	[
		"push.test",
		"push",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"attach.grant",
		"attach",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"attach.revoke",
		"attach",
		"operator.admin",
		"<=2026.7"
	],
	[
		"push.web.vapidPublicKey",
		"push",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"push.web.subscribe",
		"push",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"push.web.unsubscribe",
		"push",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"push.web.test",
		"push",
		"operator.write",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"push.web.preferences.get",
		"push",
		"operator.read",
		"2026.8"
	],
	[
		"push.web.preferences.set",
		"push",
		"operator.write",
		"2026.8"
	],
	[
		"config.openFile",
		"config",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"connect",
		"connect",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"chat.inject",
		"chat",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"nativeHook.invoke",
		"native-hook-relay",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"web.login.start",
		"web",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"web.login.wait",
		"web",
		"operator.admin",
		"<=2026.7",
		{ advertise: false }
	],
	[
		"terminal.attach",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"terminal.list",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"controlUi.githubPreview",
		"control-ui",
		"operator.read",
		"<=2026.7"
	],
	[
		"system.info",
		"system",
		"operator.read",
		"<=2026.7"
	],
	[
		"agents.workspace.list",
		"agents-workspace",
		"operator.read",
		"2026.7"
	],
	[
		"agents.workspace.get",
		"agents-workspace",
		"operator.read",
		"2026.7"
	],
	[
		"tts.speak",
		"tts",
		"operator.write",
		"2026.7"
	],
	[
		"plugins.list",
		"plugins",
		"operator.read",
		"<=2026.7"
	],
	[
		"plugins.search",
		"plugins",
		"operator.read",
		"<=2026.7"
	],
	[
		"plugins.install",
		"plugins-mutations",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"plugins.setEnabled",
		"plugins-mutations",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"plugins.uninstall",
		"plugins-mutations",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"plugins.refresh",
		"plugins-mutations",
		"operator.admin",
		"<=2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"controlUi.sessionPullRequests.subscribe",
		"control-ui",
		"operator.read",
		"2026.7"
	],
	[
		"controlUi.sessionPreview",
		"control-ui",
		"operator.read",
		"2026.8"
	],
	[
		"gateway.suspend.prepare",
		"suspend",
		"operator.admin",
		"2026.7",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"gateway.suspend.status",
		"suspend",
		"operator.read",
		"2026.7"
	],
	[
		"gateway.suspend.resume",
		"suspend",
		"operator.admin",
		"2026.7"
	],
	[
		"chat.toolTitles",
		"chat",
		"operator.write",
		"<=2026.7"
	],
	[
		"sessions.diff",
		"sessions-diff",
		"operator.read",
		"<=2026.7"
	],
	[
		"openclaw.setup.verify",
		"system-agent",
		"operator.admin",
		"<=2026.7"
	],
	[
		"environments.create",
		"environments",
		"operator.admin",
		"2026.7",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"environments.destroy",
		"environments",
		"operator.admin",
		"2026.7",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"sessions.catalog.list",
		"session-catalog",
		"operator.read",
		"2026.7"
	],
	[
		"sessions.catalog.read",
		"session-catalog",
		"operator.read",
		"2026.7"
	],
	[
		"terminal.upload",
		"terminal",
		"operator.admin",
		"2026.7"
	],
	[
		"sessions.catalog.continue",
		"session-catalog",
		"operator.write",
		"2026.7"
	],
	[
		"sessions.catalog.archive",
		"session-catalog",
		"operator.write",
		"2026.7"
	],
	[
		"approval.get",
		null,
		"operator.approvals",
		"2026.7"
	],
	[
		"approval.resolve",
		null,
		"operator.approvals",
		"2026.7"
	],
	[
		"sessions.search",
		"sessions-read",
		"operator.read",
		"<=2026.7"
	],
	[
		"sessions.dispatch",
		"sessions-dispatch",
		"dynamic",
		"2026.7",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"sessions.reclaim",
		"sessions-dispatch",
		"operator.write",
		"2026.7",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"models.probe",
		"models-probe",
		"operator.admin",
		"<=2026.7"
	],
	[
		"migrations.memory.plan",
		"migrations",
		"operator.admin",
		"2026.7"
	],
	[
		"migrations.memory.apply",
		"migrations",
		"operator.admin",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"ui.command",
		"ui-command",
		"operator.write",
		"2026.7"
	],
	[
		"approval.history",
		null,
		"operator.approvals",
		"2026.7"
	],
	[
		"plugin.surface.refresh",
		"nodes",
		"operator.read",
		"<=2026.7"
	],
	[
		"conversations.list",
		"conversations",
		"operator.admin",
		"<=2026.7"
	],
	[
		"session.discussion.info",
		"session-discussion",
		"operator.read",
		"2026.7"
	],
	[
		"session.discussion.open",
		"session-discussion",
		"operator.write",
		"2026.7"
	],
	[
		"board.prompt.authorize",
		"board",
		"operator.read",
		"2026.7"
	],
	[
		"board.data.read",
		"board",
		"operator.read",
		"2026.7"
	],
	[
		"board.action",
		"board",
		"operator.write",
		"2026.7"
	],
	[
		"sessions.observer.visibility",
		"session-observer-rpc",
		"operator.read",
		"2026.7"
	],
	[
		"session.visibility.set",
		"sessions-sharing",
		"operator.write",
		"2026.7"
	],
	[
		"session.members.list",
		"sessions-sharing",
		"operator.read",
		"2026.7"
	],
	[
		"session.members.add",
		"sessions-sharing",
		"operator.write",
		"2026.7"
	],
	[
		"session.members.remove",
		"sessions-sharing",
		"operator.write",
		"2026.7"
	],
	[
		"session.suggestions.add",
		"sessions-suggestions",
		"operator.write",
		"2026.7"
	],
	[
		"session.suggestions.list",
		"sessions-suggestions",
		"operator.read",
		"2026.7"
	],
	[
		"session.suggestions.resolve",
		"sessions-suggestions",
		"operator.write",
		"2026.7"
	],
	[
		"session.typing",
		"sessions-suggestions",
		"operator.write",
		"2026.7"
	],
	[
		"sessions.companion.ask",
		"session-companion-rpc",
		"operator.read",
		"2026.7"
	],
	[
		"sessions.companion.state",
		"session-companion-rpc",
		"operator.read",
		"2026.7"
	],
	[
		"sessions.companion.reset",
		"session-companion-rpc",
		"operator.write",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"memory.search",
		"memory-search",
		"operator.read",
		"2026.7"
	],
	[
		"skills.proposals.events.list",
		"skills",
		"operator.read",
		"2026.7"
	],
	[
		"skills.proposals.evaluate",
		"skills",
		"operator.admin",
		"2026.7",
		CONTROL_PLANE_WRITE
	],
	[
		"hooks.status",
		"hooks-status",
		"operator.read",
		"2026.7"
	],
	[
		"audit.run.inspect",
		"audit",
		"operator.read",
		"2026.7"
	],
	[
		"sessions.patchMany",
		"sessions-mutations",
		"dynamic",
		"2026.8"
	],
	[
		"update.hold",
		"update",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"sessions.catalog.startTerminal",
		"session-catalog",
		"operator.admin",
		"2026.8"
	],
	[
		"worker.desktop.observe",
		"environments",
		"operator.admin",
		"2026.8",
		{ startup: true }
	],
	[
		"projects.list",
		"projects",
		"operator.read",
		"2026.8"
	],
	[
		"projects.register",
		"projects",
		"operator.admin",
		"2026.8"
	],
	[
		"projects.remove",
		"projects",
		"operator.admin",
		"2026.8"
	],
	[
		"worker.desktop.launch",
		"environments",
		"operator.admin",
		"2026.8",
		{ startup: true }
	],
	[
		"secrets.store.list",
		null,
		"operator.admin",
		"2026.8"
	],
	[
		"secrets.store.set",
		null,
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"secrets.store.delete",
		null,
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"users.prefs.get",
		"users",
		"operator.read",
		"2026.8"
	],
	[
		"users.prefs.set",
		"users",
		"operator.write",
		"2026.8"
	],
	[
		"projects.add",
		"projects",
		"operator.write",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"projects.searchRemote",
		"projects",
		"operator.read",
		"2026.8",
		{ description: "Search GitHub repositories that can be cloned as managed projects." }
	],
	[
		"desktop.observe",
		"environments",
		"operator.admin",
		"2026.8",
		{ startup: true }
	],
	[
		"desktop.launch",
		"environments",
		"operator.admin",
		"2026.8",
		{ startup: true }
	],
	[
		"device.scopes.requestUpgrade",
		"devices",
		"operator.read",
		"2026.8"
	],
	[
		"device.scopes.waitUpgrade",
		"devices",
		"operator.read",
		"2026.8",
		{ lifetime: "observation" }
	],
	[
		"portal.list",
		"portals",
		"operator.read",
		"2026.8"
	],
	[
		"portal.open",
		"portals",
		"operator.write",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"portal.close",
		"portals",
		"operator.write",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"sessions.move",
		"sessions-dispatch",
		"dynamic",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"sessions.assignOwner",
		"sessions-mutations",
		"operator.write",
		"2026.8"
	],
	[
		"progressCard.get",
		"progress-card",
		"operator.read",
		"2026.8"
	],
	[
		"progressCard.put",
		"progress-card",
		"operator.write",
		"2026.8"
	],
	[
		"tools.github.status",
		"tools-github",
		"operator.read",
		"2026.8"
	],
	[
		"tools.github.configure",
		"tools-github",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"tools.github.authorize.start",
		"tools-github",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"tools.github.authorize.poll",
		"tools-github",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"tools.github.authorize.cancel",
		"tools-github",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"sessions.github.publish",
		"sessions-github",
		"operator.sessions.write",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"diagnostics.lanes",
		"diagnostics",
		"operator.read",
		"2026.8"
	],
	[
		"session.members.listEvidence",
		"sessions-sharing",
		"operator.read",
		"2026.8"
	],
	[
		"plugins.inspect",
		"plugins",
		"operator.read",
		"2026.8"
	],
	[
		"users.github.status",
		"users",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"users.github.authorize.start",
		"users",
		"operator.read",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"users.github.authorize.poll",
		"users",
		"operator.read",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"users.github.authorize.cancel",
		"users",
		"operator.read",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"users.github.disconnect",
		"users",
		"operator.read",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"sessions.github.options",
		"sessions-github",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"sessions.github.status",
		"sessions-github",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"sessions.github.confirm",
		"sessions-github",
		"operator.write",
		"2026.8",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"sessions.title.prepare",
		"sessions-title",
		"operator.write",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"users.mentionable",
		"users-mentionable",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"mentions.list",
		"mentions",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"mentions.dismiss",
		"mentions",
		"operator.read",
		"2026.8",
		{ startup: true }
	],
	[
		"transcripts.list",
		"transcripts",
		"operator.read",
		"2026.8"
	],
	[
		"transcripts.get",
		"transcripts",
		"operator.read",
		"2026.8"
	],
	[
		"models.authOrderSet",
		"models-auth-order",
		"operator.admin",
		"2026.8",
		CONTROL_PLANE_WRITE
	],
	[
		"canvas.document.view",
		"canvas",
		"operator.read",
		"2026.9"
	],
	[
		"plugins.controlUi.list",
		"plugins-control-ui",
		"operator.read",
		"2026.9"
	],
	[
		"plugins.controlUi.reload",
		"plugins-control-ui",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"plugins.controlUi.report",
		"plugins-control-ui",
		"operator.read",
		"2026.9"
	],
	[
		"plugins.controlUi.status",
		"plugins-control-ui",
		"operator.admin",
		"2026.9"
	],
	[
		"update.runs.get",
		"update",
		"operator.admin",
		"2026.9"
	],
	[
		"update.runs.list",
		"update",
		"operator.admin",
		"2026.9"
	],
	[
		"gateway.suspend.handoff",
		"suspend",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"transcripts.export",
		"transcripts",
		"operator.read",
		"2026.9"
	],
	[
		"transcripts.status",
		"transcripts",
		"operator.read",
		"2026.9"
	],
	[
		"update.report",
		"update",
		"operator.admin",
		"2026.9",
		{ controlPlaneWrite: true }
	],
	[
		"skills.workshop.read",
		"skills",
		"operator.read",
		"2026.9"
	],
	[
		"session.publicShare.set",
		"sessions-sharing",
		"operator.write",
		"2026.9"
	],
	[
		"claws.monitors",
		"claws-monitors",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"plugins.catalog.browse",
		"plugins",
		"operator.read",
		"2026.9"
	],
	[
		"plugins.catalog.categories",
		"plugins",
		"operator.read",
		"2026.9"
	],
	[
		"plugins.catalog.get",
		"plugins",
		"operator.read",
		"2026.9"
	],
	[
		"environments.prepare",
		"environments",
		"operator.admin",
		"2026.9",
		{
			startup: true,
			controlPlaneWrite: true
		}
	],
	[
		"models.authRefresh",
		"models-auth-status",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"models.authLogin",
		"models-auth-login",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"models.authSetApiKey",
		"models-auth-status",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"sessions.storage.status",
		"sessions-read",
		"operator.admin",
		"2026.9"
	],
	[
		"sessions.storage.run",
		"sessions-read",
		"operator.admin",
		"2026.9"
	],
	[
		"plugins.reload",
		"plugins-mutations",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"claws.packages.remove",
		"claws-packages",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"canvas.document.preview",
		"canvas",
		"operator.read",
		"2026.9"
	],
	[
		"computer.status",
		"computer",
		"operator.read",
		"2026.9"
	],
	[
		"computer.invoke",
		"computer",
		"operator.write",
		"2026.9"
	],
	[
		"sessions.activitySummary.ensure",
		"session-activity-summary",
		"operator.write",
		"2026.9"
	],
	[
		"controlUi.sessionPullRequests.checks",
		"control-ui",
		"operator.read",
		"2026.9"
	],
	[
		"diagnostics.cpuProfile",
		"diagnostics",
		"operator.admin",
		"2026.9"
	],
	[
		"talk.voice.get",
		"talk",
		"operator.talk",
		"2026.9"
	],
	[
		"talk.voice.set",
		"talk",
		"operator.talk",
		"2026.9"
	],
	[
		"talk.voice.complete",
		"talk",
		"operator.talk",
		"2026.9"
	],
	[
		"plugins.credentials.inspect",
		"plugins",
		"operator.admin",
		"2026.9"
	],
	[
		"plugins.skills.read",
		"plugins",
		"operator.read",
		"2026.9"
	],
	[
		"diagnostics.heapProfile",
		"diagnostics",
		"operator.admin",
		"2026.9"
	],
	[
		"desktop.release",
		"environments",
		"operator.admin",
		"2026.9",
		{ startup: true }
	],
	[
		"mcp.authLogin",
		"mcp-auth-login",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"environments.session.status",
		"environments",
		"operator.read",
		"2026.9"
	],
	[
		"environments.session.create",
		"environments",
		"operator.admin",
		"2026.9",
		{ controlPlaneWrite: true }
	],
	[
		"environments.session.destroy",
		"environments",
		"operator.admin",
		"2026.9",
		{ controlPlaneWrite: true }
	],
	[
		"environments.session.exec",
		"environments",
		"operator.admin",
		"2026.9"
	],
	[
		"sessions.setInvolvement",
		"sessions-mutations",
		"operator.read",
		"2026.9"
	],
	[
		"transcripts.summarize",
		"transcripts",
		"operator.write",
		"2026.9"
	],
	[
		"controlUi.linkPreview",
		"control-ui",
		"operator.read",
		"2026.9"
	],
	[
		"themes.list",
		"themes",
		"operator.read",
		"2026.9"
	],
	[
		"themes.get",
		"themes",
		"operator.read",
		"2026.9"
	],
	[
		"themes.set",
		"themes",
		"operator.write",
		"2026.9"
	],
	[
		"themes.import",
		"themes",
		"operator.write",
		"2026.9"
	],
	[
		"controlUi.githubDetail",
		"control-ui",
		"operator.read",
		"2026.9"
	],
	[
		"progressCard.refresh",
		"progress-card",
		"operator.write",
		"2026.9"
	],
	[
		"webSearch.status",
		"web-search",
		"operator.read",
		"2026.9"
	],
	[
		"webSearch.test",
		"web-search",
		"operator.admin",
		"2026.9"
	],
	[
		"sessions.providerReview.continue",
		"sessions-provider-review",
		"operator.write",
		"2026.9"
	],
	[
		"users.linkChannelIdentity",
		"users",
		"operator.admin",
		"2026.9"
	],
	[
		"users.unlinkChannelIdentity",
		"users",
		"operator.admin",
		"2026.9"
	],
	[
		"users.listChannelIdentities",
		"users",
		"operator.admin",
		"2026.9"
	],
	[
		"users.personalFile.get",
		"users",
		"operator.read",
		"2026.9"
	],
	[
		"users.personalFile.set",
		"users",
		"operator.read",
		"2026.9"
	],
	[
		"portal.session.list",
		"portals",
		"operator.write",
		"2026.9",
		{ sessionAccess: {
			mode: "write",
			allowOwnSessionScope: true,
			requiredTool: "portal"
		} }
	],
	[
		"portal.session.open",
		"portals",
		"operator.write",
		"2026.9",
		{ sessionAccess: {
			mode: "write",
			allowOwnSessionScope: true,
			requiredTool: "portal"
		} }
	],
	[
		"portal.session.close",
		"portals",
		"operator.write",
		"2026.9",
		{ sessionAccess: {
			mode: "write",
			allowOwnSessionScope: true,
			requiredTool: "portal"
		} }
	],
	[
		"cron.history",
		"cron",
		"operator.read",
		"2026.9"
	],
	[
		"presence.activity",
		"system",
		"operator.read",
		"2026.9"
	],
	[
		"presence.query",
		"presence",
		"operator.read",
		"2026.9"
	],
	[
		"users.merge",
		"users",
		"operator.admin",
		"2026.9"
	],
	[
		"gateway.stop.request",
		"restart",
		"operator.admin",
		"2026.9",
		CONTROL_PLANE_WRITE
	],
	[
		"diagnostics.heapSnapshot",
		"diagnostics",
		"operator.admin",
		"2026.9"
	]
];
//#endregion
//#region src/gateway/methods/descriptor.ts
/** Scope marker for methods that only authenticated node clients may call. */
const NODE_GATEWAY_METHOD_SCOPE = "node";
/** Scope marker for methods whose handler derives the required operator scope at runtime. */
const DYNAMIC_GATEWAY_METHOD_SCOPE = "dynamic";
//#endregion
//#region src/gateway/methods/core-method-policy.ts
const CORE_GATEWAY_METHOD_SPEC_LIST = CORE_GATEWAY_METHOD_SPECS.map(([name, family, scope, since, policy]) => Object.assign({
	name,
	scope,
	since,
	...family ? { family } : {}
}, policy));
const CORE_GATEWAY_METHOD_SPEC_BY_NAME = new Map(CORE_GATEWAY_METHOD_SPEC_LIST.map((spec) => [spec.name, spec]));
CORE_GATEWAY_METHOD_SPEC_LIST.filter((spec) => spec.startup === true).map((spec) => spec.name);
/** Looks up an operator-only core method scope, excluding node and dynamic methods. */
function resolveCoreOperatorGatewayMethodScope(method) {
	const scope = CORE_GATEWAY_METHOD_SPEC_BY_NAME.get(method)?.scope;
	return scope === "node" || scope === "dynamic" ? void 0 : scope;
}
/** Returns true for core methods reserved for authenticated node clients. */
function isCoreNodeGatewayMethod(method) {
	return CORE_GATEWAY_METHOD_SPEC_BY_NAME.get(method)?.scope === NODE_GATEWAY_METHOD_SCOPE;
}
/** Returns true for core methods whose required operator scope is resolved by the handler. */
function isDynamicOperatorGatewayMethod(method) {
	return CORE_GATEWAY_METHOD_SPEC_BY_NAME.get(method)?.scope === DYNAMIC_GATEWAY_METHOD_SCOPE;
}
/** Returns true when a method name has an explicit core policy entry. */
function isCoreGatewayMethodClassified(method) {
	return CORE_GATEWAY_METHOD_SPEC_BY_NAME.has(method);
}
//#endregion
//#region src/gateway/node-browser-proxy-policy.ts
function isForbiddenBrowserProxyMutation(params) {
	if (!params || typeof params !== "object") return false;
	const candidate = params;
	const method = normalizeOptionalString(candidate.method)?.toUpperCase();
	const path = normalizeOptionalString(candidate.path);
	if (!method || !path) return false;
	const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
	const normalizedPath = withLeadingSlash.length <= 1 ? withLeadingSlash : withLeadingSlash.replace(/\/+$/, "");
	if (method === "POST" && (normalizedPath === "/profiles/create" || normalizedPath === "/reset-profile")) return true;
	return method === "DELETE" && /^\/profiles\/[^/]+$/.test(normalizedPath);
}
//#endregion
//#region src/gateway/method-scopes.ts
/** Default scopes granted to CLI/operator clients when no narrower local policy is known. */
const CLI_DEFAULT_OPERATOR_SCOPES = [
	ADMIN_SCOPE,
	READ_SCOPE,
	WRITE_SCOPE,
	APPROVALS_SCOPE,
	QUESTIONS_SCOPE,
	PAIRING_SCOPE,
	TALK_SECRETS_SCOPE
];
function resolveScopedMethod(method) {
	const explicitScope = resolveCoreOperatorGatewayMethodScope(method);
	if (explicitScope) return explicitScope;
	const reservedScope = resolveReservedGatewayMethodScope(method);
	if (reservedScope) return reservedScope;
	const pluginScope = (getPluginRegistryForContext()?.gatewayMethodDescriptors?.find((descriptor) => descriptor.name === method))?.scope;
	return pluginScope === "node" || pluginScope === "dynamic" ? void 0 : pluginScope;
}
/** Returns true when a method is reserved for node-role clients instead of operators. */
function isNodeRoleMethod(method) {
	return isCoreNodeGatewayMethod(method);
}
function resolveSessionActionRegisteredScopes(params) {
	const record = asOptionalRecord(params);
	if (!record) return;
	const pluginId = normalizeOptionalString(record.pluginId);
	const actionId = normalizeOptionalString(record.actionId);
	if (!pluginId || !actionId) return;
	const registration = getPluginRegistryForContext()?.sessionActions?.find((entry) => entry.pluginId === pluginId && entry.action.id === actionId);
	if (!registration) return;
	const requiredScopes = registration.action.requiredScopes;
	return requiredScopes && requiredScopes.length > 0 ? [...requiredScopes] : [WRITE_SCOPE];
}
function resolveSessionActionLeastPrivilegeScopes(params) {
	const registeredScopes = resolveSessionActionRegisteredScopes(params);
	if (registeredScopes) return registeredScopes;
	const record = asOptionalRecord(params);
	if (record) {
		const pluginId = normalizeOptionalString(record.pluginId);
		const actionId = normalizeOptionalString(record.actionId);
		if (pluginId && actionId) return [...CLI_DEFAULT_OPERATOR_SCOPES];
	}
	return [WRITE_SCOPE];
}
function resolveDynamicLeastPrivilegeOperatorScopesForMethod(method, params) {
	if (method === "plugins.sessionAction") return resolveSessionActionLeastPrivilegeScopes(params);
	const record = asOptionalRecord(params);
	if (method === "agent") return isAgentSessionResetCommand(record?.message) ? [ADMIN_SCOPE] : [WRITE_SCOPE];
	if (method === "node.invoke") {
		const command = record?.command;
		if (isBrowserProxyNodeInvokeCommand(command) && isForbiddenBrowserProxyMutation(record?.params)) return [WRITE_SCOPE];
		return isAdminOnlyNodeInvokeCommand(command) ? [ADMIN_SCOPE] : [WRITE_SCOPE];
	}
	if (method === "talk.config") return record?.includeSecrets === true ? [READ_SCOPE, TALK_SECRETS_SCOPE] : [READ_SCOPE];
	if (method === "environments.list") {
		const runtimeId = record && "runtimeId" in record ? record.runtimeId : void 0;
		return typeof runtimeId === "string" && runtimeId ? [WRITE_SCOPE] : [READ_SCOPE];
	}
	if (method === "channels.pairing.approve") return record?.bootstrapCommandOwner === true ? [PAIRING_SCOPE, ADMIN_SCOPE] : [PAIRING_SCOPE];
	if (method === "fs.listDir") return [record && Object.hasOwn(record, "nodeId") ? ADMIN_SCOPE : WRITE_SCOPE];
	if (method === "sessions.patch" || method === "sessions.patchMany" || method === "sessions.create" || method === "sessions.dispatch" || method === "sessions.move") return [resolveDynamicSessionMutationRequiredScope(method, params) ?? "operator.write"];
	if (method === "sessions.delete") return [resolveDynamicSessionMutationRequiredScope(method, params) ?? "operator.admin"];
	return [WRITE_SCOPE];
}
/** Returns the narrowest known operator scopes needed to call a gateway method. */
function resolveLeastPrivilegeOperatorScopesForMethod(method, params) {
	if (isDynamicOperatorGatewayMethod(method)) return resolveDynamicLeastPrivilegeOperatorScopesForMethod(method, params);
	const requiredScope = resolveScopedMethod(method);
	if (requiredScope) return [requiredScope];
	return [];
}
/** Returns true when a method has any core, node, dynamic, reserved, or plugin scope policy. */
function isGatewayMethodClassified(method) {
	if (isNodeRoleMethod(method)) return true;
	if (isDynamicOperatorGatewayMethod(method)) return true;
	return isCoreGatewayMethodClassified(method) || resolveScopedMethod(method) !== void 0;
}
//#endregion
//#region src/gateway/operator-cli-message-input.ts
/** Operator message RPCs cannot preserve the source of an agent's shell report. */
function assertGatewayCliMessageContext(method, params) {
	const agentExec = process.env.OPENCLAW_SHELL === "exec";
	const subagentExec = process.env[SUBAGENT_EXEC_ENV_VAR] === "1";
	if (!agentExec && !subagentExec) return;
	const sessionMessage = [
		"sessions.send",
		"sessions.steer",
		"chat.send"
	].includes(method);
	if (subagentExec && sessionMessage) throw new Error("Subagent session messages must use the task completion path. Return your result or blocker in the child turn; do not use the CLI to contact other sessions.");
	if (!agentExec) return;
	const input = method === "sessions.create" ? asNullableRecord(params) : null;
	if (input && ([input.message, input.task].some((value) => typeof value === "string" && value.trim()) || Array.isArray(input.attachments) && input.attachments.length > 0) || sessionMessage || method === "agent") throw new Error(`Gateway ${method} from agent exec would lose inter-session attribution. Use the attributed session-messaging tool available to this run, or return the result through normal subagent completion. Do not retry through another CLI route or remove the exec marker.`);
}
//#endregion
//#region src/gateway/transport-error.ts
var GatewayTransportError = class extends Error {
	constructor(params) {
		super(params.message);
		this.name = "GatewayTransportError";
		this.kind = params.kind;
		this.connectionDetails = params.connectionDetails;
		if (params.code !== void 0) this.code = params.code;
		if (params.reason !== void 0) this.reason = params.reason;
		if (params.timeoutMs !== void 0) this.timeoutMs = params.timeoutMs;
		if (params.requestDispatched !== void 0) this.requestDispatched = params.requestDispatched;
	}
};
const DISPATCHED_REQUEST_OUTCOME_GUIDANCE = "The request was already sent to the gateway, so the operation may have been applied even though no response arrived; its outcome is unknown. Verify the current state (for example, re-run the equivalent read-only command) before retrying, especially for write actions.";
function createGatewayCloseTransportError(params) {
	const { code, connectionDetails, requestDispatched } = params;
	const reason = normalizeOptionalString(params.reason) || "no close reason";
	const hint = code === 1006 ? "abnormal closure (no close frame)" : code === 1e3 ? "normal closure" : "";
	let message = `gateway closed (${code}${hint ? ` ${hint}` : ""}): ${reason}\n${connectionDetails.message}`;
	if (code === 1006) message += `\n\nPossible causes:\n${requestDispatched ? "- Connection dropped without a close frame (check network and gateway load)" : "- Connection dropped without a close frame (retry; check network and gateway load)\n- Gateway not yet ready to accept connections (retry after a moment)\n- TLS mismatch (connecting with ws:// to a wss:// gateway, or vice versa)"}
- Gateway process stopped or became unreachable (confirm it is still running)
Run \`openclaw doctor\` for diagnostics.`;
	return new GatewayTransportError({
		kind: "closed",
		code,
		reason,
		connectionDetails,
		requestDispatched,
		message: requestDispatched ? `${message}\n\n${DISPATCHED_REQUEST_OUTCOME_GUIDANCE}` : message
	});
}
function createGatewayTimeoutTransportError(params) {
	const { timeoutMs, connectionDetails, requestDispatched } = params;
	const message = `gateway timeout after ${timeoutMs}ms\n${connectionDetails.message}`;
	return new GatewayTransportError({
		kind: "timeout",
		timeoutMs,
		connectionDetails,
		requestDispatched,
		message: requestDispatched ? `${message}\n\n${DISPATCHED_REQUEST_OUTCOME_GUIDANCE}` : message
	});
}
//#endregion
//#region src/gateway/call.ts
function firstGatewayErrorLine(message) {
	return message.split("\n", 1)[0]?.trim() || message;
}
const GATEWAY_UNREACHABLE_SOCKET_CODES = /* @__PURE__ */ new Set([
	"ECONNREFUSED",
	"ECONNRESET",
	"EHOSTUNREACH",
	"ENETUNREACH",
	"ENOTFOUND",
	"ETIMEDOUT"
]);
function isGatewayUnreachableSocketError(error) {
	const code = extractErrorCodeOrErrno(error);
	return code !== void 0 && GATEWAY_UNREACHABLE_SOCKET_CODES.has(code);
}
async function loadGatewayConfig() {
	return getRuntimeConfigSnapshot() ?? await readGatewayDispatchConfigWithShellEnvFallback();
}
async function stopGatewayClient(client) {
	try {
		await client.stopAndWait({ timeoutMs: 1e3 });
	} catch {
		client.stop();
	}
}
function resolveGatewayClientDisplayName(opts) {
	if (opts.clientDisplayName) return opts.clientDisplayName;
	const clientName = opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI;
	if ((opts.mode ?? GATEWAY_CLIENT_MODES.CLI) !== GATEWAY_CLIENT_MODES.BACKEND && clientName !== GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT) return;
	const method = opts.method.trim();
	return method ? `gateway:${method}` : "gateway:request";
}
/**
* Load config for a fully flag-addressed connection. Config only supplies
* gateway.remote.edgeAuth here, so an unreadable or invalid config degrades to
* empty rather than blocking a connection the flags already describe.
*/
async function loadGatewayConfigForExplicitConnection() {
	try {
		return await loadGatewayConfig();
	} catch {
		return {};
	}
}
function resolveGatewayConfigPath(env) {
	return resolveConfigPath(env, resolveStateDir(env));
}
function buildGatewayConnectionDetails(options = {}) {
	return buildGatewayConnectionDetailsWithResolvers(options, {
		getRuntimeConfig: readGatewayDispatchConfig,
		resolveConfigPath: resolveGatewayConfigPath,
		resolveGatewayPort
	});
}
function resolveGatewayCallAuth(config) {
	return resolveGatewayAuth({
		authConfig: config.gateway?.auth,
		env: process.env,
		tailscaleMode: config.gateway?.tailscale?.mode
	});
}
async function ensureGatewayCallCanAuthenticate(params) {
	const resolvedAuth = resolveGatewayCallAuth(params.context.config);
	const authMode = resolvedAuth.mode;
	if (authMode !== "token" && authMode !== "password") return;
	if (params.token || params.password || params.opts.approvalRuntimeToken) return;
	if (resolvedAuth.allowTailscale) return;
	if ((params.storedAuth === void 0 ? await loadStoredOperatorDeviceAuthToken(params.deviceIdentity, params.deviceAuthScope, params.opts.sharedStateMode) : params.storedAuth)?.token) return;
	throw new GatewayCredentialsRequiredError({
		method: params.opts.method,
		configPath: params.context.configPath
	});
}
function resolveGatewayCallTimeout(timeoutValue) {
	const resolvedHandshakeTimeoutMs = Boolean(process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS) || Boolean(isVitestRuntimeEnv() && process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS) ? resolvePreauthHandshakeTimeoutMs() : void 0;
	const defaultTimeoutMs = typeof resolvedHandshakeTimeoutMs === "number" && resolvedHandshakeTimeoutMs > 1e4 ? resolvedHandshakeTimeoutMs : 1e4;
	const explicitTimeoutMs = typeof timeoutValue === "number" && Number.isFinite(timeoutValue) ? timeoutValue : void 0;
	const startupTimeoutMs = explicitTimeoutMs ?? defaultTimeoutMs;
	const timeoutMs = timeoutValue === null ? null : explicitTimeoutMs ?? defaultTimeoutMs;
	return {
		timeoutMs,
		startupTimeoutMs,
		safeTimerTimeoutMs: resolveSafeTimeoutDelayMs(timeoutMs ?? startupTimeoutMs)
	};
}
async function resolveGatewayCallContext(opts) {
	const explicitAuth = resolveExplicitGatewayAuth({
		token: opts.token,
		password: opts.password
	});
	const urlOverride = resolveGatewayUrlOverride({
		gatewayUrl: opts.url,
		env: process.env,
		ignoreEnvUrlOverride: opts.ignoreEnvUrlOverride,
		localPortOverride: opts.localPortOverride
	}).url;
	const canSkipConfigLoad = canSkipGatewayConfigLoad({
		config: opts.config,
		urlOverride,
		explicitAuth
	});
	const explicitConnection = isExplicitGatewayConnection({
		config: opts.config,
		urlOverride,
		explicitAuth
	});
	const config = opts.config ?? (canSkipConfigLoad ? {} : explicitConnection ? await loadGatewayConfigForExplicitConnection() : await loadGatewayConfig());
	return {
		config,
		configPath: opts.configPath ?? resolveGatewayConfigPath(process.env),
		isRemoteMode: opts.localPortOverride === void 0 && config.gateway?.mode === "remote",
		explicitAuth
	};
}
function ensureRemoteModeUrlConfigured(params) {
	if (!params.context.isRemoteMode || params.urlOverrideSource || trimToUndefined(params.context.config.gateway?.remote?.url)) return;
	throw new Error([
		"gateway remote mode misconfigured: gateway.remote.url missing",
		`Config: ${params.context.configPath}`,
		"Fix: set gateway.remote.url, or set gateway.mode=local."
	].join("\n"));
}
/** Wrap raw socket-level connect failures (ECONNREFUSED etc.) into one actionable message. */
function createGatewayUnreachableTransportError(params) {
	const code = extractErrorCodeOrErrno(params.cause);
	return new GatewayTransportError({
		kind: "closed",
		reason: firstGatewayErrorLine(params.cause.message),
		connectionDetails: params.connectionDetails,
		message: [
			`Gateway not reachable at ${projectGatewayUrlForDiagnostics(params.connectionDetails.url)}${code ? ` (${code})` : ""}.`,
			"Start it with `openclaw gateway run` or check `openclaw gateway status`.",
			params.connectionDetails.message
		].join("\n")
	});
}
function createGatewayRequestAbortError(method) {
	return createAbortError(`gateway request aborted for ${method}`);
}
function ensureGatewaySupportsRequiredMethods(params) {
	const requiredMethods = Array.isArray(params.requiredMethods) ? params.requiredMethods.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : [];
	if (requiredMethods.length === 0) return;
	const supportedMethods = new Set((Array.isArray(params.methods) ? params.methods : []).map((entry) => entry.trim()).filter((entry) => entry.length > 0));
	for (const method of requiredMethods) {
		if (supportedMethods.has(method)) continue;
		throw new Error([`active gateway does not support required method "${method}" for "${params.attemptedMethod}".`, "Update or restart the active gateway and try again."].join(" "));
	}
}
function ensureGatewaySupportsRequiredCapabilities(params) {
	const required = (params.requiredCapabilities ?? []).map((entry) => entry.trim()).filter(Boolean);
	if (required.length === 0) return;
	const supported = new Set((params.capabilities ?? []).map((entry) => entry.trim()).filter(Boolean));
	for (const capability of required) if (!supported.has(capability)) throw new Error(`active gateway does not support required capability "${capability}" for "${params.attemptedMethod}". Update or restart the active gateway and try again.`);
}
function isRequiredAgentRuntimeIdentityConnectError(err) {
	return err.message.includes("gateway rejected required agent runtime identity auth field; refusing to retry without it");
}
function isAllowlistedGatewayConnectRequestError(err) {
	if (err.name !== "GatewayClientRequestError") return false;
	return readConnectErrorDetailCode(err.details) === ConnectErrorDetailCodes.AUTH_RATE_LIMITED;
}
async function executeGatewayRequestWithScopes(params) {
	const { opts, scopes, url, token, password, edgeAuthHeaders, tlsFingerprint, timeoutMs, startupTimeoutMs, safeTimerTimeoutMs, deviceIdentity, deviceAuthScope, sshTunnel, storedAuth, surfaceGatewayClientRequestErrors } = params;
	return await new Promise((resolve, reject) => {
		if (opts.signal?.aborted) {
			reject(createGatewayRequestAbortError(opts.method));
			return;
		}
		let settled = false;
		let ignoreClose = false;
		let timer;
		const startAbort = new AbortController();
		let primaryRequestStarted = false;
		let suppressedPreHelloCleanCloses = 0;
		const cleanup = () => {
			startAbort.abort();
			if (abortHandler) opts.signal?.removeEventListener("abort", abortHandler);
			if (timer) clearTimeout(timer);
		};
		const stopClientThenSettle = (activeClient, err, value) => {
			const complete = () => {
				if (err) reject(err);
				else resolve(value);
			};
			if (!activeClient) {
				complete();
				return;
			}
			stopGatewayClient(activeClient).finally(complete);
		};
		const stop = (err, value) => {
			if (settled) return;
			settled = true;
			cleanup();
			stopClientThenSettle(client, err, value);
		};
		const abortHandler = () => {
			if (settled) return;
			ignoreClose = true;
			settled = true;
			cleanup();
			const err = createGatewayRequestAbortError(opts.method);
			const activeClient = client;
			const stopAfterAbortHook = () => stopClientThenSettle(activeClient, err);
			if (!activeClient || !opts.onSignalAbort || !primaryRequestStarted) {
				stopAfterAbortHook();
				return;
			}
			const request = activeClient.request.bind(activeClient);
			Promise.resolve().then(() => opts.onSignalAbort?.(request)).catch(() => {}).finally(stopAfterAbortHook);
		};
		opts.signal?.addEventListener("abort", abortHandler, { once: true });
		const client = new GatewayClient({
			url,
			sshTunnel,
			token,
			password,
			edgeAuthHeaders,
			tlsFingerprint,
			preauthHandshakeTimeoutMs: opts.preauthHandshakeTimeoutMs,
			instanceId: opts.instanceId ?? randomUUID(),
			clientName: opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
			clientDisplayName: resolveGatewayClientDisplayName(opts),
			clientVersion: opts.clientVersion ?? VERSION,
			caps: opts.caps,
			platform: opts.platform,
			mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI,
			...opts.approvalRuntimeToken ? { approvalRuntimeToken: opts.approvalRuntimeToken } : {},
			...opts.agentRuntimeIdentityToken ? { agentRuntimeIdentityToken: opts.agentRuntimeIdentityToken } : {},
			role: "operator",
			...Array.isArray(scopes) ? { scopes } : {},
			deviceIdentity,
			...deviceAuthScope ? { deviceAuthScope } : {},
			...storedAuth ? { preparedDeviceAuth: storedAuth } : {},
			...opts.sharedStateMode ? { sharedStateMode: opts.sharedStateMode } : {},
			minProtocol: opts.minProtocol ?? 4,
			maxProtocol: opts.maxProtocol ?? 4,
			onHelloOk: (hello) => {
				if (timeoutMs === null && timer) {
					clearTimeout(timer);
					timer = void 0;
				}
				try {
					opts.onHelloOk?.(hello);
				} catch {}
				if (settled) return;
				(async () => {
					try {
						ensureGatewaySupportsRequiredMethods({
							requiredMethods: opts.requiredMethods,
							methods: hello.features?.methods,
							attemptedMethod: opts.method
						});
						ensureGatewaySupportsRequiredCapabilities({
							requiredCapabilities: opts.requiredCapabilities,
							capabilities: hello.features?.capabilities,
							attemptedMethod: opts.method
						});
						const activeClient = client;
						if (!activeClient) throw new Error("gateway client not initialized");
						opts.assertDispatchCurrent?.();
						primaryRequestStarted = true;
						const result = await activeClient.request(opts.method, opts.params, {
							expectFinal: opts.expectFinal,
							timeoutMs: opts.timeoutMs,
							signal: opts.signal,
							onAccepted: opts.onAccepted
						});
						ignoreClose = true;
						stop(void 0, result);
					} catch (err) {
						ignoreClose = true;
						stop(err);
					}
				})();
			},
			onClose: (code, reason, info) => {
				if (settled || ignoreClose) return;
				if (info?.connectError) {
					ignoreClose = true;
					stop(isGatewayUnreachableSocketError(info.connectError) ? createGatewayUnreachableTransportError({
						cause: info.connectError,
						connectionDetails: params.connectionDetails
					}) : info.connectError);
					return;
				}
				if (!primaryRequestStarted && info?.transientPreHelloCleanClose === true && suppressedPreHelloCleanCloses < 1) {
					suppressedPreHelloCleanCloses += 1;
					return;
				}
				ignoreClose = true;
				stop(createGatewayCloseTransportError({
					code,
					reason,
					connectionDetails: params.connectionDetails,
					requestDispatched: primaryRequestStarted
				}));
			},
			onConnectError: (err) => {
				const gatewayClientRequestError = err.name === "GatewayClientRequestError";
				const isAgentRuntimeIdentityConnectError = Boolean(opts.agentRuntimeIdentityToken) && isRequiredAgentRuntimeIdentityConnectError(err);
				const shouldSurface = isGatewayConnectAssemblyError(err) || isAgentRuntimeIdentityConnectError || isAllowlistedGatewayConnectRequestError(err) || surfaceGatewayClientRequestErrors && gatewayClientRequestError;
				if (settled || !shouldSurface) return;
				ignoreClose = true;
				stop(err);
			}
		});
		const wrapperTimeoutMs = timeoutMs ?? startupTimeoutMs;
		timer = setTimeout(() => {
			ignoreClose = true;
			stop(createGatewayTimeoutTransportError({
				timeoutMs: wrapperTimeoutMs,
				connectionDetails: params.connectionDetails,
				requestDispatched: primaryRequestStarted
			}));
		}, safeTimerTimeoutMs);
		startGatewayClientWhenEventLoopReady(client, {
			timeoutMs: safeTimerTimeoutMs,
			signal: startAbort.signal
		}).then((readiness) => {
			if (settled || readiness.ready || readiness.aborted) return;
			ignoreClose = true;
			stop(createGatewayTimeoutTransportError({
				timeoutMs: startupTimeoutMs,
				connectionDetails: params.connectionDetails,
				requestDispatched: false
			}));
		}).catch((err) => {
			if (settled) return;
			ignoreClose = true;
			stop(err instanceof Error ? err : new Error(String(err)));
		});
	});
}
async function callGatewayWithScopes(opts, scopes, localCliAbort = false) {
	const context = await resolveGatewayCallContext(opts);
	const { timeoutMs, startupTimeoutMs, safeTimerTimeoutMs } = resolveGatewayCallTimeout(opts.timeoutMs);
	const urlOverrideSource = resolveGatewayUrlOverride({
		gatewayUrl: opts.url,
		env: process.env,
		ignoreEnvUrlOverride: opts.ignoreEnvUrlOverride,
		localPortOverride: opts.localPortOverride
	}).source;
	if (opts.requireLocalBackendSharedAuth && (urlOverrideSource || context.isRemoteMode)) throw new GatewayLocalBackendSharedAuthUnavailableError("local backend shared auth is limited to the configured local gateway");
	const requestedStoredDeviceAuth = opts.useStoredDeviceAuth === true;
	const hasExplicitAuth = Boolean(context.explicitAuth.token || context.explicitAuth.password);
	const useStoredDeviceAuth = requestedStoredDeviceAuth && !hasExplicitAuth;
	const bootstrap = await resolveGatewayClientBootstrap({
		config: context.config,
		gatewayUrl: opts.url,
		explicitAuth: context.explicitAuth,
		env: process.env,
		configPath: context.configPath,
		ignoreEnvUrlOverride: opts.localPortOverride !== void 0 || opts.ignoreEnvUrlOverride === true || opts.serviceTargetUrl !== void 0,
		localPortOverride: opts.localPortOverride,
		explicitTlsFingerprint: opts.tlsFingerprint,
		skipImplicitAuth: useStoredDeviceAuth || opts.skipImplicitAuth === true,
		...useStoredDeviceAuth ? {} : { overrideAuthErrorHint: "Fix: pass --token or --password with --url (or gatewayToken in tools)." },
		buildConnectionDetails: buildGatewayConnectionDetails,
		...opts.serviceTargetUrl ? { serviceTargetUrl: opts.serviceTargetUrl } : {}
	});
	ensureRemoteModeUrlConfigured({
		context,
		urlOverrideSource: bootstrap.urlOverrideSource
	});
	const connectionDetails = bootstrap.connectionDetails;
	const url = bootstrap.url;
	if (opts.expectUrl !== void 0 && url !== opts.expectUrl) throw new Error("Gateway destination changed. Refresh the selected Gateway before retrying.");
	const deviceAuthScope = bootstrap.deviceAuthScope;
	const token = useStoredDeviceAuth ? void 0 : bootstrap.auth.token;
	const password = useStoredDeviceAuth ? void 0 : bootstrap.auth.password;
	const { clientOptions, omitDeviceIdentity, deviceIdentity } = resolveGatewayCallDeviceAuth({
		opts,
		url,
		authMode: resolveGatewayCallAuth(context.config).mode,
		isImplicitLocalTarget: !urlOverrideSource && !context.isRemoteMode,
		token,
		password
	});
	const connectionOpts = {
		...opts,
		...clientOptions
	};
	let storedAuth = connectionOpts.preparedDeviceAuth;
	if (useStoredDeviceAuth) {
		storedAuth ??= await loadStoredOperatorDeviceAuthToken(deviceIdentity, deviceAuthScope, connectionOpts.sharedStateMode);
		if (!storedAuth?.token && deviceAuthScope) throw new GatewayStoredDeviceAuthUnavailableError(["No stored device auth for this gateway origin.", `Run \`openclaw tui${bootstrap.sshTunnel ? "" : ` --url ${projectGatewayUrlForDiagnostics(url)}`}\` to send a pairing request, approve it in that gateway's Control UI (Settings -> Devices) or run \`openclaw devices approve --latest\` on the gateway host, then retry.`].join("\n"));
	}
	const tlsFingerprint = bootstrap.tlsFingerprint;
	const edgeAuthConfig = normalizeEdgeAuthHeadersConfig(gatewayEdgeAuthValueForTarget({
		config: context.config,
		targetUrl: url
	}));
	const edgeAuthHeaders = await resolveEdgeAuthHeaders({
		config: context.config,
		value: edgeAuthConfig,
		targetUrl: url,
		env: process.env
	});
	if (useStoredDeviceAuth) {
		if (!storedAuth?.token) throw new GatewayCredentialsRequiredError({
			method: connectionOpts.method,
			configPath: context.configPath
		});
		if (Array.isArray(connectionOpts.requiredStoredDeviceAuthScopes) && !roleScopesAllow({
			role: "operator",
			requestedScopes: connectionOpts.requiredStoredDeviceAuthScopes,
			allowedScopes: storedAuth.scopes
		})) throw new GatewayStoredDeviceAuthUnavailableError("stored device auth does not grant the required operator scopes");
	}
	await ensureGatewayCallCanAuthenticate({
		opts: connectionOpts,
		context,
		token,
		password,
		deviceIdentity,
		deviceAuthScope,
		storedAuth
	});
	try {
		await prepareGatewayClientDeviceAuth({
			url,
			token,
			password,
			edgeAuthHeaders,
			tlsFingerprint,
			deviceIdentity,
			deviceAuthScope,
			sharedStateMode: connectionOpts.sharedStateMode,
			preparedDeviceAuth: storedAuth ?? void 0,
			approvalRuntimeToken: connectionOpts.approvalRuntimeToken,
			agentRuntimeIdentityToken: connectionOpts.agentRuntimeIdentityToken
		}, connectionOpts.signal);
	} catch (error) {
		if (connectionOpts.signal?.aborted) throw createGatewayRequestAbortError(connectionOpts.method);
		throw error;
	}
	return await executeGatewayRequestWithScopes({
		opts: connectionOpts,
		scopes: requestedStoredDeviceAuth && hasExplicitAuth && connectionOpts.requiredStoredDeviceAuthScopes ? connectionOpts.requiredStoredDeviceAuthScopes : useStoredDeviceAuth ? void 0 : localCliAbort && omitDeviceIdentity && !deviceIdentity ? [ADMIN_SCOPE] : scopes,
		url,
		token,
		password,
		edgeAuthHeaders,
		tlsFingerprint,
		timeoutMs,
		startupTimeoutMs,
		safeTimerTimeoutMs,
		connectionDetails,
		deviceIdentity,
		deviceAuthScope,
		sshTunnel: bootstrap.sshTunnel,
		...storedAuth ? { storedAuth } : {},
		surfaceGatewayClientRequestErrors: useStoredDeviceAuth || connectionOpts.requireLocalBackendSharedAuth === true || Boolean(connectionOpts.agentRuntimeIdentityToken)
	});
}
function shouldEscalateSessionCreateCwdScope(params) {
	if (params.opts.method !== "sessions.create" || !isRecord(params.opts.params) || !normalizeOptionalString(params.opts.params.cwd) || params.scopes.length !== 1 || params.scopes[0] !== "operator.write") return false;
	const missingScope = readMissingScopeErrorDetails((isRecord(params.error) ? params.error : void 0)?.details);
	return missingScope?.missingScope === "operator.admin" && missingScope.requiredScopes.includes("operator.admin");
}
async function callGatewayWithScopeEscalation(opts, scopes) {
	try {
		return await callGatewayWithScopes(opts, scopes);
	} catch (error) {
		if (!shouldEscalateSessionCreateCwdScope({
			opts,
			scopes,
			error
		})) throw error;
		return await callGatewayWithScopes(opts, [ADMIN_SCOPE]);
	}
}
async function callGatewayCli(opts) {
	assertGatewayCliMessageContext(opts.method, opts.params);
	if (Array.isArray(opts.scopes)) return await callGatewayWithScopes(opts, opts.scopes);
	const scopes = isGatewayMethodClassified(opts.method) ? resolveLeastPrivilegeOperatorScopesForMethod(opts.method, opts.params) : CLI_DEFAULT_OPERATOR_SCOPES;
	if (opts.method === "chat.abort" || opts.method === "sessions.abort") return await callGatewayWithScopes(opts, scopes, true);
	return await callGatewayWithScopeEscalation(opts, scopes);
}
async function callGatewayLeastPrivilege(opts) {
	return await callGatewayWithScopeEscalation(opts, resolveLeastPrivilegeOperatorScopesForMethod(opts.method, opts.params));
}
async function callGateway(opts) {
	const callerMode = opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND;
	const callerName = opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
	if (callerMode === GATEWAY_CLIENT_MODES.CLI || callerName === GATEWAY_CLIENT_NAMES.CLI) return await callGatewayCli(opts);
	if (Array.isArray(opts.scopes)) return await callGatewayWithScopes({
		...opts,
		mode: callerMode,
		clientName: callerName
	}, opts.scopes);
	return await callGatewayLeastPrivilege({
		...opts,
		mode: callerMode,
		clientName: callerName
	});
}
//#endregion
export { callGateway };