import { c as normalizeOptionalString } from "./string-coerce-Bje8XVt9.js";
import { n as resolveOpenClawPackageRootSync } from "./openclaw-root-BizNJX65.js";
import { d as resolveConfigDir, p as resolveUserPath } from "./utils-DvkbxKCZ.js";
import { a as resolvePackageExtensionEntries } from "./manifest-BRvO4Lcw.js";
import { r as validatePackageExtensionEntriesForInstall } from "./package-entry-resolution-BysVvIJY.js";
import { a as safePathSegmentHashed, i as safeDirName, n as packageNameMatchesId, o as unscopedPackageName, r as resolveSafeInstallDir } from "./install-safe-path-Bw-OXEL6.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/plugins/install-paths.ts
function safePluginInstallFileName(input) {
	return safeDirName(input);
}
function encodePluginInstallDirName(pluginId) {
	const trimmed = pluginId.trim();
	if (!trimmed.includes("/")) return safeDirName(trimmed);
	return `@${safePathSegmentHashed(trimmed)}`;
}
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;
}
function matchesExpectedPluginId(params) {
	if (!params.expectedPluginId) return true;
	if (params.expectedPluginId === params.pluginId) return true;
	return !params.manifestPluginId && params.pluginId === params.npmPluginId && params.expectedPluginId === unscopedPackageName(params.npmPluginId);
}
function resolveDefaultPluginExtensionsDir(env = process.env, homedir) {
	return path.join(resolveConfigDir(env, homedir), "extensions");
}
function resolvePluginInstallDir(pluginId, extensionsDir) {
	const extensionsBase = extensionsDir ? resolveUserPath(extensionsDir) : resolveDefaultPluginExtensionsDir();
	const pluginIdError = validatePluginId(pluginId);
	if (pluginIdError) throw new Error(pluginIdError);
	const targetDirResult = resolveSafeInstallDir({
		baseDir: extensionsBase,
		id: pluginId,
		invalidNameMessage: "invalid plugin name: path traversal detected",
		nameEncoder: encodePluginInstallDirName
	});
	if (!targetDirResult.ok) throw new Error(targetDirResult.error);
	return targetDirResult.path;
}
//#endregion
//#region src/plugins/install.ts
let pluginInstallRuntimePromise;
async function loadPluginInstallRuntime() {
	pluginInstallRuntimePromise ??= import("./install.runtime-DJLBZb3q.js");
	return pluginInstallRuntimePromise;
}
const MISSING_EXTENSIONS_ERROR = "package.json missing openclaw.extensions; update the plugin package to include openclaw.extensions (for example [\"./dist/index.js\"]). See https://docs.openclaw.ai/help/troubleshooting#plugin-install-fails-with-missing-openclaw-extensions";
const PLUGIN_ARCHIVE_ROOT_MARKERS = [
	"package.json",
	"openclaw.plugin.json",
	".codex-plugin/plugin.json",
	".claude-plugin/plugin.json",
	".cursor-plugin/plugin.json"
];
const PLUGIN_INSTALL_ERROR_CODE = {
	INVALID_NPM_SPEC: "invalid_npm_spec",
	INVALID_MIN_HOST_VERSION: "invalid_min_host_version",
	UNKNOWN_HOST_VERSION: "unknown_host_version",
	INCOMPATIBLE_HOST_VERSION: "incompatible_host_version",
	MISSING_OPENCLAW_EXTENSIONS: "missing_openclaw_extensions",
	MISSING_PLUGIN_MANIFEST: "missing_plugin_manifest",
	EMPTY_OPENCLAW_EXTENSIONS: "empty_openclaw_extensions",
	INVALID_OPENCLAW_EXTENSIONS: "invalid_openclaw_extensions",
	NPM_PACKAGE_NOT_FOUND: "npm_package_not_found",
	PLUGIN_ID_MISMATCH: "plugin_id_mismatch",
	SECURITY_SCAN_BLOCKED: "security_scan_blocked",
	SECURITY_SCAN_FAILED: "security_scan_failed"
};
const defaultLogger = {};
function ensureOpenClawExtensions(params) {
	const resolved = resolvePackageExtensionEntries(params.manifest);
	if (resolved.status === "missing") return {
		ok: false,
		error: MISSING_EXTENSIONS_ERROR,
		code: PLUGIN_INSTALL_ERROR_CODE.MISSING_OPENCLAW_EXTENSIONS
	};
	if (resolved.status === "empty") return {
		ok: false,
		error: "package.json openclaw.extensions is empty",
		code: PLUGIN_INSTALL_ERROR_CODE.EMPTY_OPENCLAW_EXTENSIONS
	};
	return {
		ok: true,
		entries: resolved.entries
	};
}
function isNpmPackageNotFoundMessage(error) {
	const normalized = error.trim();
	if (normalized.startsWith("Package not found on npm:")) return true;
	return /E404|404 not found|not in this registry/i.test(normalized);
}
function buildFileInstallResult(pluginId, targetFile) {
	return {
		ok: true,
		pluginId,
		targetDir: targetFile,
		manifestName: void 0,
		version: void 0,
		extensions: [path.basename(targetFile)]
	};
}
function buildDirectoryInstallResult(params) {
	return {
		ok: true,
		pluginId: params.pluginId,
		targetDir: params.targetDir,
		manifestName: params.manifestName,
		version: params.version,
		extensions: params.extensions
	};
}
function buildBlockedInstallResult(params) {
	return {
		ok: false,
		error: params.blocked.reason,
		...params.blocked.code === "security_scan_failed" ? { code: PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED } : params.blocked.code === "security_scan_blocked" ? { code: PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED } : {}
	};
}
function pickPackageInstallCommonParams(params) {
	return {
		dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
		extensionsDir: params.extensionsDir,
		timeoutMs: params.timeoutMs,
		logger: params.logger,
		mode: params.mode,
		dryRun: params.dryRun,
		expectedPluginId: params.expectedPluginId,
		requirePluginManifest: params.requirePluginManifest,
		installPolicyRequest: params.installPolicyRequest
	};
}
function pickFileInstallCommonParams(params) {
	return {
		dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
		extensionsDir: params.extensionsDir,
		logger: params.logger,
		mode: params.mode,
		dryRun: params.dryRun,
		installPolicyRequest: params.installPolicyRequest
	};
}
async function ensureInstallTargetAvailableForMode(params) {
	return await params.runtime.ensureInstallTargetAvailable({
		mode: params.mode,
		targetDir: params.targetPath,
		alreadyExistsError: `plugin already exists: ${params.targetPath} (delete it first)`
	});
}
async function resolvePreparedDirectoryInstallTarget(params) {
	const targetDirResult = await resolvePluginInstallTarget({
		runtime: params.runtime,
		pluginId: params.pluginId,
		extensionsDir: params.extensionsDir,
		nameEncoder: params.nameEncoder
	});
	if (!targetDirResult.ok) return targetDirResult;
	return {
		ok: true,
		target: {
			targetPath: targetDirResult.targetDir,
			effectiveMode: await resolveEffectiveInstallMode({
				runtime: params.runtime,
				requestedMode: params.requestedMode,
				targetPath: targetDirResult.targetDir
			})
		}
	};
}
async function runInstallSourceScan(params) {
	try {
		const scanResult = await params.scan();
		if (scanResult?.blocked) return buildBlockedInstallResult({ blocked: scanResult.blocked });
		return null;
	} catch (err) {
		return {
			ok: false,
			error: `${params.subject} installation blocked: code safety scan failed (${String(err)}). Run "openclaw security audit --deep" for details.`,
			code: PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED
		};
	}
}
async function installPluginDirectoryIntoExtensions(params) {
	const runtime = await loadPluginInstallRuntime();
	let targetDir = params.targetDir;
	if (!targetDir) {
		const targetDirResult = await resolvePluginInstallTarget({
			runtime,
			pluginId: params.pluginId,
			extensionsDir: params.extensionsDir,
			nameEncoder: params.nameEncoder
		});
		if (!targetDirResult.ok) return {
			ok: false,
			error: targetDirResult.error
		};
		targetDir = targetDirResult.targetDir;
	}
	const availability = await ensureInstallTargetAvailableForMode({
		runtime,
		targetPath: targetDir,
		mode: params.mode
	});
	if (!availability.ok) return availability;
	if (params.dryRun) return buildDirectoryInstallResult({
		pluginId: params.pluginId,
		targetDir,
		manifestName: params.manifestName,
		version: params.version,
		extensions: params.extensions
	});
	const installRes = await runtime.installPackageDir({
		sourceDir: params.sourceDir,
		targetDir,
		mode: params.mode,
		timeoutMs: params.timeoutMs,
		logger: params.logger,
		copyErrorPrefix: params.copyErrorPrefix,
		hasDeps: params.hasDeps,
		depsLogMessage: params.depsLogMessage,
		afterCopy: params.afterCopy,
		afterInstall: async (installedDir) => {
			const postInstallResult = await params.afterInstall?.(installedDir);
			if (!postInstallResult) return { ok: true };
			return {
				ok: false,
				error: postInstallResult.error,
				...postInstallResult.code ? { code: postInstallResult.code } : {}
			};
		}
	});
	if (!installRes.ok) return {
		ok: false,
		error: installRes.error,
		...installRes.code ? { code: installRes.code } : {}
	};
	return buildDirectoryInstallResult({
		pluginId: params.pluginId,
		targetDir,
		manifestName: params.manifestName,
		version: params.version,
		extensions: params.extensions
	});
}
async function resolvePluginInstallTarget(params) {
	const extensionsDir = params.extensionsDir ? resolveUserPath(params.extensionsDir) : resolveDefaultPluginExtensionsDir();
	return await params.runtime.resolveCanonicalInstallTarget({
		baseDir: extensionsDir,
		id: params.pluginId,
		invalidNameMessage: "invalid plugin name: path traversal detected",
		boundaryLabel: "extensions directory",
		nameEncoder: params.nameEncoder
	});
}
async function resolveEffectiveInstallMode(params) {
	if (params.requestedMode !== "update") return "install";
	return await params.runtime.fileExists(params.targetPath) ? "update" : "install";
}
async function installBundleFromSourceDir(params) {
	const runtime = await loadPluginInstallRuntime();
	const bundleFormat = runtime.detectBundleManifestFormat(params.sourceDir);
	if (!bundleFormat) return null;
	const { logger, timeoutMs, mode, dryRun } = runtime.resolveTimedInstallModeOptions(params, defaultLogger);
	const manifestRes = runtime.loadBundleManifest({
		rootDir: params.sourceDir,
		bundleFormat,
		rejectHardlinks: true
	});
	if (!manifestRes.ok) return {
		ok: false,
		error: manifestRes.error
	};
	const pluginId = manifestRes.manifest.id;
	const pluginIdError = validatePluginId(pluginId);
	if (pluginIdError) return {
		ok: false,
		error: pluginIdError
	};
	if (params.expectedPluginId && params.expectedPluginId !== pluginId) return {
		ok: false,
		error: `plugin id mismatch: expected ${params.expectedPluginId}, got ${pluginId}`,
		code: PLUGIN_INSTALL_ERROR_CODE.PLUGIN_ID_MISMATCH
	};
	const targetResult = await resolvePreparedDirectoryInstallTarget({
		runtime,
		pluginId,
		extensionsDir: params.extensionsDir,
		requestedMode: mode
	});
	if (!targetResult.ok) return {
		ok: false,
		error: targetResult.error
	};
	const scanResult = await runInstallSourceScan({
		subject: `Bundle "${pluginId}"`,
		scan: async () => await runtime.scanBundleInstallSource({
			dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
			sourceDir: params.sourceDir,
			pluginId,
			logger,
			requestKind: params.installPolicyRequest?.kind,
			requestedSpecifier: params.installPolicyRequest?.requestedSpecifier,
			mode: targetResult.target.effectiveMode,
			version: manifestRes.manifest.version
		})
	});
	if (scanResult) return scanResult;
	return await installPluginDirectoryIntoExtensions({
		sourceDir: params.sourceDir,
		pluginId,
		manifestName: manifestRes.manifest.name,
		version: manifestRes.manifest.version,
		extensions: [],
		targetDir: targetResult.target.targetPath,
		extensionsDir: params.extensionsDir,
		logger,
		timeoutMs,
		mode: targetResult.target.effectiveMode,
		dryRun,
		copyErrorPrefix: "failed to copy plugin bundle",
		hasDeps: false,
		depsLogMessage: ""
	});
}
async function installPluginFromSourceDir(params) {
	if (await detectNativePackageInstallSource(params.sourceDir)) return await installPluginFromPackageDir({
		packageDir: params.sourceDir,
		...pickPackageInstallCommonParams(params)
	});
	const bundleResult = await installBundleFromSourceDir({
		sourceDir: params.sourceDir,
		...pickPackageInstallCommonParams(params)
	});
	if (bundleResult) return bundleResult;
	return await installPluginFromPackageDir({
		packageDir: params.sourceDir,
		...pickPackageInstallCommonParams(params)
	});
}
async function detectNativePackageInstallSource(packageDir) {
	const runtime = await loadPluginInstallRuntime();
	const manifestPath = path.join(packageDir, "package.json");
	if (!await runtime.fileExists(manifestPath)) return false;
	try {
		return ensureOpenClawExtensions({ manifest: await runtime.readJsonFile(manifestPath) }).ok;
	} catch {
		return false;
	}
}
/**
* After the staged plugin tree has been scanned, symlink the host openclaw
* package for plugins that declare it as a peer dependency.
*/
async function linkOpenClawPeerDependencies(params) {
	const peers = Object.keys(params.peerDependencies).filter((name) => name === "openclaw");
	if (peers.length === 0) return;
	const hostRoot = resolveOpenClawPackageRootSync({
		argv1: process.argv[1],
		moduleUrl: import.meta.url,
		cwd: process.cwd()
	});
	if (!hostRoot) {
		params.logger.warn?.("Could not locate openclaw package root to symlink peerDependencies; plugin may fail to resolve openclaw at runtime.");
		return;
	}
	const nodeModulesDir = path.join(params.installedDir, "node_modules");
	await fs.mkdir(nodeModulesDir, { recursive: true });
	for (const peerName of peers) {
		const linkPath = path.join(nodeModulesDir, peerName);
		try {
			await fs.rm(linkPath, {
				recursive: true,
				force: true
			});
			await fs.symlink(hostRoot, linkPath, "junction");
			params.logger.info?.(`Linked peerDependency "${peerName}" -> ${hostRoot}`);
		} catch (err) {
			params.logger.warn?.(`Failed to symlink peerDependency "${peerName}": ${String(err)}`);
		}
	}
}
async function installPluginFromPackageDir(params) {
	const runtime = await loadPluginInstallRuntime();
	const { logger, timeoutMs, mode, dryRun } = runtime.resolveTimedInstallModeOptions(params, defaultLogger);
	const manifestPath = path.join(params.packageDir, "package.json");
	if (!await runtime.fileExists(manifestPath)) return {
		ok: false,
		error: "extracted package missing package.json"
	};
	let manifest;
	try {
		manifest = await runtime.readJsonFile(manifestPath);
	} catch (err) {
		return {
			ok: false,
			error: `invalid package.json: ${String(err)}`
		};
	}
	const extensionsResult = ensureOpenClawExtensions({ manifest });
	if (!extensionsResult.ok) return {
		ok: false,
		error: extensionsResult.error,
		code: extensionsResult.code
	};
	const extensions = extensionsResult.entries;
	const pkgName = normalizeOptionalString(manifest.name) ?? "";
	const npmPluginId = pkgName || "plugin";
	const ocManifestResult = runtime.loadPluginManifest(params.packageDir);
	if (!ocManifestResult.ok && params.requirePluginManifest) return {
		ok: false,
		error: `package missing valid openclaw.plugin.json: ${ocManifestResult.error}`,
		code: PLUGIN_INSTALL_ERROR_CODE.MISSING_PLUGIN_MANIFEST
	};
	const manifestPluginId = ocManifestResult.ok && ocManifestResult.manifest.id ? ocManifestResult.manifest.id.trim() : void 0;
	const pluginId = manifestPluginId ?? npmPluginId;
	const pluginIdError = validatePluginId(pluginId);
	if (pluginIdError) return {
		ok: false,
		error: pluginIdError
	};
	if (!matchesExpectedPluginId({
		expectedPluginId: params.expectedPluginId,
		pluginId,
		manifestPluginId,
		npmPluginId
	})) return {
		ok: false,
		error: `plugin id mismatch: expected ${params.expectedPluginId}, got ${pluginId}`,
		code: PLUGIN_INSTALL_ERROR_CODE.PLUGIN_ID_MISMATCH
	};
	if (manifestPluginId && !packageNameMatchesId(npmPluginId, manifestPluginId)) logger.info?.(`Plugin manifest id "${manifestPluginId}" differs from npm package name "${npmPluginId}"; using manifest id as the config key.`);
	const packageMetadata = runtime.getPackageManifestMetadata(manifest);
	const minHostVersionCheck = runtime.checkMinHostVersion({
		currentVersion: runtime.resolveCompatibilityHostVersion(),
		minHostVersion: packageMetadata?.install?.minHostVersion
	});
	if (!minHostVersionCheck.ok) {
		if (minHostVersionCheck.kind === "invalid") return {
			ok: false,
			error: `invalid package.json openclaw.install.minHostVersion: ${minHostVersionCheck.error}`,
			code: PLUGIN_INSTALL_ERROR_CODE.INVALID_MIN_HOST_VERSION
		};
		if (minHostVersionCheck.kind === "unknown_host_version") return {
			ok: false,
			error: `plugin "${pluginId}" requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host version could not be determined. Re-run from a released build or set OPENCLAW_VERSION and retry.`,
			code: PLUGIN_INSTALL_ERROR_CODE.UNKNOWN_HOST_VERSION
		};
		return {
			ok: false,
			error: `plugin "${pluginId}" requires OpenClaw >=${minHostVersionCheck.requirement.minimumLabel}, but this host is ${minHostVersionCheck.currentVersion}. Upgrade OpenClaw and retry.`,
			code: PLUGIN_INSTALL_ERROR_CODE.INCOMPATIBLE_HOST_VERSION
		};
	}
	const extensionValidation = await validatePackageExtensionEntriesForInstall({
		packageDir: params.packageDir,
		extensions,
		manifest
	});
	if (!extensionValidation.ok) return {
		ok: false,
		error: extensionValidation.error,
		code: PLUGIN_INSTALL_ERROR_CODE.INVALID_OPENCLAW_EXTENSIONS
	};
	const targetResult = await resolvePreparedDirectoryInstallTarget({
		runtime,
		pluginId,
		extensionsDir: params.extensionsDir,
		requestedMode: mode,
		nameEncoder: encodePluginInstallDirName
	});
	if (!targetResult.ok) return {
		ok: false,
		error: targetResult.error
	};
	const scanResult = await runInstallSourceScan({
		subject: `Plugin "${pluginId}"`,
		scan: async () => await runtime.scanPackageInstallSource({
			dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
			packageDir: params.packageDir,
			pluginId,
			logger,
			extensions,
			requestKind: params.installPolicyRequest?.kind,
			requestedSpecifier: params.installPolicyRequest?.requestedSpecifier,
			mode: targetResult.target.effectiveMode,
			packageName: pkgName || void 0,
			manifestId: manifestPluginId,
			version: typeof manifest.version === "string" ? manifest.version : void 0
		})
	});
	if (scanResult) return scanResult;
	const deps = {
		...manifest.dependencies,
		...manifest.optionalDependencies
	};
	const peerDeps = manifest.peerDependencies ?? {};
	return await installPluginDirectoryIntoExtensions({
		sourceDir: params.packageDir,
		pluginId,
		manifestName: pkgName || void 0,
		version: typeof manifest.version === "string" ? manifest.version : void 0,
		extensions,
		targetDir: targetResult.target.targetPath,
		extensionsDir: params.extensionsDir,
		logger,
		timeoutMs,
		mode: targetResult.target.effectiveMode,
		dryRun,
		copyErrorPrefix: "failed to copy plugin",
		hasDeps: Object.keys(deps).length > 0,
		depsLogMessage: "Installing plugin dependencies…",
		nameEncoder: encodePluginInstallDirName,
		afterInstall: async (installedDir) => {
			const scanResult = await runInstallSourceScan({
				subject: `Plugin "${pluginId}"`,
				scan: async () => await runtime.scanInstalledPackageDependencyTree({
					logger,
					packageDir: installedDir,
					pluginId
				})
			});
			if (scanResult) return scanResult;
			await linkOpenClawPeerDependencies({
				installedDir,
				peerDependencies: peerDeps,
				logger
			});
			return null;
		}
	});
}
async function installPluginFromArchive(params) {
	const runtime = await loadPluginInstallRuntime();
	const logger = params.logger ?? defaultLogger;
	const timeoutMs = params.timeoutMs ?? 12e4;
	const mode = params.mode ?? "install";
	const installPolicyRequest = params.installPolicyRequest ?? {
		kind: "plugin-archive",
		requestedSpecifier: params.archivePath
	};
	const archivePathResult = await runtime.resolveArchiveSourcePath(params.archivePath);
	if (!archivePathResult.ok) return archivePathResult;
	const archivePath = archivePathResult.path;
	return await runtime.withExtractedArchiveRoot({
		archivePath,
		tempDirPrefix: "openclaw-plugin-",
		timeoutMs,
		logger,
		rootMarkers: PLUGIN_ARCHIVE_ROOT_MARKERS,
		onExtracted: async (sourceDir) => await installPluginFromSourceDir({
			sourceDir,
			...pickPackageInstallCommonParams({
				dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
				extensionsDir: params.extensionsDir,
				timeoutMs,
				logger,
				mode,
				dryRun: params.dryRun,
				expectedPluginId: params.expectedPluginId,
				requirePluginManifest: true,
				installPolicyRequest
			})
		})
	});
}
async function installPluginFromDir(params) {
	const runtime = await loadPluginInstallRuntime();
	const dirPath = resolveUserPath(params.dirPath);
	const installPolicyRequest = params.installPolicyRequest ?? {
		kind: "plugin-dir",
		requestedSpecifier: params.dirPath
	};
	if (!await runtime.fileExists(dirPath)) return {
		ok: false,
		error: `directory not found: ${dirPath}`
	};
	if (!(await fs.stat(dirPath)).isDirectory()) return {
		ok: false,
		error: `not a directory: ${dirPath}`
	};
	return await installPluginFromSourceDir({
		sourceDir: dirPath,
		...pickPackageInstallCommonParams({
			...params,
			installPolicyRequest
		})
	});
}
async function installPluginFromFile(params) {
	const runtime = await loadPluginInstallRuntime();
	const { logger, mode, dryRun } = runtime.resolveInstallModeOptions(params, defaultLogger);
	const filePath = resolveUserPath(params.filePath);
	const installPolicyRequest = params.installPolicyRequest ?? {
		kind: "plugin-file",
		requestedSpecifier: params.filePath
	};
	if (!await runtime.fileExists(filePath)) return {
		ok: false,
		error: `file not found: ${filePath}`
	};
	const extensionsDir = params.extensionsDir ? resolveUserPath(params.extensionsDir) : resolveDefaultPluginExtensionsDir();
	await fs.mkdir(extensionsDir, { recursive: true });
	const pluginId = path.basename(filePath, path.extname(filePath)) || "plugin";
	const pluginIdError = validatePluginId(pluginId);
	if (pluginIdError) return {
		ok: false,
		error: pluginIdError
	};
	const targetFile = path.join(extensionsDir, `${safePluginInstallFileName(pluginId)}${path.extname(filePath)}`);
	const preparedTarget = {
		targetPath: targetFile,
		effectiveMode: await resolveEffectiveInstallMode({
			runtime,
			requestedMode: mode,
			targetPath: targetFile
		})
	};
	const availability = await ensureInstallTargetAvailableForMode({
		runtime,
		targetPath: preparedTarget.targetPath,
		mode: preparedTarget.effectiveMode
	});
	if (!availability.ok) return availability;
	if (dryRun) return buildFileInstallResult(pluginId, preparedTarget.targetPath);
	const scanResult = await runInstallSourceScan({
		subject: `Plugin file "${pluginId}"`,
		scan: async () => await runtime.scanFileInstallSource({
			dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
			filePath,
			logger,
			mode: preparedTarget.effectiveMode,
			pluginId,
			requestedSpecifier: installPolicyRequest.requestedSpecifier
		})
	});
	if (scanResult) return scanResult;
	logger.info?.(`Installing to ${preparedTarget.targetPath}…`);
	try {
		await runtime.writeFileFromPathWithinRoot({
			rootDir: extensionsDir,
			relativePath: path.basename(preparedTarget.targetPath),
			sourcePath: filePath
		});
	} catch (err) {
		return {
			ok: false,
			error: String(err)
		};
	}
	return buildFileInstallResult(pluginId, preparedTarget.targetPath);
}
async function installPluginFromNpmSpec(params) {
	const runtime = await loadPluginInstallRuntime();
	const { logger, timeoutMs, mode, dryRun } = runtime.resolveTimedInstallModeOptions(params, defaultLogger);
	const expectedPluginId = params.expectedPluginId;
	const spec = params.spec.trim();
	const specError = runtime.validateRegistryNpmSpec(spec);
	if (specError) return {
		ok: false,
		error: specError,
		code: PLUGIN_INSTALL_ERROR_CODE.INVALID_NPM_SPEC
	};
	logger.info?.(`Downloading ${spec}…`);
	const installPolicyRequest = {
		kind: "plugin-npm",
		requestedSpecifier: spec
	};
	const flowResult = await runtime.installFromNpmSpecArchiveWithInstaller({
		tempDirPrefix: "openclaw-npm-pack-",
		spec,
		timeoutMs,
		expectedIntegrity: params.expectedIntegrity,
		onIntegrityDrift: params.onIntegrityDrift,
		warn: (message) => {
			logger.warn?.(message);
		},
		installFromArchive: installPluginFromArchive,
		archiveInstallParams: {
			dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
			extensionsDir: params.extensionsDir,
			timeoutMs,
			logger,
			mode,
			dryRun,
			expectedPluginId,
			installPolicyRequest
		}
	});
	const finalized = runtime.finalizeNpmSpecArchiveInstall(flowResult);
	if (!finalized.ok && isNpmPackageNotFoundMessage(finalized.error)) return {
		ok: false,
		error: finalized.error,
		code: PLUGIN_INSTALL_ERROR_CODE.NPM_PACKAGE_NOT_FOUND
	};
	return finalized;
}
async function installPluginFromPath(params) {
	const runtime = await loadPluginInstallRuntime();
	const pathResult = await runtime.resolveExistingInstallPath(params.path);
	if (!pathResult.ok) return pathResult;
	const { resolvedPath: resolved, stat } = pathResult;
	const packageInstallOptions = pickPackageInstallCommonParams(params);
	if (stat.isDirectory()) return await installPluginFromDir({
		dirPath: resolved,
		...packageInstallOptions,
		installPolicyRequest: {
			kind: "plugin-dir",
			requestedSpecifier: params.path
		}
	});
	if (runtime.resolveArchiveKind(resolved)) return await installPluginFromArchive({
		archivePath: resolved,
		...packageInstallOptions,
		installPolicyRequest: {
			kind: "plugin-archive",
			requestedSpecifier: params.path
		}
	});
	return await installPluginFromFile({
		filePath: resolved,
		...pickFileInstallCommonParams({
			...params,
			installPolicyRequest: {
				kind: "plugin-file",
				requestedSpecifier: params.path
			}
		})
	});
}
//#endregion
export { resolveDefaultPluginExtensionsDir as a, installPluginFromPath as i, installPluginFromArchive as n, resolvePluginInstallDir as o, installPluginFromNpmSpec as r, PLUGIN_INSTALL_ERROR_CODE as t };
