UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,257 lines 63.4 kB
import { r as stripAnsi } from "./ansi-DrXAcdMD.js";
import { r as asNullableRecord } from "./record-coerce-DItp3I4t.js";
import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { o as safeRealpathSync } from "./path-safety-Bi0ppMWC.js";
import "./utils-P__uGsPB.js";
import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js";
import { o as resolveCompatibilityHostVersion, t as VERSION } from "./version-v1kuAkGj.js";
import { a as loadInstalledPluginIndex } from "./installed-plugin-index-wrDsjyMD.js";
import { l as normalizePluginsConfig, u as resolveEffectiveEnableState } from "./config-state-BkU1frVq.js";
import { l as readLegacyNpmPluginDeclaration } from "./discovery-D_VDsZuY.js";
import { o as parseRegistryNpmSpec, t as compareOpenClawReleaseVersions } from "./npm-registry-spec-CM_p1_uq.js";
import { a as resolveDefaultPluginExtensionsDir, c as resolvePluginInstallDir, d as resolvePluginNpmPackageDir, s as resolveDefaultPluginNpmDir } from "./install-paths-ChnxikBv.js";
import "./path-safety-BT3PFs5V.js";
import { t as parseClawHubPluginSpec } from "./clawhub-spec-Er3Np6VI.js";
import { A as installWithSourceFallback, D as resolveOfficialExternalProviderPluginIdsForEnv, E as resolveOfficialExternalProviderPluginIds, F as resolvePluginInstallSources, L as isUnavailableClawHubTarget, M as resolveClawHubInstallSpecsForUpdateChannel, O as resolveOfficialExternalWebProviderContractPluginIdsForEnv, P as resolveNpmInstallSpecsForUpdateChannel, T as resolveOfficialExternalProviderContractPluginIds, k as installWithChannelFallback, p as listOfficialExternalPluginCatalogEntries, s as isExternallyDistributedPlugin, v as resolveOfficialExternalPluginId, x as resolveOfficialExternalPluginLabel, y as resolveOfficialExternalPluginInstall } from "./official-external-plugin-catalog-Dzu7dwBN.js";
import { r as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-SXWwf_BU.js";
import { c as normalizeUpdateChannel, d as resolveRegistryUpdateChannel } from "./update-channels-BcfztK6k.js";
import { n as isUnavailableNpmTarget } from "./install-types-DY_kphq4.js";
import { t as listDoctorConfiguredChannelIds } from "./configured-channel-ids-BPL8Xd0F.js";
import { c as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-BbV7X6pV.js";
import { u as resolveConfiguredChannelPresencePolicy } from "./channel-presence-policy-_eDSlojg.js";
import { t as ManagedPluginLifecycleError } from "./management-lifecycle-error-BlJhejU6.js";
import { n as resolveWebSearchInstallCatalogEntriesForEnv, r as resolveWebSearchInstallCatalogEntry } from "./web-search-install-catalog-mZmYPP1I.js";
import { t as PLUGIN_CAPABILITY_CONSENT_REQUIRED } from "./capability-consent-error-details-D4ZVP4Ge.js";
import { l as buildNpmResolutionInstallFields, p as resolveNpmInstallRecordSpec, r as removePluginInstallRecordFromRecords, s as writePersistedInstalledPluginIndexInstallRecords } from "./installed-plugin-index-records-C06Xozeq.js";
import { C as collectConfiguredMemoryEmbeddingProviderIds } from "./gateway-startup-plugin-config-Bq9ZdScF.js";
import { t as collectConfiguredSpeechProviderIds } from "./gateway-startup-speech-providers-C1sqNf-9.js";
import "./gateway-startup-plugin-ids-D2iVoeEm.js";
import "./channel-plugin-ids-DIyH-LxI.js";
import { t as withPluginLifecycleLease } from "./plugin-lifecycle-lease-BtAFH872.js";
import { n as resolveProviderInstallCatalogEntries } from "./provider-install-catalog-BzkhdO6-.js";
import { r as prepareManagedPluginArtifactConsentHandler, t as capturePluginCapabilityConsentHandlerErrors } from "./capability-consent-DAQVy0Fy.js";
import { r as installPluginFromNpmSpec } from "./install-BezM8CtS.js";
import { t as buildClawHubPluginInstallRecordFields } from "./clawhub-install-records-Dy2deHKG.js";
import { t as installPluginFromClawHub } from "./clawhub-DgZbJqg3.js";
import { r as listRawChannelPluginCatalogEntries } from "./catalog-BiRdaEi8.js";
import { n as expectedIntegrityForUpdate } from "./package-update-utils-Bt0FtB_O.js";
import { n as updateNpmInstalledPlugins, r as isClawHubTrustSkippedOutcome } from "./update-BAjyWYBA.js";
import { i as collectConfiguredRuntimePluginIds, n as VERSION_BOUND_RUNTIME_PLUGIN_IDS, t as CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES } from "./configured-runtime-plugin-installs-DWgjbBZ4.js";
import { c as isLegacyPackageUpdateDoctorPass, f as shouldDeferConfiguredPluginInstallRepair } from "./update-phase-J3w3q1-f.js";
import { n as isPayloadMissing } from "./payload-verification-BZuECmYT.js";
import { n as collectConfiguredModelProviderSelectionIds, r as collectConfiguredProviderSelectionIds, t as collectConfiguredMediaProviderSelectionIds } from "./configured-provider-selection-ids-ClDBppbl.js";
import { existsSync } from "node:fs";
import path from "node:path";
import { rm } from "node:fs/promises";
//#region src/commands/doctor/shared/configured-provider-plugin-ids.ts
/** Lists official external provider plugins without loading installed plugin registries. */
function collectConfiguredOfficialProviderPluginIds(params) {
	const configuredProviderIds = collectConfiguredModelProviderSelectionIds(params.cfg);
	const configuredMediaProviderIds = collectConfiguredMediaProviderSelectionIds(params.cfg);
	const pluginIds = new Set(resolveOfficialExternalProviderPluginIds({ providerIds: configuredProviderIds }));
	for (const pluginId of resolveOfficialExternalProviderPluginIdsForEnv(params.env ?? process.env)) pluginIds.add(pluginId);
	for (const pluginId of resolveOfficialExternalProviderContractPluginIds({
		contract: "mediaUnderstandingProviders",
		providerIds: configuredMediaProviderIds
	})) pluginIds.add(pluginId);
	for (const pluginId of resolveOfficialExternalProviderContractPluginIds({
		contract: "speechProviders",
		providerIds: configuredProviderIds
	})) pluginIds.add(pluginId);
	return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/commands/doctor/shared/configured-provider-plugin-installs.ts
/** Lists external provider plugins implied by configured auth profiles and model refs. */
function collectConfiguredProviderPluginIds(params) {
	const selectedProviderIds = collectConfiguredProviderSelectionIds(params.cfg);
	const pluginIds = new Set(collectConfiguredOfficialProviderPluginIds(params));
	for (const entry of resolveProviderInstallCatalogEntries({
		config: params.cfg,
		env: params.env,
		includeUntrustedWorkspacePlugins: false
	})) if ([entry.providerId, ...entry.providerAliases ?? []].some((providerId) => selectedProviderIds.has(providerId.toLowerCase()))) pluginIds.add(entry.pluginId);
	return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.ids.ts
function addConfiguredPluginId(ids, value) {
	if (typeof value !== "string") return;
	const pluginId = value.trim();
	if (pluginId) ids.add(pluginId);
}
function addConfiguredAgentRuntimePluginIds(ids, cfg) {
	for (const runtime of collectConfiguredRuntimePluginIds(cfg)) addConfiguredPluginId(ids, runtime);
}
function addConfiguredMemoryEmbeddingProviderPluginIds(ids, cfg) {
	const configuredProviderIds = collectConfiguredMemoryEmbeddingProviderIds(cfg);
	if (configuredProviderIds.size === 0) return;
	for (const pluginId of resolveOfficialExternalProviderContractPluginIds({
		contract: "embeddingProviders",
		providerIds: configuredProviderIds
	})) ids.add(pluginId);
}
function addConfiguredSpeechProviderPluginIds(ids, cfg) {
	for (const pluginId of resolveOfficialExternalProviderContractPluginIds({
		contract: "speechProviders",
		providerIds: collectConfiguredSpeechProviderIds(cfg)
	})) ids.add(pluginId);
}
function addConfiguredWebFetchProviderPluginIds(ids, cfg) {
	const webFetch = cfg.tools?.web?.fetch;
	if (webFetch?.enabled === false) return;
	const providerId = normalizeOptionalLowercaseString(webFetch?.provider);
	if (!providerId) return;
	for (const pluginId of resolveOfficialExternalProviderContractPluginIds({
		contract: "webFetchProviders",
		providerIds: /* @__PURE__ */ new Set([providerId])
	})) ids.add(pluginId);
}
function addEnvWebFetchProviderPluginIds(ids, cfg, env) {
	if (cfg.tools?.web?.fetch?.enabled === false) return;
	for (const pluginId of resolveOfficialExternalWebProviderContractPluginIdsForEnv({
		contract: "webFetchProviders",
		env: env ?? process.env
	})) ids.add(pluginId);
}
function collectConfiguredPluginIds(cfg, env) {
	const ids = /* @__PURE__ */ new Set();
	const plugins = asNullableRecord(cfg.plugins);
	if (plugins?.enabled === false) return ids;
	const entries = asNullableRecord(plugins?.entries);
	for (const [pluginId, entry] of Object.entries(entries ?? {})) {
		if (asNullableRecord(entry)?.enabled === false) continue;
		addConfiguredPluginId(ids, pluginId);
	}
	const searchProvider = cfg.tools?.web?.search?.provider;
	if (cfg.tools?.web?.search?.enabled !== false && typeof searchProvider === "string") {
		const installEntry = resolveWebSearchInstallCatalogEntry({ providerId: searchProvider });
		if (installEntry?.pluginId) ids.add(installEntry.pluginId);
	}
	if (cfg.tools?.web?.search?.enabled !== false) for (const entry of resolveWebSearchInstallCatalogEntriesForEnv(env ?? process.env)) ids.add(entry.pluginId);
	addConfiguredAgentRuntimePluginIds(ids, cfg);
	for (const pluginId of collectConfiguredProviderPluginIds({
		cfg,
		env
	})) ids.add(pluginId);
	addConfiguredMemoryEmbeddingProviderPluginIds(ids, cfg);
	addConfiguredSpeechProviderPluginIds(ids, cfg);
	addConfiguredWebFetchProviderPluginIds(ids, cfg);
	addEnvWebFetchProviderPluginIds(ids, cfg, env);
	return ids;
}
function collectBlockedPluginIds(cfg) {
	const ids = /* @__PURE__ */ new Set();
	const deny = cfg.plugins?.deny;
	if (Array.isArray(deny)) {
		for (const pluginId of deny) if (typeof pluginId === "string" && pluginId.trim()) ids.add(pluginId.trim());
	}
	const entries = asNullableRecord(cfg.plugins?.entries);
	for (const [pluginId, entry] of Object.entries(entries ?? {})) if (pluginId.trim() && asNullableRecord(entry)?.enabled === false) ids.add(pluginId.trim());
	return ids;
}
function collectConfiguredChannelIds(cfg, env) {
	if (asNullableRecord(cfg.plugins)?.enabled === false) return /* @__PURE__ */ new Set();
	const candidateChannelIds = listRawChannelPluginCatalogEntries({
		env,
		excludeWorkspace: true
	}).map((entry) => entry.id);
	return new Set(listDoctorConfiguredChannelIds(cfg, {
		configEntryPolicy: "meaningful",
		env: env ?? process.env,
		candidateChannelIds,
		skipWhenPluginsDisabled: true,
		excludeExplicitlyDisabled: true
	}));
}
function collectEffectiveConfiguredChannelOwnerPluginIds(params) {
	const owners = /* @__PURE__ */ new Map();
	const configuredChannelIds = new Set([...params.configuredChannelIds].map((channelId) => normalizeOptionalLowercaseString(channelId)).filter((channelId) => Boolean(channelId)));
	if (configuredChannelIds.size === 0) return owners;
	for (const entry of resolveConfiguredChannelPresencePolicy({
		config: params.cfg,
		env: params.env,
		includePersistedAuthState: false,
		manifestRecords: params.snapshot.plugins
	})) {
		if (!entry.effective || !configuredChannelIds.has(entry.channelId)) continue;
		const pluginIds = new Set(entry.pluginIds);
		if (pluginIds.size > 0) owners.set(entry.channelId, pluginIds);
	}
	return owners;
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.candidates.ts
/** Keep doctor diagnostics and actual package repair on the same discovery snapshot. */
async function resolveConfiguredPluginInstallContext(params) {
	const realpathCache = /* @__PURE__ */ new Map();
	const resolvePathIdentity = (value) => {
		const resolved = path.resolve(resolveUserPath(value, params.env));
		return safeRealpathSync(resolved, realpathCache) ?? resolved;
	};
	const snapshot = loadManifestMetadataSnapshot({
		config: params.cfg,
		env: params.env
	});
	const currentBundledPlugins = loadInstalledPluginIndex({
		config: params.cfg,
		env: params.env,
		installRecords: {}
	}).plugins.filter((plugin) => plugin.origin === "bundled");
	const knownIds = /* @__PURE__ */ new Set([...snapshot.plugins.filter((plugin) => plugin.origin !== "bundled").map((plugin) => plugin.id), ...currentBundledPlugins.map((plugin) => plugin.pluginId)]);
	const configuredChannelOwnerPluginIds = collectEffectiveConfiguredChannelOwnerPluginIds({
		cfg: params.cfg,
		env: params.env,
		snapshot,
		configuredChannelIds: params.configuredChannelIds
	});
	const bundledPluginsById = new Map(currentBundledPlugins.filter((plugin) => !isExternallyDistributedPlugin(plugin)).map((plugin) => [plugin.pluginId, { packageName: plugin.packageName }]));
	const configuredPluginIdsWithStaleDescriptors = collectConfiguredPluginIdsWithMissingChannelConfigDescriptors({
		snapshot,
		configuredPluginIds: params.configuredPluginIds,
		configuredChannelIds: params.configuredChannelIds
	});
	const records = params.baselineRecords ?? await loadInstalledPluginIndexInstallRecords({ env: params.env });
	const updateChannel = resolveRegistryUpdateChannel({
		configChannel: normalizeUpdateChannel(params.cfg.update?.channel),
		currentVersion: VERSION
	});
	const installedPluginIdsWithRepairablePackageDiagnostics = collectInstalledPluginIdsWithRepairablePackageDiagnostics({
		snapshot,
		installRecords: records,
		resolvePathIdentity
	});
	const installedPluginIdsWithStaleVersionBoundRuntimePackages = collectInstalledPluginIdsWithStaleVersionBoundRuntimePackages({
		snapshot,
		installRecords: records,
		configuredPluginIds: params.configuredPluginIds,
		updateChannel
	});
	const installedPluginIdsWithRepairablePackages = /* @__PURE__ */ new Set([...installedPluginIdsWithRepairablePackageDiagnostics, ...installedPluginIdsWithStaleVersionBoundRuntimePackages]);
	const officialReplacementPluginIds = new Set(collectOfficialReplacementInstallCandidates({
		cfg: params.cfg,
		env: params.env,
		repairablePluginIds: installedPluginIdsWithRepairablePackages,
		configuredPluginIds: params.configuredPluginIds,
		configuredChannelIds: params.configuredChannelIds,
		configuredChannelOwnerPluginIds,
		blockedPluginIds: params.blockedPluginIds
	}).keys());
	const configuredLoadPathIdentities = new Set(snapshot.discovery?.candidates.filter((candidate) => candidate.configSelected).flatMap((candidate) => [candidate.rootDir, candidate.source]).map(resolvePathIdentity));
	const configuredLoadPathPluginsById = /* @__PURE__ */ new Map();
	for (const plugin of snapshot.plugins) if (plugin.origin === "config" || [plugin.rootDir, plugin.source].some((value) => configuredLoadPathIdentities.has(resolvePathIdentity(value)))) configuredLoadPathPluginsById.set(plugin.id, plugin.rootDir);
	const stalePathInstallPluginIds = /* @__PURE__ */ new Set();
	for (const [pluginId, record] of Object.entries(records)) {
		if (installedPluginIdsWithRepairablePackages.has(pluginId)) continue;
		const configPluginRoot = configuredLoadPathPluginsById.get(pluginId);
		const recordedPaths = [record.installPath, record.sourcePath].filter((value) => Boolean(value?.trim()));
		if (!configPluginRoot || record.source !== "path" || recordedPaths.length === 0) continue;
		const configRootIdentity = resolvePathIdentity(configPluginRoot);
		if (isPayloadMissing(params.env, record.installPath) && recordedPaths.every((value) => resolvePathIdentity(value) !== configRootIdentity)) stalePathInstallPluginIds.add(pluginId);
	}
	let effectiveRecords = records;
	for (const pluginId of stalePathInstallPluginIds) effectiveRecords = removePluginInstallRecordFromRecords(effectiveRecords, pluginId);
	return {
		knownIds,
		configuredChannelOwnerPluginIds,
		bundledPluginsById,
		configuredPluginIdsWithStaleDescriptors,
		stalePathInstallPluginIds,
		records: effectiveRecords,
		persistedRecords: records,
		updateChannel,
		installedPluginIdsWithRepairablePackageDiagnostics,
		installedPluginIdsWithStaleVersionBoundRuntimePackages,
		installedPluginIdsWithRepairablePackages,
		officialReplacementPluginIds
	};
}
const MISSING_CHANNEL_CONFIG_DESCRIPTOR_DIAGNOSTIC = "without channelConfigs metadata";
const REPAIRABLE_PACKAGE_ENTRY_DIAGNOSTIC_MARKERS = [
	"extension entry not found",
	"extension entry escapes package directory",
	"extension entry unreadable",
	"requires compiled runtime output"
];
const OPENCLAW_BETA_COMPANION_VERSION_RE = /^(\d{4}\.[1-9]\d?\.[1-9]\d?)-beta\.[1-9]\d*$/;
const OPENCLAW_STABLE_OR_BETA_COMPANION_VERSION_RE = /^(\d{4}\.[1-9]\d?\.[1-9]\d?)(?:-beta\.[1-9]\d*)?$/;
function resolveCandidateClawHubSpec(install) {
	const explicit = install.clawhubSpec?.trim();
	if (explicit) return explicit;
}
function setDownloadableInstallCandidate(params) {
	const npmSpec = params.install.npmSpec?.trim();
	const clawhubSpec = resolveCandidateClawHubSpec(params.install);
	if (!npmSpec && !clawhubSpec) return;
	params.candidates.set(params.pluginId, {
		pluginId: params.pluginId,
		label: params.label,
		...npmSpec ? { npmSpec } : {},
		...clawhubSpec ? { clawhubSpec } : {},
		...params.install.expectedIntegrity ? { expectedIntegrity: params.install.expectedIntegrity } : {},
		...params.trustedSourceLinkedOfficialInstall ? { trustedSourceLinkedOfficialInstall: true } : {},
		...params.install.defaultChoice ? { defaultChoice: params.install.defaultChoice } : {}
	});
}
function collectDownloadableInstallCandidates(params) {
	const configuredPluginIds = params.configuredPluginIds ?? collectConfiguredPluginIds(params.cfg);
	const configuredChannelIds = params.configuredChannelIds ?? collectConfiguredChannelIds(params.cfg, params.env);
	const candidates = /* @__PURE__ */ new Map();
	for (const entry of listRawChannelPluginCatalogEntries({
		env: params.env,
		excludeWorkspace: true
	})) {
		if (entry.origin === "bundled") continue;
		const pluginId = entry.pluginId ?? entry.id;
		const channelId = normalizeOptionalLowercaseString(entry.id);
		if (params.blockedPluginIds?.has(pluginId)) continue;
		const selectedOnlyByChannel = !params.missingPluginIds.has(pluginId) && !configuredPluginIds.has(pluginId) && (channelId ? configuredChannelIds.has(channelId) : configuredChannelIds.has(entry.id));
		const configuredChannelOwnerPluginIds = channelId ? params.configuredChannelOwnerPluginIds?.get(channelId) : void 0;
		if (selectedOnlyByChannel && configuredChannelOwnerPluginIds && configuredChannelOwnerPluginIds.size > 0 && !configuredChannelOwnerPluginIds.has(pluginId)) continue;
		if (!params.missingPluginIds.has(pluginId) && !configuredPluginIds.has(pluginId) && !configuredChannelIds.has(entry.id)) continue;
		setDownloadableInstallCandidate({
			candidates,
			pluginId,
			label: entry.meta.label,
			install: entry.install,
			trustedSourceLinkedOfficialInstall: entry.trustedSourceLinkedOfficialInstall
		});
	}
	for (const entry of resolveProviderInstallCatalogEntries({
		config: params.cfg,
		env: params.env,
		includeUntrustedWorkspacePlugins: false
	})) {
		if (!configuredPluginIds.has(entry.pluginId) && !params.missingPluginIds.has(entry.pluginId)) continue;
		if (params.blockedPluginIds?.has(entry.pluginId)) continue;
		setDownloadableInstallCandidate({
			candidates,
			pluginId: entry.pluginId,
			label: entry.label,
			install: entry.install,
			trustedSourceLinkedOfficialInstall: entry.origin === "bundled"
		});
	}
	for (const entry of listOfficialExternalPluginCatalogEntries()) {
		const pluginId = resolveOfficialExternalPluginId(entry);
		if (!pluginId || candidates.has(pluginId) || params.blockedPluginIds?.has(pluginId)) continue;
		if (!configuredPluginIds.has(pluginId) && !params.missingPluginIds.has(pluginId)) continue;
		const install = resolveOfficialExternalPluginInstall(entry);
		if (!install) continue;
		setDownloadableInstallCandidate({
			candidates,
			pluginId,
			label: resolveOfficialExternalPluginLabel(entry),
			install,
			trustedSourceLinkedOfficialInstall: true
		});
	}
	for (const entry of CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES) {
		if (!configuredPluginIds.has(entry.pluginId) && !params.missingPluginIds.has(entry.pluginId)) continue;
		if (params.blockedPluginIds?.has(entry.pluginId)) continue;
		const existing = candidates.get(entry.pluginId);
		if (existing && entry.versionBoundToOpenClaw) candidates.set(entry.pluginId, {
			...existing,
			versionBoundToOpenClaw: true
		});
		else if (!existing) candidates.set(entry.pluginId, entry);
	}
	for (const candidate of collectLegacyNpmDeclarationInstallCandidates({
		cfg: params.cfg,
		env: params.env,
		configuredPluginIds,
		missingPluginIds: params.missingPluginIds,
		blockedPluginIds: params.blockedPluginIds
	})) if (!candidates.has(candidate.pluginId)) candidates.set(candidate.pluginId, candidate);
	return [...candidates.values()].toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
}
function addLegacyNpmDeclarationInstallCandidate(params) {
	const declaration = readLegacyNpmPluginDeclaration(params.pluginDir);
	if (!declaration) return;
	if (params.blockedPluginIds?.has(declaration.pluginId) || !params.configuredPluginIds.has(declaration.pluginId) && !params.missingPluginIds.has(declaration.pluginId)) return;
	params.candidates.set(declaration.pluginId, {
		pluginId: declaration.pluginId,
		label: declaration.pluginId,
		npmSpec: declaration.npmSpec,
		defaultChoice: "npm"
	});
}
function collectLegacyNpmDeclarationInstallCandidates(params) {
	const candidates = /* @__PURE__ */ new Map();
	const env = params.env ?? process.env;
	const loadPaths = params.cfg.plugins?.load?.paths;
	if (Array.isArray(loadPaths)) for (const rawPath of loadPaths) {
		if (typeof rawPath !== "string" || !rawPath.trim()) continue;
		addLegacyNpmDeclarationInstallCandidate({
			candidates,
			pluginDir: resolveUserPath(rawPath, env),
			configuredPluginIds: params.configuredPluginIds,
			missingPluginIds: params.missingPluginIds,
			blockedPluginIds: params.blockedPluginIds
		});
	}
	const extensionsDir = resolveDefaultPluginExtensionsDir(env);
	const configuredOrMissingPluginIds = /* @__PURE__ */ new Set([...params.configuredPluginIds, ...params.missingPluginIds]);
	for (const pluginId of configuredOrMissingPluginIds) try {
		addLegacyNpmDeclarationInstallCandidate({
			candidates,
			pluginDir: resolvePluginInstallDir(pluginId, extensionsDir),
			configuredPluginIds: params.configuredPluginIds,
			missingPluginIds: params.missingPluginIds,
			blockedPluginIds: params.blockedPluginIds
		});
	} catch {
		continue;
	}
	return [...candidates.values()].toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
}
function collectUpdateDeferredPluginIds(params) {
	const pluginIds = new Set(params.configuredPluginIds);
	for (const candidate of collectDownloadableInstallCandidates({
		cfg: params.cfg,
		env: params.env,
		missingPluginIds: /* @__PURE__ */ new Set(),
		configuredPluginIds: params.configuredPluginIds,
		configuredChannelIds: params.configuredChannelIds,
		configuredChannelOwnerPluginIds: params.configuredChannelOwnerPluginIds,
		blockedPluginIds: params.blockedPluginIds
	})) pluginIds.add(candidate.pluginId);
	return pluginIds;
}
function collectConfiguredPluginIdsWithMissingChannelConfigDescriptors(params) {
	const stalePluginIds = /* @__PURE__ */ new Set();
	const pluginsById = new Map(params.snapshot.plugins.map((plugin) => [plugin.id, plugin]));
	for (const diagnostic of params.snapshot.diagnostics) {
		const pluginId = diagnostic.pluginId?.trim();
		if (!pluginId || !diagnostic.message.includes(MISSING_CHANNEL_CONFIG_DESCRIPTOR_DIAGNOSTIC)) continue;
		const ownsConfiguredChannel = pluginsById.get(pluginId)?.channels.some((channelId) => params.configuredChannelIds.has(channelId));
		if (params.configuredPluginIds.has(pluginId) || ownsConfiguredChannel) stalePluginIds.add(pluginId);
	}
	return stalePluginIds;
}
function collectInstalledPluginIdsWithRepairablePackageDiagnostics(params) {
	const pluginIds = /* @__PURE__ */ new Set();
	for (const diagnostic of params.snapshot.diagnostics) {
		const pluginId = diagnostic.pluginId?.trim();
		if (!pluginId || !Object.hasOwn(params.installRecords, pluginId)) continue;
		const installPath = params.installRecords[pluginId]?.installPath;
		if (installPath && diagnostic.source && params.resolvePathIdentity(diagnostic.source) === params.resolvePathIdentity(installPath) && REPAIRABLE_PACKAGE_ENTRY_DIAGNOSTIC_MARKERS.some((marker) => diagnostic.message.includes(marker))) pluginIds.add(pluginId);
	}
	return pluginIds;
}
function resolveInstalledRuntimePackageVersion(params) {
	const plugin = params.snapshot.byPluginId?.get(params.pluginId) ?? params.snapshot.plugins.find((entry) => entry.id === params.pluginId);
	return normalizeOptionalLowercaseString(params.record.resolvedVersion ?? params.record.version ?? plugin?.packageVersion ?? plugin?.version);
}
function installedRuntimePackageVersionIsStale(params) {
	if (!params.installedVersion) return false;
	if (params.updateChannel === "beta" && betaCompanionMatchesCurrentStableVersion({
		installedVersion: params.installedVersion,
		currentVersion: params.currentVersion
	})) return false;
	const comparison = compareOpenClawReleaseVersions(params.installedVersion, params.currentVersion);
	return comparison === null ? params.installedVersion !== params.currentVersion : comparison < 0;
}
function betaCompanionMatchesCurrentStableVersion(params) {
	const installedBase = OPENCLAW_BETA_COMPANION_VERSION_RE.exec(params.installedVersion)?.[1];
	const currentBase = OPENCLAW_STABLE_OR_BETA_COMPANION_VERSION_RE.exec(params.currentVersion)?.[1];
	return Boolean(installedBase && currentBase && installedBase === currentBase);
}
function collectInstalledPluginIdsWithStaleVersionBoundRuntimePackages(params) {
	const pluginIds = /* @__PURE__ */ new Set();
	const currentVersion = normalizeOptionalLowercaseString(VERSION);
	if (!currentVersion) return pluginIds;
	for (const candidate of CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES) {
		if (!VERSION_BOUND_RUNTIME_PLUGIN_IDS.has(candidate.pluginId) || !params.configuredPluginIds.has(candidate.pluginId)) continue;
		const record = params.installRecords[candidate.pluginId];
		if (!record) continue;
		if (installedRuntimePackageVersionIsStale({
			installedVersion: resolveInstalledRuntimePackageVersion({
				pluginId: candidate.pluginId,
				snapshot: params.snapshot,
				record
			}),
			currentVersion,
			updateChannel: params.updateChannel
		})) pluginIds.add(candidate.pluginId);
	}
	return pluginIds;
}
function isConfiguredPluginRepairTarget(params) {
	if (params.configuredPluginIds.has(params.pluginId)) return true;
	if (params.configuredChannelIds.has(params.pluginId)) return true;
	for (const ownerIds of params.configuredChannelOwnerPluginIds.values()) if (ownerIds.has(params.pluginId)) return true;
	return false;
}
function collectOfficialReplacementInstallCandidates(params) {
	const repairableConfiguredPluginIds = new Set([...params.repairablePluginIds].filter((pluginId) => isConfiguredPluginRepairTarget({
		pluginId,
		configuredPluginIds: params.configuredPluginIds,
		configuredChannelIds: params.configuredChannelIds,
		configuredChannelOwnerPluginIds: params.configuredChannelOwnerPluginIds
	})));
	if (repairableConfiguredPluginIds.size === 0) return /* @__PURE__ */ new Map();
	const candidates = collectDownloadableInstallCandidates({
		cfg: params.cfg,
		env: params.env,
		missingPluginIds: repairableConfiguredPluginIds,
		configuredPluginIds: params.configuredPluginIds,
		configuredChannelIds: params.configuredChannelIds,
		configuredChannelOwnerPluginIds: params.configuredChannelOwnerPluginIds,
		blockedPluginIds: params.blockedPluginIds
	});
	return new Map(candidates.filter((candidate) => repairableConfiguredPluginIds.has(candidate.pluginId) && candidate.trustedSourceLinkedOfficialInstall).map((candidate) => [candidate.pluginId, candidate]));
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.records.ts
function forceNpmInstallRecordRepair(record) {
	if (record.source !== "npm") return record;
	const next = { ...record };
	delete next.resolvedSpec;
	delete next.resolvedVersion;
	return next;
}
function installPathsEqual(left, right) {
	return path.resolve(left) === path.resolve(right);
}
function resolveNpmPackageInstallPath(params) {
	return resolvePluginNpmPackageDir({
		npmDir: params.npmRoot,
		packageName: params.packageName
	});
}
function resolveLegacyNpmPackageInstallPath(params) {
	return path.join(params.npmRoot, "node_modules", ...params.packageName.split("/"));
}
function collectCandidateOfficialPackageNames(candidate) {
	const names = /* @__PURE__ */ new Set();
	const npmName = candidate.npmSpec ? parseRegistryNpmSpec(candidate.npmSpec)?.name : void 0;
	const clawhubName = candidate.clawhubSpec ? parseClawHubPluginSpec(candidate.clawhubSpec)?.name : void 0;
	if (npmName) names.add(npmName);
	if (clawhubName) names.add(clawhubName);
	return names;
}
function collectInstalledRecordPackageNames(record) {
	const names = /* @__PURE__ */ new Set();
	if (record.source === "npm") {
		const specName = record.spec ? parseRegistryNpmSpec(record.spec)?.name : void 0;
		const resolvedSpecName = record.resolvedSpec ? parseRegistryNpmSpec(record.resolvedSpec)?.name : void 0;
		for (const value of [
			record.resolvedName,
			specName,
			resolvedSpecName
		]) if (value) names.add(value);
	}
	if (record.source === "clawhub") {
		const specName = record.spec ? parseClawHubPluginSpec(record.spec)?.name : void 0;
		for (const value of [record.clawhubPackage, specName]) if (value) names.add(value);
	}
	return names;
}
function isTrustedOfficialInstallRecordForCandidate(params) {
	const record = params.record;
	if (!record) return false;
	if (record.source !== "npm" && record.source !== "clawhub") return false;
	if (record.source === "clawhub" && record.clawhubChannel !== "official") return false;
	const candidatePackageNames = collectCandidateOfficialPackageNames(params.candidate);
	if (candidatePackageNames.size === 0) return false;
	for (const installedPackageName of collectInstalledRecordPackageNames(record)) if (candidatePackageNames.has(installedPackageName)) return true;
	return false;
}
function resolveSafeBrokenOfficialInstallRemovalPath(params) {
	const installPath = params.record?.installPath?.trim();
	if (!installPath) return null;
	const resolvedInstallPath = resolveUserPath(installPath, params.env);
	try {
		const extensionsDir = resolveDefaultPluginExtensionsDir(params.env);
		if (installPathsEqual(resolvedInstallPath, resolvePluginInstallDir(params.pluginId, extensionsDir))) return resolvedInstallPath;
	} catch {}
	const parsedNpmSpec = params.candidate.npmSpec ? parseRegistryNpmSpec(params.candidate.npmSpec) : null;
	if (!parsedNpmSpec?.name) return null;
	const npmRoot = resolveDefaultPluginNpmDir(params.env);
	return [resolveNpmPackageInstallPath({
		packageName: parsedNpmSpec.name,
		npmRoot
	}), resolveLegacyNpmPackageInstallPath({
		packageName: parsedNpmSpec.name,
		npmRoot
	})].some((expectedPath) => installPathsEqual(resolvedInstallPath, expectedPath)) ? resolvedInstallPath : null;
}
function recordMatchesBundledPackage(record, bundled) {
	const packageName = bundled.packageName?.trim() || bundled.name?.trim();
	return Boolean(packageName && collectInstalledRecordPackageNames(record).has(packageName));
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.install.ts
function isActionableClawHubSkippedOutcome(outcome) {
	return isClawHubTrustSkippedOutcome(outcome);
}
function isClawHubReviewNotice(message) {
	const audit = stripAnsi(message);
	return audit.includes("ClawHub Security Audit") && audit.includes("Outcome: Review");
}
function formatInstalledConfiguredPluginChange(params) {
	return params.repairReason === "stale-version-bound-runtime" ? `Refreshed stale configured plugin "${params.pluginId}" from ${params.installSpec}.` : `Installed missing configured plugin "${params.pluginId}" from ${params.installSpec}.`;
}
async function installCandidate(params) {
	const consent = capturePluginCapabilityConsentHandlerErrors(params.onCapabilityConsent);
	try {
		const result = await installCandidatePackage({
			...params,
			onCapabilityConsent: consent.onCapabilityConsent
		});
		consent.rethrowCallbackError();
		return result;
	} catch (error) {
		consent.rethrowCallbackError();
		if (!(error instanceof ManagedPluginLifecycleError)) throw error;
		return {
			records: params.records,
			changes: [],
			notices: [],
			warnings: [sanitizeTerminalText(error.message)],
			failedPluginId: params.candidate.pluginId,
			...error.capabilityConsent ? { code: PLUGIN_CAPABILITY_CONSENT_REQUIRED } : {}
		};
	}
}
async function installCandidatePackage(params) {
	const record = params.records[params.candidate.pluginId];
	const recordedSource = record?.source === "npm" || record?.source === "clawhub" ? record.source : void 0;
	const staleRuntimeRepair = params.repairReason === "stale-version-bound-runtime";
	const declaredSource = recordedSource ? resolvePluginInstallSources(params.candidate, recordedSource)[0] : void 0;
	const recordedSpec = staleRuntimeRepair ? declaredSource?.spec : record?.spec ?? declaredSource?.spec;
	const candidate = record && recordedSource ? {
		...params.candidate,
		defaultChoice: recordedSource,
		...recordedSource === "npm" ? {
			npmSpec: recordedSpec,
			clawhubSpec: void 0
		} : {
			clawhubSpec: recordedSpec,
			npmSpec: void 0
		},
		expectedIntegrity: staleRuntimeRepair ? declaredSource?.expectedIntegrity : expectedIntegrityForUpdate(record.spec, record.integrity),
		trustedSourceLinkedOfficialInstall: params.candidate.trustedSourceLinkedOfficialInstall && (!record.spec || (recordedSource === "npm" ? parseRegistryNpmSpec(record.spec)?.name === parseRegistryNpmSpec(params.candidate.npmSpec ?? "")?.name : parseClawHubPluginSpec(record.spec)?.name === parseClawHubPluginSpec(params.candidate.clawhubSpec ?? "")?.name))
	} : params.candidate;
	const extensionsDir = resolveDefaultPluginExtensionsDir(params.env);
	const warnings = [];
	const channelNotices = [];
	const pinResolvedSpecForStaleRepair = staleRuntimeRepair && parseRegistryNpmSpec(params.records[candidate.pluginId]?.spec ?? "")?.selectorKind === "exact-version";
	const clawhubSpecs = candidate.clawhubSpec ? resolveClawHubInstallSpecsForUpdateChannel({
		spec: candidate.clawhubSpec,
		updateChannel: params.updateChannel,
		officialPackageName: candidate.trustedSourceLinkedOfficialInstall ? parseClawHubPluginSpec(candidate.clawhubSpec)?.name : void 0,
		coreVersion: resolveCompatibilityHostVersion(params.env),
		versionBoundToCore: candidate.versionBoundToOpenClaw
	}) : null;
	const npmSpecs = candidate.npmSpec ? resolveNpmInstallSpecsForUpdateChannel({
		spec: candidate.npmSpec,
		updateChannel: params.updateChannel,
		officialPackageName: candidate.trustedSourceLinkedOfficialInstall ? parseRegistryNpmSpec(candidate.npmSpec)?.name : void 0,
		coreVersion: resolveCompatibilityHostVersion(params.env),
		versionBoundToCore: candidate.versionBoundToOpenClaw
	}) : null;
	const clawhubInstallSpec = clawhubSpecs?.installSpec ?? candidate.clawhubSpec;
	const npmInstallSpec = npmSpecs?.installSpec ?? candidate.npmSpec;
	const prepareConsent = (source, spec, expectedIntegrity) => prepareManagedPluginArtifactConsentHandler({
		config: params.config,
		env: params.env,
		source,
		spec,
		previousRecords: params.records,
		expectedIntegrity,
		onCapabilityConsent: params.onCapabilityConsent,
		beforePersistentEffect: params.beforePersistentEffect
	});
	const npmDir = resolveDefaultPluginNpmDir(params.env);
	const existingClawHubPackagePath = clawhubInstallSpec ? resolveExistingCandidateClawHubPackagePath({
		candidate,
		extensionsDir
	}) : null;
	const existingNpmPackagePath = npmInstallSpec ? resolveExistingCandidateNpmPackagePath({
		candidate,
		npmDir
	}) : null;
	const sources = resolvePluginInstallSources(candidate, recordedSource);
	if (sources.length === 0) return {
		records: params.records,
		changes: [],
		notices: [],
		warnings: [`Failed to install missing configured plugin "${candidate.pluginId}": no declared remote source.`],
		failedPluginId: candidate.pluginId
	};
	const { attempt: { result: installResult, capabilityConsent: acceptedConsent }, source: installedSource } = await installWithSourceFallback({
		sources,
		install: async (source) => {
			const specs = source.source === "npm" ? npmSpecs : clawhubSpecs;
			const installSpec = specs?.installSpec ?? source.spec;
			return await installWithChannelFallback({
				installSpec,
				...source.expectedIntegrity ? {} : { fallbackSpec: specs?.fallbackSpec },
				install: async (spec) => {
					const capabilityConsent = await prepareConsent(source.source, spec, source.expectedIntegrity);
					const options = {
						spec,
						config: params.config,
						extensionsDir,
						expectedPluginId: candidate.pluginId,
						expectedIntegrity: source.expectedIntegrity,
						onBeforePluginArtifactCommit: capabilityConsent.onBeforePluginArtifactCommit
					};
					if (source.source === "clawhub") return {
						result: await installPluginFromClawHub({
							...options,
							env: params.env,
							...recordedSource === "clawhub" ? { baseUrl: record?.clawhubUrl } : {},
							mode: params.mode === "update" || existingClawHubPackagePath ? "update" : "install",
							logger: {
								terminalLinks: false,
								warn: (message) => warnings.push(stripAnsi(message))
							}
						}),
						capabilityConsent
					};
					const mode = params.mode === "update" || existingNpmPackagePath ? "update" : "install";
					const install = (installMode) => installPluginFromNpmSpec({
						...options,
						npmDir,
						mode: installMode,
						trustedSourceLinkedOfficialInstall: candidate.trustedSourceLinkedOfficialInstall
					});
					let result = await install(mode);
					if (!result.ok && mode === "install" && isPluginAlreadyExistsError(result.error)) result = await install("update");
					return {
						result,
						capabilityConsent
					};
				},
				isRetryable: (attempt) => !attempt.result.ok && (source.source === "npm" ? isUnavailableNpmTarget(attempt.result) : isUnavailableClawHubTarget(attempt.result)),
				onFallback: (message) => {
					channelNotices.push(message);
				}
			});
		},
		result: (attempt) => attempt.result,
		onFallback: (message) => {
			channelNotices.push(message);
		}
	});
	if (!installResult.ok) return {
		records: params.records,
		changes: [],
		notices: [],
		warnings: [
			...warnings,
			...channelNotices,
			`Failed to install missing configured plugin "${candidate.pluginId}" from ${installedSource.spec}: ${installResult.error}`
		],
		failedPluginId: candidate.pluginId
	};
	const pluginId = installResult.pluginId;
	const recordSpec = (installedSource.source === "npm" ? npmSpecs : clawhubSpecs)?.recordSpec ?? installedSource.spec;
	const installedRecord = "clawhub" in installResult ? {
		...buildClawHubPluginInstallRecordFields(installResult.clawhub),
		spec: recordSpec,
		installPath: installResult.targetDir
	} : {
		source: "npm",
		spec: resolveNpmInstallRecordSpec({
			requestedSpec: recordSpec,
			resolution: installResult.npmResolution,
			pinResolvedRegistrySpec: pinResolvedSpecForStaleRepair
		}),
		installPath: installResult.targetDir,
		version: installResult.version,
		...buildNpmResolutionInstallFields(installResult.npmResolution)
	};
	return {
		records: {
			...params.records,
			[pluginId]: acceptedConsent.applyAcceptedSurface(pluginId, {
				...installedRecord,
				installedAt: (/* @__PURE__ */ new Date()).toISOString()
			})
		},
		changes: [formatInstalledConfiguredPluginChange({
			pluginId,
			installSpec: (installedSource.source === "npm" ? npmSpecs : clawhubSpecs)?.installSpec ?? installedSource.spec,
			repairReason: params.repairReason
		})],
		notices: [...channelNotices, ...warnings],
		warnings: []
	};
}
function isPluginAlreadyExistsError(error) {
	return /\bplugin already exists:/.test(error);
}
function resolveExistingCandidateNpmPackagePath(params) {
	const npmName = params.candidate.npmSpec ? parseRegistryNpmSpec(params.candidate.npmSpec)?.name : void 0;
	if (!npmName) return null;
	const packagePath = resolveNpmPackageInstallPath({
		packageName: npmName,
		npmRoot: params.npmDir
	});
	if (existsSync(packagePath)) return packagePath;
	const legacyPackagePath = resolveLegacyNpmPackageInstallPath({
		packageName: npmName,
		npmRoot: params.npmDir
	});
	return existsSync(legacyPackagePath) ? legacyPackagePath : null;
}
function resolveExistingCandidateClawHubPackagePath(params) {
	try {
		const packagePath = resolvePluginInstallDir(params.candidate.pluginId, params.extensionsDir);
		return existsSync(packagePath) ? packagePath : null;
	} catch {
		return null;
	}
}
function resolveCandidateInstallSpec(params) {
	if (resolvePluginInstallSources(params.candidate)[0]?.source === "clawhub" && params.candidate.clawhubSpec) return resolveClawHubInstallSpecsForUpdateChannel({
		spec: params.candidate.clawhubSpec,
		updateChannel: params.updateChannel,
		officialPackageName: params.candidate.trustedSourceLinkedOfficialInstall ? parseClawHubPluginSpec(params.candidate.clawhubSpec)?.name : void 0,
		coreVersion: params.coreVersion,
		versionBoundToCore: params.candidate.versionBoundToOpenClaw
	}).installSpec;
	if (params.candidate.npmSpec) return resolveNpmInstallSpecsForUpdateChannel({
		spec: params.candidate.npmSpec,
		updateChannel: params.updateChannel,
		officialPackageName: params.candidate.trustedSourceLinkedOfficialInstall ? parseRegistryNpmSpec(params.candidate.npmSpec)?.name : void 0,
		coreVersion: params.coreVersion,
		versionBoundToCore: params.candidate.versionBoundToOpenClaw
	}).installSpec;
	if (params.candidate.clawhubSpec) return resolveClawHubInstallSpecsForUpdateChannel({
		spec: params.candidate.clawhubSpec,
		updateChannel: params.updateChannel,
		officialPackageName: params.candidate.trustedSourceLinkedOfficialInstall ? parseClawHubPluginSpec(params.candidate.clawhubSpec)?.name : void 0,
		coreVersion: params.coreVersion,
		versionBoundToCore: params.candidate.versionBoundToOpenClaw
	}).installSpec;
}
function resolveRecordInstallPath(record, env) {
	const installPath = record?.installPath?.trim();
	return installPath ? resolveUserPath(installPath, env) : void 0;
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.health.ts
const CONFIGURED_PLUGIN_INSTALLS_CHECK_ID = "core/doctor/configured-plugin-installs";
function missingRecordedPluginIssueKind(params) {
	if (params.staleVersionBoundRuntimePluginIds.has(params.pluginId)) return "stale-version-bound-runtime";
	if (params.repairablePackageDiagnosticPluginIds.has(params.pluginId)) return "repairable-installed-plugin";
	if (params.staleDescriptorPluginIds.has(params.pluginId)) return "stale-channel-config-descriptor";
	return "missing-installed-payload";
}
/** Detect configured plugin installs that Doctor can repair without mutating package state. */
async function detectConfiguredPluginInstallHealthIssues(params) {
	const env = params.env ?? process.env;
	const pluginIds = collectConfiguredPluginIds(params.cfg, env);
	const channelIds = collectConfiguredChannelIds(params.cfg, env);
	const blockedPluginIds = collectBlockedPluginIds(params.cfg);
	const { knownIds, configuredChannelOwnerPluginIds, bundledPluginsById, configuredPluginIdsWithStaleDescriptors: staleDescriptorPluginIds, records, updateChannel, installedPluginIdsWithRepairablePackageDiagnostics: repairablePackageDiagnosticPluginIds, installedPluginIdsWithStaleVersionBoundRuntimePackages: staleVersionBoundRuntimePluginIds, installedPluginIdsWithRepairablePackages: repairableInstalledPluginIds, officialReplacementPluginIds } = await resolveConfiguredPluginInstallContext({
		cfg: params.cfg,
		env,
		configuredPluginIds: pluginIds,
		configuredChannelIds: channelIds,
		blockedPluginIds,
		baselineRecords: params.baselineRecords
	});
	const deferredPluginIds = /* @__PURE__ */ new Set();
	const reportedPluginIds = /* @__PURE__ */ new Set();
	const issues = [];
	if (shouldDeferConfiguredPluginInstallRepair(env)) for (const pluginId of collectUpdateDeferredPluginIds({
		cfg: params.cfg,
		env,
		configuredPluginIds: pluginIds,
		configuredChannelIds: channelIds,
		configuredChannelOwnerPluginIds,
		blockedPluginIds
	})) {
		deferredPluginIds.add(pluginId);
		const record = records[pluginId];
		if (!record || !isPayloadMissing(env, record.installPath)) continue;
		issues.push({
			kind: "deferred-package-manager-repair",
			pluginId,
			...resolveRecordInstallPath(record, env) ? { installPath: resolveRecordInstallPath(record, env) } : {}
		});
		reportedPluginIds.add(pluginId);
	}
	const missingRecordedPluginIds = Object.keys(records).filter((pluginId) => !deferredPluginIds.has(pluginId) && !officialReplacementPluginIds.has(pluginId) && !bundledPluginsById.has(pluginId) && (pluginIds.has(pluginId) && (!knownIds.has(pluginId) || isPayloadMissing(env, records[pluginId]?.installPath)) || staleDescriptorPluginIds.has(pluginId) || repairableInstalledPluginIds.has(pluginId)));
	for (const pluginId of missingRecordedPluginIds) {
		const record = records[pluginId];
		const kind = missingRecordedPluginIssueKind({
			pluginId,
			staleVersionBoundRuntimePluginIds,
			repairablePackageDiagnosticPluginIds,
			staleDescriptorPluginIds
		});
		const installPath = resolveRecordInstallPath(record, env);
		if (kind === "stale-channel-config-descriptor") {
			issues.push({
				kind,
				pluginId,
				...installPath ? { installPath } : {}
			});
			reportedPluginIds.add(pluginId);
			continue;
		}
		issues.push({
			kind,
			pluginId,
			...installPath ? { installPath } : {},
			...record?.spec ? { installSpec: record.spec } : {}
		});
		reportedPluginIds.add(pluginId);
	}
	const missingPluginIds = new Set([...pluginIds].filter((pluginId) => {
		if (deferredPluginIds.has(pluginId)) return false;
		const hasRecord = Object.hasOwn(records, pluginId);
		return !knownIds.has(pluginId) && !hasRecord && !bundledPluginsById.has(pluginId) || hasRecord && !bundledPluginsById.has(pluginId) && isPayloadMissing(env, records[pluginId]?.installPath);
	}));
	const installCandidatePluginIds = /* @__PURE__ */ new Set([...missingPluginIds, ...officialReplacementPluginIds]);
	for (const candidate of collectDownloadableInstallCandidates({
		cfg: params.cfg,
		env,
		missingPluginIds: installCandidatePluginIds,
		configuredPluginIds: pluginIds,
		configuredChannelIds: channelIds,
		configuredChannelOwnerPluginIds,
		blockedPluginIds: deferredPluginIds.size > 0 ? /* @__PURE__ */ new Set([...blockedPluginIds, ...deferredPluginIds]) : blockedPluginIds
	})) {
		if (bundledPluginsById.has(candidate.pluginId)) continue;
		if (reportedPluginIds.has(candidate.pluginId)) continue;
		const shouldReplaceBrokenOfficialInstall = officialReplacementPluginIds.has(candidate.pluginId);
		if (shouldReplaceBrokenOfficialInstall && !candidate.trustedSourceLinkedOfficialInstall) continue;
		const record = records[candidate.pluginId];
		if (shouldReplaceBrokenOfficialInstall && !isTrustedOfficialInstallRecordForCandidate({
			record,
			candidate
		})) continue;
		const hasRecord = Object.hasOwn(records, candidate.pluginId);
		const hasUsableRecord = hasRecord && !isPayloadMissing(env, records[candidate.pluginId]?.installPath);
		if (!shouldReplaceBrokenOfficialInstall && (hasUsableRecord || knownIds.has(candidate.pluginId) && !hasRecord)) continue;
		const installSpec = resolveCandidateInstallSpec({
			candidate,
			updateChannel,
			coreVersion: resolveCompatibilityHostVersion(env)
		});
		if (shouldReplaceBrokenOfficialInstall) {
			const installPath = resolveRecordInstallPath(record, env);
			if (staleVersionBoundRuntimePluginIds.has(candidate.pluginId)) issues.push({
				kind: "stale-version-bound-runtime",
				pluginId: candidate.pluginId,
				...installPath ? { installPath } : {},
				...installSpec ? { installSpec } : {}
			});
			else issues.push({
				kind: "repairable-installed-plugin",
				pluginId: candidate.pluginId,
				...installPath ? { installPath } : {},
				...installSpec ? { installSpec } : {}
			});
			continue;
		}
		if (record) {
			const installPath = resolveRecordInstallPath(record, env);
			issues.push({
				kind: "missing-installed-payload",
				pluginId: candidate.pluginId,
				...installPath ? { installPath } : {},
				...installSpec ? { installSpec } : {}
			});
		} else if (installSpec) issues.push({
			kind: "missing-install-record",
			pluginId: candidate.pluginId,
			installSpec
		});
	}
	return issues.toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
}
const CONFIGURED_PLUGIN_INSTALL_ISSUE_DETAILS = {
	"missing-install-record": {
		message: (pluginId) => `Configured plugin ${pluginId} is not installed.`,
		fixHint: "",
		action: "would-install-configured-plugin",
		dryRunSafe: false
	},
	"missing-installed-payload": {
		message: (pluginId) => `Configured plugin ${pluginId} has an install record but its package payload is missing.`,
		fixHint: "Run `openclaw doctor --fix` to reinstall the configured plugin package.",
		action: "would-reinstall-configured-plugin",
		dryRunSafe: false
	},
	"repairable-installed-plugin": {
		message: (pluginId) => `Configured plugin ${pluginId} has a repairable package install problem.`,
		fixHint: "Run `openclaw doctor --fix` to repair the configured plugin package.",
		action: "would-repair-configured-plugin-install",
		dryRunSafe: false
	},
	"stale-version-bound-runtime": {
		message: (pluginId) => `Configured runtime plugin ${pluginId} is older than this OpenClaw version.`,
		fixHint: "Run `openclaw doctor --fix` to refresh the configured runtime plugin.",
		action: "would-refresh-configured-runtime-plugin",
		dryRunSafe: false
	},
	"stale-channel-config-descriptor": {
		message: (pluginId) => `Configured plugin ${pluginId} has stale channel config metadata.`,
		fixHint: "Run `openclaw doctor --fix` to repair the configured plugin install metadata.",
		action: "would-repair-configured-plugin-install",
		dryRunSafe: false
	},
	"deferred-package-manager-repair": {
		message: (pluginId) => `Configured plugin ${pluginId} package repair is deferred until the package update finishes.`,
		fixHint: "Rerun `openclaw doctor --fix` after the package update completes.",
		action: "would-defer-configured-plugin-install-repair",
		dryRunSafe: true
	}
};
function configuredPluginInstallIssueToHealthFinding(issue) {
	const detail = CONFIGURED_PLUGIN_INSTALL_ISSUE_DETAILS[issue.kind];
	return {
		checkId: CONFIGURED_PLUGIN_INSTALLS_CHECK_ID,
		severity: "warning",
		message: detail.message(issue.pluginId),
		target: issue.pluginId,
		..."installPath" in issue && issue.installPath ? { path: issue.installPath } : {},
		fixHint: issue.kind === "missing-install-record" ? `Run \`openclaw doctor --fix\` to install ${issue.installSpec}.` : detail.fixHint
	};
}
function configuredPluginInstallIssueToRepairEffect(issue) {
	const detail = CONFIGURED_PLUGIN_INSTALL_ISSUE_DETAILS[issue.kind];
	return {
		kind: "package",
		action: detail.action,
		target: issue.pluginId,
		dryRunSafe: detail.dryRunSafe
	};
}
//#endregion
//#region src/commands/doctor/shared/missing-configured-plugin-install.repair.ts
/** Repair missing installs inferred from the current OpenClaw config. */
async function repairMissingConfiguredPluginInstalls(params) {
	return repairMissingPluginInstalls({
		cfg: params.cfg,
		env: params.env,
		pluginIds: collectConfiguredPluginIds(params.cfg, params.env),
		channelIds: collectConfiguredChannelIds(params.cfg, params.env),
		blockedPluginIds: collectBlockedPluginIds(params.cfg),
		...params.onCapabilityConsent ? { onCapabilityConsent: params.onCapabilityConsent } : {},
		...params.baselineRecords ? { baselineRecords: params.baselineRecords } : {}
	});
}
/** Repair missing installs for an explicit plugin/channel id set. */
async function repairMissingPluginInstallsForIds(params) {
	return repairMissingPluginInstalls({
		cfg: params.cfg,
		env: params.env,
		pluginIds: new Set([...params.pluginIds].map((pluginId) => pluginId.trim()).filter((pluginId) => pluginId)),
		channelIds: new Set([...params.channelIds ?? []].map((channelId) => channelId.trim()).filter((channelId) => channelId)),
		blockedPluginIds: new Set([...params.blockedPluginIds ?? []].map((pluginId) => pluginId.trim()).filter((pluginId) => pluginId)),
		...params.onCapabilityConsent ? { onCapabilityConsent: params.onCapabilityConsent } : {},
		beforePersistentEffect: params.beforePersistentEffect,
		...params.baselineRecords ? { baselineRecords: params.baselineRecords } : {}
	});
}
async function repairMissingPluginInstalls(params) {
	return await withPluginLifecycleLease({ env: params.env }, () => repairMissingPluginInstallsWithLease(params));
}
async function repairMissingPluginInstallsWithLease(params) {
	const env = params.env ?? process.env;
	const { knownIds, configuredChannelOwnerPluginIds, bundledPluginsById, configuredPluginIdsWithStaleDescriptors, stalePathInstallPluginIds, records, persistedRecords, updateChannel, installedPluginIdsWithRepairablePackageDiagnostics, installedPluginIdsWithStaleVersionBoundRuntimePackages, installedPluginIdsWithRepairablePackages, officialReplacementPluginIds } = await resolveConfiguredPluginInstallContext({
		cfg: params.cfg,
		env,
		configuredPluginIds: params.pluginIds,
		configuredChannelIds: params.channelIds,
		blockedPluginIds: params.blockedPluginIds,
		baselineRecords: params.baselineRecords
	});
	const changes = [];
	const notices = [];
	const warnings = [];
	const deferredRepairDetails = [];
	const failedPlugins = /* @__PURE__ */ new Map();
	const repairedPluginIds = /* @__PURE__ */ new Set();
	const deferredPluginIds = /* @__PURE__ */ new Set();
	const preferNpmInstalls = isLegacyPackageUpdateDoctorPass(env);
	let nextRecords = records;
	const normalizedPluginConfig = normalizePluginsConfig(params.cfg.plugins);
	const recordFailure = (pluginId, messages, code) => {
		let outcome = failedPlugins.get(pluginId);
		if (code === "PLUGIN_CAPABILITY_CONSENT_REQUIRED" && knownIds.has(pluginId) && !isPayloadMissing(env, records[pluginId]?.installPath) && !installedPluginIdsWithRepairablePackageDiagnostics.has(pluginId) && !configuredPluginIdsWithStaleDescriptors.has(pluginId) && resolveEffectiveEnableState({
			id: pluginId,
			origin: "global",
			config: normalizedPluginConfig,
			rootConfig: params.cfg
		}).enabled) notices.push(`Kept installed plugin "${pluginId}"; replacement deferred. ${messages.join(" ")}`);
		else {
			warnings.push(...messages);
			if (code === "PLUGIN_CAPABILITY_CONSENT_REQUIRED") outcome = {
				pluginId,
				status: "error",
				code,
				message: messages.join(" ")
			};
		}
		failedPlugins.set(pluginId, outcome);
	};
	for (const [pluginId, record] of Object.entries(records)) {
		const bundled = bundledPluginsById.get(pluginId);
		if (!bundled || !recordMatchesBundledPackage(record, bundled)) continue;
		if (nextRecords === records) nextRecords = { ...records };
		delete nextRecords[pluginId];
		changes.push(`Removed stale managed install record for bundled plugin "${pluginId}".`);
	}
	for (const pluginId of stalePathInstallPluginIds) changes.push(`Removed stale path-install record for plugin "${pluginId}" (loaded from a configured load path).`);
	if (shouldDeferConfiguredPluginInstallRepair(env)) {
		const updateDeferredPluginIds = collectUpdateDeferredPluginIds({
			cfg: params.cfg,
			env,
			configuredPluginIds: params.pluginIds,
			configuredChannelIds: params.channelIds,
			configuredChannelOwnerPluginIds,
			blockedPluginIds: params.blockedPluginIds
		});
		for (const pluginId of updateDeferredPluginIds) {
			deferredPluginIds.add(pluginId);
			const record = nextRecords[pluginId];
			if (!record || !isPayloadMissing(env, record.installPath)) continue;
			const detail = `Skipped package-manager repair for configured plugin "${pluginId}" during package update; rerun "openclaw doctor --fix" after the update completes.`;
			changes.push(detail);
			deferredRepairDetails.push(detail);
		}
	}
	const missingRecordedPlugins = Object.entries(records).filter(([pluginId]) => !deferredPluginIds.has(pluginId) && !officialReplacementPluginIds.has(pluginId) && Object.hasOwn(nextRecords, pluginId) && !bundledPluginsById.has(pluginId) && (params.pluginIds.has(pluginId) && (!knownIds.has(pluginId) || isPayloadMissing(env, nextRecords[pluginId]?.installPath)) || configuredPluginIdsWithStaleDescriptors.has(pluginId) || installedPluginIdsWithRepairablePackages.has(pluginId)));
	const missingRecordedPluginIds = missingRecordedPlugins.map(([pluginId]) => pluginId);
	if (missingRecordedPluginIds.length > 0) {
		const repairRecords = { ...nextRecords };
		for (const [pluginId, record] of missingRecordedPlugins) repairRecords[pluginId] = forceNpmInstallRecordRepair(record);
		const updateResult = await updateNpmInstalledPlugins({
			config: {
				...params.cfg,
				plugins: {
					...params.cfg.plugins,
					installs: repairRecords
				}
			},
			pluginIds: missingRecordedPluginIds,
			skipDisabledPlugins: true,
			updateChannel,
			coreVersion: resolveCompatibilityHostVersion(env),
			logger: {
				terminalLinks: false,
				warn: (message) => {
					if (isClawHubReviewNotice(message)) {
						notices.push(stripAnsi(message));
						return;
					}
					warnings.push(message);
				},
				error: (message) => warnings.push(message)
			},
			...params.onCapabilityConsent ? { onCapabilityConsent: params.onCapabilityConsent } : {},
			beforePersistentEffect: params.beforePersistentEffect
		});
		for (const outcome of updateResult.outcomes) if (outcome.status === "updated" || outcome.status === "unchanged") {
			repairedPluginIds.add(outcome.pluginId);
			failedPlugins.delete(outcome.pluginId);
			changes.push(installedPluginIdsWithStaleVersionBoundRuntimePackages.has(outcome.pluginId) ? `Refreshed stale configured plugin "${outcome.pluginId}".` : installedPluginIdsWithRepairablePackageDiagnostics.has(outcome.pluginId) ? `Repaired broken installed plugin "${outcome.pluginId}".` : `Repaired missing configured plugin "${outcome.pluginId}".`);
		} else if (outcome.status === "error" || isActionableClawHubSkippedOutcome(outcome)) recordFailure(outcome.pluginId, [outcome.message], outcome.code);
		if (repairedPluginIds.size > 0) {
			nextRecords = { ...updateResult.config.plugins?.installs ?? nextRecords };
			for (const [pluginId, record] of missingRecordedPlugins) if (!repairedPluginIds.has(pluginId)) nextRecords[pluginId] = record;
		}
	}
	const missingPluginIds = new Set([...params.pluginIds].filter((pluginId) => {
		if (deferredPluginIds.has(pluginId)) return false;
		const hasRecord = Object.hasOwn(nextRecords, pluginId);
		return !knownIds.has(pluginId) && !hasRecord && !bundledPluginsById.has(pluginId) || hasRecord && !bundledPluginsById.has(pluginId) && isPayloadMissing(env, nextRecords[pluginId]?.installPath);
	}));
	const installCandidatePluginIds = /* @__PURE__ */ new Set([...missingPluginIds, ...officialReplacementPluginIds]);
	for (const candidate of collectDownloadableInstallCandidates({
		cfg: params.cfg,
		env,
		missingPluginIds: installCandidatePluginIds,
		configuredPluginIds: params.pluginIds,
		configuredChannelIds: params.channelIds,
		configuredChannelOwnerPluginIds,
		blockedPluginIds: deferredPluginIds.size > 0 ? /* @__PURE__ */ new Set([...params.blockedPluginIds ?? [], ...deferredPluginIds]) : params.blockedPluginIds
	})) {
		if (bundledPluginsById.has(candidate.pluginId)) continue;
		const shouldReplaceBrokenOfficialInstall = officialReplacementPluginIds.has(candidate.pluginId);
		if (shouldReplaceBrokenOfficialInstall && !candidate.trustedSourceLinkedOfficialInstall) continue;
		const record = nextRecords[candidate.pluginId];
		if (shouldReplaceBrokenOfficialInstall && !isTrustedOfficialInstallRecordForCandidate({
			record,
			candidate
		})) continue;
		const hasRecord = Object.hasOwn(nextRecords, candidate.pluginId);
		const hasUsableRecord = hasRecord && !isPayloadMissing(env, nextRecords[candidate.pluginId]?.installPath);
		if (!shouldReplaceBrokenOfficialInstall && (hasUsableRecord || knownIds.has(candidate.pluginId) && !hasRecord)) continue;
		const removalPath = shouldReplaceBrokenOfficialInstall ? resolveSafeBrokenOfficialInstallRemovalPath({
			pluginId: candidate.pluginId,
			candidate,
			record,
			env
		}) : null;
		const previousRecords = nextRecords;
		const installed = await installCandidate({
			candidate,
			config: params.cfg,
			records: nextRecords,
			env,
			updateChannel,
			mode: shouldReplaceBrokenOfficialInstall ? "update" : "install",
			preferNpm: preferNpmInstalls,
			...installedPluginIdsWithStaleVersionBoundRuntimePackages.has(candidate.pluginId) ? { repairReason: "stale-version-bound-runtime" } : {},
			...params.onCapabilityConsent ? { onCapabilityConsent: params.onCapabilityConsent } : {},
			beforePersistentEffect: params.beforePersistentEffect
		});
		if (shouldReplaceBrokenOfficialInstall) {
			const installedRecord = installed.records[candidate.pluginId];
			if (installed.records !== previousRecords && removalPath && (!installedRecord?.installPath || !installPathsEqual(resolveUserPath(installedRecord.installPath, env), removalPath))) try {
				await rm(removalPath, {
					recursive: true,
					force: true
				});
			} catch (error) {
				warnings.push(`Failed to remove broken installed plugin "${candidate.pluginId}" at ${removalPath}: ${String(error)}`);
			}
		}
		nextRecords = installed.records;
		changes.push(...installed.changes);
		notices.push(...installed.notices);
		if (!installed.failedPluginId && installed.records[candidate.pluginId]) {
			repairedPluginIds.add(candidate.pluginId);
			failedPlugins.delete(candidate.pluginId);
		}
		if (installed.failedPluginId) recordFailure(installed.failedPluginId, installed.warnings, installed.code);
		else warnings.push(...installed.warnings);
	}
	const persistedIndexOptions = {
		config: params.cfg,
		env
	};
	if (nextRecords !== persistedRecords || params.baselineRecords) {
		await params.beforePersistentEffect?.();
		await writePersistedInstalledPluginIndexInstallRecords(nextRecords, persistedIndexOptions);
	}
	const pluginInventoryChanged = nextRecords !== persistedRecords || repairedPluginIds.size > 0;
	const outcomes = [...failedPlugins.values()].filter((outcome) => outcome !== void 0);
	return {
		changes,
		warnings,
		...outcomes.length > 0 ? { outcomes } : {},
		...notices.length > 0 ? { notices } : {},
		...deferredRepairDetails.length > 0 ? { deferredRepairDetails } : {},
		...repairedPluginIds.size > 0 ? { repairedPluginIds: [...repairedPluginIds].toSorted((left, right) => left.localeCompare(right)) } : {},
		...pluginInventoryChanged ? { pluginInventoryChanged: true } : {},
		...failedPlugins.size > 0 ? { failedPluginIds: [...failedPlugins.keys()].toSorted((left, right) => left.localeCompare(right)) } : {},
		records: nextRecords
	};
}
//#endregion
export { detectConfiguredPluginInstallHealthIssues as a, configuredPluginInstallIssueToRepairEffect as i, repairMissingPluginInstallsForIds as n, collectConfiguredProviderPluginIds as o, configuredPluginInstallIssueToHealthFinding as r, repairMissingConfiguredPluginInstalls as t };