UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,568 lines 64.6 kB
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { n as isNotFoundPathError, r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { u as resolveAgentDir, y as resolveDefaultAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { r as normalizeProviderId } from "./provider-id-DMd-TDFp.js";
import { g as resolveGatewayLockDir } from "./paths-D2sRr1a_.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { l as withConsoleSubsystemsSuppressed } from "./console-BSaonU6s.js";
import { g as writeJsonAtomic, r as readDurableJsonFile } from "./json-files-Bq1lIlQB.js";
import { r as closeOpenClawStateDatabaseByPath } from "./openclaw-state-db-cache-C7ljO0xP.js";
import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db-schema-version-c1ZL6JGz.js";
import "./openclaw-state-db-BRTnL-D8.js";
import { t as migratePersistedImplicitMainRoster } from "./legacy.roster-CQ9vDbV2.js";
import { a as resolveAgentModelPrimaryValue } from "./model-input-BuGMCNOz.js";
import "./agent-scope-DbtJyKUL.js";
import { i as listAvailableManifestContractPlugins, o as loadManifestContractSnapshot } from "./manifest-contract-eligibility-BbV7X6pV.js";
import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js";
import { d as registerOpenClawAgentDatabase, f as unregisterOpenClawAgentDatabase } from "./openclaw-agent-db-maintenance-wTIy-jt-.js";
import { p as openOpenClawAgentDatabase, s as disposeOpenClawAgentDatabaseByPath } from "./openclaw-agent-db-CWtDoRbC.js";
import { n as clearRuntimeAuthProfileStoreSnapshot } from "./store-F1B2duCT.js";
import { n as t } from "./i18n-hynzGFbD.js";
import { t as applyAutoLocalModelLean } from "./local-model-lean-auto-DgTezp-6.js";
import { n as WizardCancelledError } from "./prompts-DLsO8MlU.js";
import { a as prepareAgentModelDefaults, c as resolveOnboardingSetupTarget, n as applyOnboardingPrimaryModel, o as projectAgentModelDefaults } from "./onboard-agent-target-CNKxVAZK.js";
import { y as summarizeMigrationItems } from "./migration-q6QzXKht.js";
import { s as writeMigrationReport } from "./migration-runtime-C428wS8a.js";
import { n as runWizardWithPromptNavigationScope } from "./navigation-prompter-D98bFrMO.js";
import { a as buildSetupMigrationPlanSourceSnapshot, c as prepareSetupMigrationAttemptBoundary, d as hashSetupMigrationConfig, i as assertFreshSetupMigrationTarget, l as preserveSetupMigrationOnboardingConsents, n as SetupMigrationTargetChangedError, o as buildSetupMigrationTargetSnapshot, s as inspectSetupMigrationFreshness, u as withSetupMigrationTargetLock } from "./setup.migration-snapshot-DEcmXjFO.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/wizard/setup.model-auth.ts
const loadAuthChoiceModule = createLazyRuntimeModule(() => import("./auth-choice-CiJJPKKB.js"));
const loadModelPickerModule = createLazyRuntimeModule(() => import("./model-picker-DlX6GVC5.js"));
async function resolveAuthChoiceModelSelectionPolicy(params) {
	const preferredProvider = await params.resolvePreferredProviderForAuthChoice({
		choice: params.authChoice,
		config: params.config,
		workspaceDir: params.workspaceDir,
		env: params.env
	});
	const [{ resolveManifestProviderAuthChoice }, { resolvePluginSetupProviderCore }] = await Promise.all([import("./provider-auth-choices-W7cUgCtR.js"), import("./setup-registry-B9WRC0qr.js")]);
	const manifestChoice = resolveManifestProviderAuthChoice(params.authChoice, {
		config: params.config,
		workspaceDir: params.workspaceDir,
		env: params.env,
		includeUntrustedWorkspacePlugins: false
	});
	if (manifestChoice) {
		const setupProvider = resolvePluginSetupProviderCore({
			provider: manifestChoice.providerId,
			config: params.config,
			workspaceDir: params.workspaceDir,
			env: params.env,
			pluginIds: [manifestChoice.pluginId]
		});
		const setupPolicy = (setupProvider?.auth.find((method) => normalizeProviderId(method.id) === normalizeProviderId(manifestChoice.methodId)))?.wizard?.modelSelection ?? setupProvider?.wizard?.setup?.modelSelection;
		return {
			preferredProvider,
			promptWhenAuthChoiceProvided: setupPolicy?.promptWhenAuthChoiceProvided === true,
			allowKeepCurrent: setupPolicy?.allowKeepCurrent ?? true
		};
	}
	const { resolvePluginProviders, resolveProviderPluginChoice } = await import("./provider-auth-choice.runtime.js");
	const providers = resolvePluginProviders({
		config: params.config,
		workspaceDir: params.workspaceDir,
		env: params.env,
		mode: "setup"
	});
	const resolvedChoice = resolveProviderPluginChoice({
		providers,
		choice: params.authChoice
	});
	const matchedProvider = resolvedChoice?.provider ?? (() => {
		const preferredId = preferredProvider?.trim();
		if (!preferredId) return;
		return providers.find((provider) => typeof provider.id === "string" && provider.id.trim() === preferredId);
	})();
	const setupPolicy = resolvedChoice?.wizard?.modelSelection ?? matchedProvider?.wizard?.setup?.modelSelection;
	return {
		preferredProvider,
		promptWhenAuthChoiceProvided: setupPolicy?.promptWhenAuthChoiceProvided === true,
		allowKeepCurrent: setupPolicy?.allowKeepCurrent ?? true
	};
}
/**
* Run the provider auth-choice + default-model selection loop. When
* `opts.authChoice` is set the prompt is skipped and the flag drives the flow
* (public onboarding automation contract).
*/
async function runSetupModelAuthStep(params) {
	const { opts, prompter, runtime } = params;
	const env = params.stateDir ? {
		...process.env,
		OPENCLAW_STATE_DIR: params.stateDir
	} : void 0;
	let nextConfig = params.stagedCandidate?.config ?? params.config;
	let replacementBaseConfig = params.config;
	let authProfiles = params.stagedCandidate?.authProfiles ?? [];
	let persistAuthProfiles = params.stagedCandidate?.persistAuthProfiles ?? (async () => {});
	const authChoiceFromPrompt = opts.authChoice === void 0;
	let authChoice = opts.authChoice;
	let authStore;
	let promptAuthChoiceGrouped;
	let isKeepCurrentAuthChoice;
	let detectedProviderIds;
	const target = resolveOnboardingSetupTarget(params.config, params.pendingAgent);
	if (authChoiceFromPrompt) {
		const [{ ensureAuthProfileStore }, { promptAuthChoiceGrouped: promptAuthChoice, isKeepCurrentAuthChoice: isKeepCurrentChoice }, { detectAvailableSetupProviderIds }] = await Promise.all([
			import("./agents/auth-profiles.runtime.js"),
			import("./auth-choice-prompt-OYub_ykx.js"),
			import("./provider-setup-availability-BaQOoNDv.js")
		]);
		promptAuthChoiceGrouped = promptAuthChoice;
		isKeepCurrentAuthChoice = isKeepCurrentChoice;
		authStore = ensureAuthProfileStore(params.agentDir ?? target.agentDir, {
			allowKeychainPrompt: false,
			readOnly: true
		});
		detectedProviderIds = await detectAvailableSetupProviderIds({
			config: nextConfig,
			workspaceDir: target.workspaceDir,
			env
		});
	}
	while (true) {
		if (authChoiceFromPrompt) authChoice = await promptAuthChoiceGrouped({
			prompter,
			store: authStore,
			includeSkip: true,
			config: nextConfig,
			workspaceDir: target.workspaceDir,
			allowKeepCurrentProvider: true,
			detectedProviderIds
		});
		if (authChoice === void 0) throw new WizardCancelledError(t("wizard.setup.authChoiceRequired"));
		if (isKeepCurrentAuthChoice?.(authChoice)) break;
		nextConfig = replacementBaseConfig;
		authProfiles = [];
		persistAuthProfiles = async () => {};
		if (authChoice === "custom-api-key") {
			const { promptCustomApiConfig } = await import("./onboard-custom-CDT-eaYr.js");
			nextConfig = (await promptCustomApiConfig({
				prompter,
				runtime,
				config: nextConfig,
				target,
				secretInputMode: opts.secretInputMode,
				setAsPrimary: !params.preserveExistingModelSelection
			})).config;
			prompter.disableBackNavigation?.();
			break;
		}
		if (authChoice === "skip") {
			if (authChoiceFromPrompt) {
				const { promptDefaultModel } = await loadModelPickerModule();
				const modelSelection = await promptDefaultModel({
					config: nextConfig,
					prompter,
					allowKeep: true,
					ignoreAllowlist: true,
					includeProviderPluginSetups: false,
					loadCatalog: false,
					agentId: target.agentId,
					agentDir: target.agentDir,
					workspaceDir: target.workspaceDir,
					runtime
				});
				if (modelSelection.config) nextConfig = modelSelection.config;
				if (modelSelection.model) nextConfig = applyOnboardingPrimaryModel(nextConfig, target, modelSelection.model);
				const { warnIfModelConfigLooksOff } = await loadAuthChoiceModule();
				await warnIfModelConfigLooksOff(nextConfig, prompter, {
					agentId: target.agentId,
					agentDir: target.agentDir,
					validateCatalog: false
				});
			}
			break;
		}
		const [{ prepareAuthChoice, resolvePreferredProviderForAuthChoice, warnIfModelConfigLooksOff }, { promptDefaultModel }] = await Promise.all([loadAuthChoiceModule(), loadModelPickerModule()]);
		prompter.disableBackNavigation?.();
		const agentScopedModels = nextConfig.agents?.ownership === "explicit";
		let authResult;
		try {
			authResult = await prepareAuthChoice({
				authChoice,
				config: agentScopedModels ? prepareAgentModelDefaults(nextConfig, target) : nextConfig,
				prompter,
				runtime,
				agentId: target.agentId,
				agentDir: params.agentDir ?? target.agentDir,
				workspaceDir: target.workspaceDir,
				setDefaultModel: true,
				preserveExistingDefaultModel: true,
				env,
				opts: {
					...opts,
					token: opts.authChoice === "apiKey" && opts.token ? opts.token : void 0
				}
			});
		} catch (error) {
			if (error instanceof WizardCancelledError || !authChoiceFromPrompt) throw error;
			await prompter.note([formatErrorMessage(error), t("wizard.setup.authChoiceFailedRetry")].join("\n"), t("wizard.setup.authChoiceFailedTitle"));
			continue;
		}
		nextConfig = agentScopedModels ? projectAgentModelDefaults(nextConfig, target, authResult.config) : authResult.config;
		authProfiles = authResult.authProfiles;
		persistAuthProfiles = authResult.persistAuthProfiles;
		if (authResult.retrySelection) {
			if (authChoiceFromPrompt) {
				replacementBaseConfig = nextConfig;
				continue;
			}
			break;
		}
		if (authResult.agentModelOverride) nextConfig = applyOnboardingPrimaryModel(nextConfig, target, authResult.agentModelOverride);
		const authChoiceModelSelectionPolicy = await resolveAuthChoiceModelSelectionPolicy({
			authChoice,
			config: nextConfig,
			workspaceDir: target.workspaceDir,
			resolvePreferredProviderForAuthChoice
		});
		if (authChoiceFromPrompt || authChoiceModelSelectionPolicy.promptWhenAuthChoiceProvided && (!params.preserveExistingModelSelection || !authChoiceModelSelectionPolicy.allowKeepCurrent)) {
			const modelSelection = await promptDefaultModel({
				config: nextConfig,
				prompter,
				allowKeep: authChoiceModelSelectionPolicy?.allowKeepCurrent ?? true,
				ignoreAllowlist: true,
				includeProviderPluginSetups: true,
				preferredProvider: authChoiceModelSelectionPolicy?.preferredProvider,
				browseCatalogOnDemand: true,
				agentId: target.agentId,
				agentDir: target.agentDir,
				workspaceDir: target.workspaceDir,
				runtime
			});
			if (modelSelection.config) nextConfig = modelSelection.config;
			if (modelSelection.model) nextConfig = applyOnboardingPrimaryModel(nextConfig, target, modelSelection.model);
		}
		await warnIfModelConfigLooksOff(nextConfig, prompter, {
			agentId: target.agentId,
			agentDir: target.agentDir,
			pendingAuthProfiles: authProfiles,
			validateCatalog: false
		});
		break;
	}
	if (authChoice !== "skip" && !isKeepCurrentAuthChoice?.(authChoice)) {
		const { resolveDefaultModelForAgent } = await import("./model-selection-config-Xf7Xd1Mr.js");
		const selected = resolveDefaultModelForAgent({
			cfg: nextConfig,
			agentId: target.agentId
		});
		nextConfig = applyAutoLocalModelLean({
			config: nextConfig,
			providerId: selected.provider,
			modelRef: `${selected.provider}/${selected.model}`,
			previousModelRef: resolveAgentModelPrimaryValue(prepareAgentModelDefaults(params.config, target).agents?.defaults?.model)
		}).config;
	}
	return {
		config: nextConfig,
		authProfiles,
		persistAuthProfiles
	};
}
//#endregion
//#region src/wizard/setup.inference-verification.ts
async function offerLiveModelVerification(params) {
	const requiresCandidateVerification = (config) => {
		const provider = resolveDefaultModelForAgent({ cfg: config }).provider;
		return params.opts.nonInteractive !== true && config.models?.providers?.[provider]?.localService !== void 0;
	};
	let required = params.required || params.initialCandidate !== void 0 && requiresCandidateVerification(params.initialCandidate.config);
	if (!required) {
		if (!await params.prompter.confirm({
			message: t("wizard.setup.testAiAccess"),
			initialValue: true
		})) return {
			config: params.config,
			attempted: false,
			persisted: false,
			verified: false
		};
	}
	const [inference, authStore, agentDatabase] = await Promise.all([
		import("./system-agent/setup-inference.js"),
		import("./store-DNxWSPNc.js"),
		import("./openclaw-agent-db-Bfc4Wmxr.js")
	]);
	const stagedEnv = params.stateDir ? {
		...process.env,
		OPENCLAW_STATE_DIR: params.stateDir
	} : void 0;
	let shouldPersistCandidate = params.initialCandidate !== void 0;
	const verify = async (candidate) => {
		const progress = params.prompter.progress(t("wizard.setup.testAiProgress"));
		const verification = withConsoleSubsystemsSuppressed(() => inference.verifySetupInferenceConfig({
			config: migratePersistedImplicitMainRoster(candidate.config).config,
			runtime: params.runtime,
			authProfiles: candidate.authProfiles,
			verifyAgentTools: shouldPersistCandidate && params.opts.nonInteractive !== true,
			...params.agentDir ? { agentDir: params.agentDir } : {},
			...params.stateDir ? { deps: {
				updateAuthProfileStoreWithLock: async (updateParams) => await authStore.updateAuthProfileStoreWithLock({
					...updateParams,
					stateDir: params.stateDir
				}),
				disposeOpenClawAgentDatabaseByPath: (pathname) => agentDatabase.disposeOpenClawAgentDatabaseByPath(pathname, { env: stagedEnv })
			} } : {}
		}));
		let result;
		try {
			result = await verification;
		} finally {
			progress.stop();
		}
		if (result.ok) await params.prompter.note(t("wizard.setup.testAiSuccess", { seconds: (result.latencyMs / 1e3).toFixed(1) }), t("wizard.setup.testAiTitle"));
		else await params.prompter.note(t("wizard.setup.testAiFailure", { reason: result.error }), t("wizard.setup.testAiTitle"));
		return result;
	};
	let candidate = params.initialCandidate ?? {
		config: params.config,
		authProfiles: [],
		persistAuthProfiles: async () => {}
	};
	while (true) {
		const result = await verify(candidate);
		if (result.ok) {
			if (!shouldPersistCandidate) return {
				config: params.config,
				attempted: true,
				persisted: false,
				verified: true,
				modelRef: result.modelRef
			};
			await candidate.persistAuthProfiles(result.authProfiles);
			return {
				config: await params.writeConfig(candidate.config),
				attempted: true,
				persisted: true,
				verified: true,
				modelRef: result.modelRef
			};
		}
		if (result.authProfiles) candidate.authProfiles = result.authProfiles;
		if (params.opts.nonInteractive) return {
			config: params.config,
			attempted: true,
			persisted: false,
			verified: false
		};
		if (!required && await params.prompter.select({
			message: t("wizard.setup.testAiFailureChoice"),
			options: [{
				value: "fix",
				label: t("wizard.setup.testAiFix")
			}, {
				value: "continue",
				label: t("wizard.setup.testAiContinue")
			}]
		}) === "continue") return {
			config: params.config,
			attempted: true,
			persisted: false,
			verified: false
		};
		candidate = await runSetupModelAuthStep({
			config: params.config,
			stagedCandidate: candidate,
			opts: {
				...params.opts,
				authChoice: void 0
			},
			prompter: params.prompter,
			runtime: params.runtime,
			...params.agentDir ? { agentDir: params.agentDir } : {},
			...params.stateDir ? { stateDir: params.stateDir } : {}
		});
		shouldPersistCandidate = true;
		required ||= requiresCandidateVerification(candidate.config);
	}
}
//#endregion
//#region src/wizard/setup.migration-promotion.ts
const PROMOTION_JOURNAL_FILE = "onboarding-promotion.json";
/** Counts dangling leaf entries as present and propagates non-missing path errors. */
async function migrationPathEntryExists(candidate) {
	try {
		await fs.lstat(candidate);
		return true;
	} catch (error) {
		if (isNotFoundPathError(error)) return false;
		throw error;
	}
}
async function readLatestPromotionJournal(params) {
	const root = path.join(params.stateDir, "migration", params.providerId);
	let entries;
	try {
		entries = await fs.readdir(root, { withFileTypes: true });
	} catch (error) {
		if (isNotFoundPathError(error)) return;
		throw error;
	}
	for (const entry of entries.filter((candidate) => candidate.isDirectory()).toSorted((left, right) => right.name.localeCompare(left.name))) {
		const journalPath = path.join(root, entry.name, PROMOTION_JOURNAL_FILE);
		const value = await readDurableJsonFile(journalPath);
		if (value?.version === 1 && value.providerId === params.providerId) return {
			path: journalPath,
			journal: value
		};
	}
}
async function writePromotionJournal(journalPath, journal) {
	await writeJsonAtomic(journalPath, {
		...journal,
		updatedAt: (/* @__PURE__ */ new Date()).toISOString()
	}, {
		mode: 384,
		dirMode: 448,
		trailingNewline: true
	});
}
async function copyPromotionReportArtifacts(params) {
	let entries;
	try {
		entries = await fs.readdir(params.stagedReportDir, { withFileTypes: true });
	} catch (error) {
		if (isNotFoundPathError(error)) return;
		throw error;
	}
	await fs.mkdir(params.reportDir, {
		recursive: true,
		mode: 448
	});
	for (const entry of entries) {
		if (entry.name === "report.json" || entry.name === "summary.md" || entry.name === "onboarding-promotion.json") continue;
		await fs.cp(path.join(params.stagedReportDir, entry.name), path.join(params.reportDir, entry.name), {
			recursive: true,
			force: true
		});
	}
}
async function cleanupPromotionStaging(continuation) {
	await Promise.all(continuation.stagedRoots.map(async (root) => await fs.rm(root, {
		recursive: true,
		force: true
	})));
}
function createPromotionResume(journalPath, journal) {
	const continuation = journal.continuation;
	if (!continuation) throw new Error(`Onboarding migration continuation is missing from ${journalPath}.`);
	return {
		journalPath,
		continuation,
		copyReportArtifacts: async () => await copyPromotionReportArtifacts({
			stagedReportDir: continuation.stagedReportDir,
			reportDir: path.dirname(journalPath)
		}),
		async saveDeferredResult(result) {
			continuation.deferredResult = result;
			await writePromotionJournal(journalPath, journal);
		},
		async complete() {
			journal.status = "completed";
			await writePromotionJournal(journalPath, journal);
		},
		async acknowledge() {
			await Promise.all(journal.components.map(async (component) => {
				if (component.emptyTargetBackupPath) await fs.rm(component.emptyTargetBackupPath, {
					recursive: true,
					force: true
				});
			}));
			await fs.rm(journalPath, { force: true });
		},
		cleanup: async () => await cleanupPromotionStaging(continuation)
	};
}
async function removeCreatedPromotionParents(components) {
	const parents = [...new Set(components.flatMap((component) => component.createdParentPaths ?? []))].toSorted((left, right) => right.length - left.length || right.localeCompare(left));
	for (const parent of parents) try {
		await fs.rmdir(parent);
	} catch (error) {
		if (isNotFoundPathError(error)) continue;
		throw error;
	}
}
async function rollbackComponents(components) {
	try {
		for (const component of components.toReversed()) {
			const stagedExists = await migrationPathEntryExists(component.stagedPath);
			const finalExists = await migrationPathEntryExists(component.finalPath);
			const backupExists = component.emptyTargetBackupPath ? await migrationPathEntryExists(component.emptyTargetBackupPath) : false;
			if (!stagedExists && !finalExists) return false;
			if (finalExists && !stagedExists) {
				await fs.mkdir(path.dirname(component.stagedPath), {
					recursive: true,
					mode: 448
				});
				await fs.rename(component.finalPath, component.stagedPath);
			} else if (finalExists && stagedExists) {
				if (backupExists || !component.targetWasEmptyDirectory || (await fs.readdir(component.finalPath)).length > 0) return false;
			}
			if (backupExists) {
				if (await migrationPathEntryExists(component.finalPath)) return false;
				await fs.rename(component.emptyTargetBackupPath, component.finalPath);
			} else if (component.targetWasEmptyDirectory) await fs.mkdir(component.finalPath, {
				recursive: true,
				mode: 448
			});
			component.status = "rolled-back";
		}
		await removeCreatedPromotionParents(components);
		return true;
	} catch {
		return false;
	}
}
async function hasPublishedPromotionComponent(components) {
	for (const component of components) {
		if (component.status === "promoted") return true;
		const [stagedExists, finalExists] = await Promise.all([migrationPathEntryExists(component.stagedPath), migrationPathEntryExists(component.finalPath)]);
		if (!stagedExists && finalExists) return true;
	}
	return false;
}
/** Reconciles interrupted promotion and returns any committed finalization to resume. */
async function recoverSetupMigrationPromotion(params) {
	const found = await readLatestPromotionJournal(params);
	if (!found) return;
	const journal = found.journal;
	if (journal.status === "rolled-back") {
		if (journal.continuation) await cleanupPromotionStaging(journal.continuation);
		return;
	}
	if (journal.status === "indeterminate") throw new Error(`An onboarding migration promotion is indeterminate. Review ${found.path} and run openclaw doctor before retrying.`);
	const currentConfigHash = hashSetupMigrationConfig(await params.readConfigFile());
	const finalPaths = journal.components.map((component) => component.finalPath);
	const allFinal = (await Promise.all(finalPaths.map(migrationPathEntryExists))).every(Boolean);
	if (journal.status === "completed") return createPromotionResume(found.path, journal);
	if (journal.status === "committed") {
		if (allFinal) return createPromotionResume(found.path, journal);
		journal.status = "indeterminate";
		await writePromotionJournal(found.path, journal);
		throw new Error(`A committed onboarding migration no longer matches its promoted target. Review ${found.path} and run openclaw doctor before retrying.`);
	}
	if (currentConfigHash === journal.configHashTarget && allFinal) {
		journal.status = "committed";
		await writePromotionJournal(found.path, journal);
		return createPromotionResume(found.path, journal);
	}
	if (currentConfigHash === journal.configHashBefore) {
		if (await hasPublishedPromotionComponent(journal.components)) {
			journal.status = "indeterminate";
			await writePromotionJournal(found.path, journal);
			throw new Error(`An interrupted onboarding migration published local data before config commit. Review ${found.path} and run openclaw doctor before retrying.`);
		}
		if (await rollbackComponents(journal.components)) {
			journal.status = "rolled-back";
			await writePromotionJournal(found.path, journal);
			if (journal.continuation) await cleanupPromotionStaging(journal.continuation);
			return;
		}
	}
	journal.status = "indeterminate";
	await writePromotionJournal(found.path, journal);
	throw new Error(`Could not reconcile an interrupted onboarding migration. Review ${found.path} and run openclaw doctor before retrying.`);
}
async function listMissingPromotionParents(target) {
	const missing = [];
	let current = path.dirname(target);
	while (!await migrationPathEntryExists(current)) {
		missing.push(current);
		const parent = path.dirname(current);
		if (parent === current) throw new Error(`Could not find an existing parent for migration promotion at ${target}.`);
		current = parent;
	}
	return missing;
}
async function reserveEmptyTargetBackupPath(target) {
	const reserved = await fs.mkdtemp(path.join(path.dirname(target), ".openclaw-migration-empty-"));
	await fs.rmdir(reserved);
	return reserved;
}
async function recordPromotionTargetState(component) {
	component.createdParentPaths = await listMissingPromotionParents(component.finalPath);
	if (!await migrationPathEntryExists(component.finalPath)) return;
	if (!(await fs.lstat(component.finalPath)).isDirectory() || (await fs.readdir(component.finalPath)).length > 0) throw new SetupMigrationTargetChangedError(`Migration target changed before promotion: ${component.finalPath}`);
	component.targetWasEmptyDirectory = true;
	component.emptyTargetBackupPath = await reserveEmptyTargetBackupPath(component.finalPath);
}
async function moveRecordedEmptyTarget(component) {
	if (!component.targetWasEmptyDirectory) return;
	if ((await fs.readdir(component.finalPath)).length > 0) throw new SetupMigrationTargetChangedError(`Migration target changed before promotion: ${component.finalPath}`);
	if (component.emptyTargetBackupPath) await fs.rename(component.finalPath, component.emptyTargetBackupPath);
	else await fs.rmdir(component.finalPath);
}
async function usesCaseInsensitivePaths(directory) {
	const probe = await fs.mkdtemp(path.join(directory, ".openclaw-case-probe-"));
	try {
		const alias = path.join(path.dirname(probe), path.basename(probe).toUpperCase());
		if (alias === probe) return false;
		await fs.access(alias);
		return true;
	} catch (error) {
		if (isNotFoundPathError(error)) return false;
		throw error;
	} finally {
		await fs.rm(probe, {
			recursive: true,
			force: true
		});
	}
}
async function usesNormalizationInsensitivePaths(directory) {
	const probe = await fs.mkdtemp(path.join(directory, ".openclaw-normalization-é-"));
	try {
		const alias = path.join(path.dirname(probe), path.basename(probe).normalize("NFD"));
		if (alias === probe) return false;
		await fs.access(alias);
		return true;
	} catch (error) {
		if (isNotFoundPathError(error)) return false;
		throw error;
	} finally {
		await fs.rm(probe, {
			recursive: true,
			force: true
		});
	}
}
async function canonicalizePromotionPath(candidate) {
	const suffix = [];
	let current = path.resolve(candidate);
	while (true) try {
		const ancestor = await fs.realpath(current);
		const probeDirectory = (await fs.stat(ancestor)).isDirectory() ? ancestor : path.dirname(ancestor);
		return {
			path: path.join(ancestor, ...suffix.toReversed()),
			caseInsensitive: await usesCaseInsensitivePaths(probeDirectory),
			normalizationInsensitive: await usesNormalizationInsensitivePaths(probeDirectory)
		};
	} catch (error) {
		if (!isNotFoundPathError(error)) throw error;
		const parent = path.dirname(current);
		if (parent === current) throw new Error(`Could not resolve a promotion target for ${candidate}.`, { cause: error });
		suffix.push(path.basename(current));
		current = parent;
	}
}
async function assertSupportedStagedStateTree(params) {
	const assertEntries = async (directory, allowed) => {
		let entries;
		try {
			entries = await fs.readdir(directory);
		} catch (error) {
			if (isNotFoundPathError(error)) return;
			throw error;
		}
		const unexpected = entries.filter((entry) => !allowed.has(entry));
		if (unexpected.length > 0) throw new Error(`Migration provider wrote unsupported staged state: ${unexpected.map((entry) => path.join(directory, entry)).join(", ")}.`);
	};
	await assertEntries(params.stagedStateDir, /* @__PURE__ */ new Set([
		"agents",
		"migration",
		"state"
	]));
	await assertEntries(path.join(params.stagedStateDir, "agents"), /* @__PURE__ */ new Set([params.agentId]));
	await assertEntries(path.join(params.stagedStateDir, "agents", params.agentId), /* @__PURE__ */ new Set(["agent"]));
	await assertEntries(path.join(params.stagedStateDir, "migration"), /* @__PURE__ */ new Set([params.providerId]));
	await assertEntries(path.join(params.stagedStateDir, "migration", params.providerId), /* @__PURE__ */ new Set([params.reportDirName]));
}
async function assertDisjointPromotionTargets(components) {
	const canonicalPaths = await Promise.all(components.map(async (component) => ({
		component,
		path: await canonicalizePromotionPath(component.finalPath)
	})));
	for (const [index, current] of canonicalPaths.entries()) for (const other of canonicalPaths.slice(index + 1)) {
		const caseInsensitive = current.path.caseInsensitive || other.path.caseInsensitive;
		const normalizationInsensitive = current.path.normalizationInsensitive || other.path.normalizationInsensitive;
		const normalizePath = (pathname) => {
			const normalized = normalizationInsensitive ? pathname.normalize("NFC") : pathname;
			return caseInsensitive ? normalized.toLocaleLowerCase("en-US") : normalized;
		};
		const currentPath = normalizePath(current.path.path);
		const otherPath = normalizePath(other.path.path);
		if (isPathInside(currentPath, otherPath) || isPathInside(otherPath, currentPath)) throw new Error(`Migration promotion targets overlap: ${current.component.finalPath} and ${other.component.finalPath}.`);
	}
}
//#endregion
//#region src/wizard/setup.migration-stage.ts
const DEFERRED_REASON = "deferred until durable onboarding promotion";
async function findExistingAncestor(candidate) {
	let current = path.resolve(candidate);
	while (!await migrationPathEntryExists(current)) {
		const parent = path.dirname(current);
		if (parent === current) throw new Error(`Could not find an existing parent for migration staging at ${candidate}.`);
		current = parent;
	}
	return current;
}
async function makePrivateStageNear(target, label) {
	const ancestor = await findExistingAncestor(path.dirname(path.resolve(target)));
	const staged = await fs.mkdtemp(path.join(ancestor, `.openclaw-${label}-`));
	await fs.chmod(staged, 448);
	return staged;
}
function replacePathPrefix(value, from, to) {
	if (value === from) return to;
	const prefix = `${from}${path.sep}`;
	return value.startsWith(prefix) ? `${to}${value.slice(from.length)}` : value;
}
function projectPath(value, mappings) {
	const mapping = mappings.filter(([from]) => value === from || value.startsWith(`${from}${path.sep}`)).toSorted(([left], [right]) => right.length - left.length)[0];
	return mapping ? replacePathPrefix(value, mapping[0], mapping[1]) : value;
}
function projectValue(value, mappings) {
	if (typeof value === "string") return projectPath(value, mappings);
	if (Array.isArray(value)) return value.map((entry) => projectValue(entry, mappings));
	if (!value || typeof value !== "object") return value;
	return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, projectValue(entry, mappings)]));
}
function projectPlanTargets(plan, mappings) {
	return {
		...plan,
		...plan.target ? { target: projectValue(plan.target, mappings) } : {},
		items: plan.items.map((item) => ({
			...item,
			...item.target ? { target: projectValue(item.target, mappings) } : {}
		})),
		...plan.metadata ? { metadata: projectValue(plan.metadata, mappings) } : {}
	};
}
function createInMemoryConfigRuntime(params) {
	let finalConfig = structuredClone(params.finalConfig);
	let stagedConfig = structuredClone(params.stagedConfig);
	const mutateConfigFile = async (mutation) => {
		const stagedDraft = structuredClone(stagedConfig);
		const result = await mutation.mutate(stagedDraft, {
			snapshot: {},
			previousHash: null
		});
		stagedConfig = stagedDraft;
		finalConfig = params.projectToFinal(stagedDraft);
		return {
			nextConfig: stagedConfig,
			result,
			path: "<onboarding-migration-stage>",
			previousHash: null,
			snapshot: {},
			persistedHash: null,
			afterWrite: mutation.afterWrite,
			followUp: {
				mode: "none",
				reason: "staged migration config",
				requiresRestart: false
			}
		};
	};
	return {
		runtime: {
			current: () => stagedConfig,
			mutateConfigFile
		},
		getFinalConfig: () => structuredClone(finalConfig),
		getStagedConfig: () => structuredClone(stagedConfig),
		replaceConfigs(next) {
			finalConfig = structuredClone(next.finalConfig);
			stagedConfig = structuredClone(next.stagedConfig);
		}
	};
}
function phasePlan(plan, phase) {
	const items = plan.items.map((item) => {
		if ((item.applyPhase ?? "before-promotion") === phase || item.status !== "planned") return item;
		return {
			...item,
			status: "skipped",
			reason: DEFERRED_REASON
		};
	});
	return {
		...plan,
		items,
		summary: summarizeMigrationItems(items)
	};
}
function buildSetupMigrationPhasePlan(plan, phase) {
	return phasePlan(plan, phase);
}
function takeMatchingItem(items, item) {
	const index = items.findIndex((candidate) => candidate.id === item.id);
	if (index < 0) return;
	return items.splice(index, 1)[0];
}
function mergeSetupMigrationPhaseResults(params) {
	const stagedItems = [...params.staged.items];
	const deferredItems = [...params.deferred?.items ?? []];
	const items = params.plan.items.map((item) => {
		return takeMatchingItem(item.applyPhase === "after-promotion" ? deferredItems : stagedItems, item) ?? item;
	});
	const plannedItemIds = new Set(params.plan.items.map((item) => item.id));
	items.push(...stagedItems.filter((item) => !plannedItemIds.has(item.id)), ...deferredItems.filter((item) => !plannedItemIds.has(item.id)));
	return {
		...params.staged,
		items,
		summary: summarizeMigrationItems(items),
		warnings: [.../* @__PURE__ */ new Set([...params.staged.warnings ?? [], ...params.deferred?.warnings ?? []])],
		nextSteps: [.../* @__PURE__ */ new Set([...params.staged.nextSteps ?? [], ...params.deferred?.nextSteps ?? []])]
	};
}
async function createSetupMigrationStage(params) {
	const agentId = resolveDefaultAgentId(params.targetConfig);
	const finalEnv = {
		...process.env,
		OPENCLAW_STATE_DIR: params.stateDir
	};
	const finalAgentDir = resolveAgentDir(params.targetConfig, agentId, finalEnv);
	const stagedStateDir = await makePrivateStageNear(params.stateDir, "migration-state");
	const stagedWorkspaceDir = await makePrivateStageNear(params.workspaceDir, "migration-workspace");
	const stagedAgentDir = path.join(stagedStateDir, "agents", agentId, "agent");
	const stagedReportDir = path.join(stagedStateDir, "migration", params.providerId, path.basename(params.reportDir));
	const stageEnv = {
		...process.env,
		OPENCLAW_STATE_DIR: stagedStateDir
	};
	const stagedConfig = {
		...structuredClone(params.targetConfig),
		agents: {
			...structuredClone(params.targetConfig.agents),
			defaults: {
				...structuredClone(params.targetConfig.agents?.defaults),
				workspace: stagedWorkspaceDir
			}
		}
	};
	const finalPaths = {
		stateDir: params.stateDir,
		workspaceDir: params.workspaceDir,
		agentDir: finalAgentDir,
		reportDir: params.reportDir
	};
	const stagedPaths = {
		stateDir: stagedStateDir,
		workspaceDir: stagedWorkspaceDir,
		agentDir: stagedAgentDir,
		reportDir: stagedReportDir
	};
	const toStage = [
		[finalPaths.workspaceDir, stagedPaths.workspaceDir],
		[finalPaths.agentDir, stagedPaths.agentDir],
		[finalPaths.stateDir, stagedPaths.stateDir],
		[finalPaths.reportDir, stagedPaths.reportDir]
	];
	const toFinal = toStage.map(([finalPath, stagedPath]) => [stagedPath, finalPath]);
	const projectConfigToFinal = (config) => projectValue(config, toFinal);
	const configs = createInMemoryConfigRuntime({
		finalConfig: params.targetConfig,
		stagedConfig,
		projectToFinal: projectConfigToFinal
	});
	openOpenClawAgentDatabase({
		agentId,
		env: stageEnv
	});
	let databasesDisposed = false;
	let finalAgentDatabaseRegistered = false;
	let retainForRecovery = false;
	const disposeDatabases = () => {
		if (databasesDisposed) return;
		clearRuntimeAuthProfileStoreSnapshot(stagedAgentDir);
		const stagedAgentDatabasePath = path.join(stagedAgentDir, "openclaw-agent.sqlite");
		disposeOpenClawAgentDatabaseByPath(stagedAgentDatabasePath, { env: stageEnv });
		closeOpenClawStateDatabaseByPath(resolveOpenClawStateSqlitePath(stageEnv));
		databasesDisposed = true;
	};
	return {
		staged: stagedPaths,
		final: finalPaths,
		configRuntime: configs.runtime,
		getFinalConfig: configs.getFinalConfig,
		getStagedConfig: configs.getStagedConfig,
		replaceStagedConfig(config) {
			configs.replaceConfigs({
				stagedConfig: config,
				finalConfig: projectConfigToFinal(config)
			});
		},
		projectPlanToStage: (plan) => projectPlanTargets(plan, toStage),
		projectResultToFinal: (result) => projectValue(result, toFinal),
		async promote({ expectedConfig, continuation, readConfigFile, commitConfigFile }) {
			disposeDatabases();
			const gatewayLockDir = resolveGatewayLockDir(stagedStateDir);
			await fs.rm(gatewayLockDir, {
				recursive: true,
				force: true
			});
			try {
				await fs.rmdir(path.dirname(gatewayLockDir));
			} catch (error) {
				if (!isNotFoundPathError(error) && error.code !== "ENOTEMPTY") throw error;
			}
			const configBefore = await readConfigFile();
			if (hashSetupMigrationConfig(configBefore) !== hashSetupMigrationConfig(expectedConfig)) throw new SetupMigrationTargetChangedError("Migration config changed before promotion. Review it and retry.");
			const configTarget = configs.getFinalConfig();
			const components = [{
				name: "workspace",
				stagedPath: stagedWorkspaceDir,
				finalPath: params.workspaceDir,
				status: "staged"
			}, {
				name: "agent",
				stagedPath: stagedAgentDir,
				finalPath: finalAgentDir,
				status: "staged"
			}];
			const existingComponents = [];
			for (const component of components) {
				const { name, stagedPath } = component;
				if (name === "workspace" || await migrationPathEntryExists(stagedPath)) existingComponents.push(component);
			}
			await assertSupportedStagedStateTree({
				stagedStateDir,
				agentId,
				providerId: params.providerId,
				reportDirName: path.basename(params.reportDir)
			});
			await assertDisjointPromotionTargets([...existingComponents, { finalPath: params.reportDir }]);
			await fs.mkdir(params.reportDir, {
				recursive: true,
				mode: 448
			});
			for (const component of existingComponents) await recordPromotionTargetState(component);
			const journalPath = path.join(params.reportDir, PROMOTION_JOURNAL_FILE);
			const journal = {
				version: 1,
				status: "prepared",
				providerId: params.providerId,
				configHashBefore: hashSetupMigrationConfig(configBefore),
				configHashTarget: hashSetupMigrationConfig(configTarget),
				components: existingComponents,
				continuation: {
					...continuation,
					workspaceDir: params.workspaceDir,
					stagedReportDir,
					stagedRoots: [stagedStateDir, stagedWorkspaceDir]
				},
				updatedAt: (/* @__PURE__ */ new Date()).toISOString()
			};
			await writePromotionJournal(journalPath, journal);
			journal.status = "promoting";
			await writePromotionJournal(journalPath, journal);
			try {
				for (const component of journal.components) {
					if (component.targetWasEmptyDirectory) {
						await writePromotionJournal(journalPath, journal);
						await moveRecordedEmptyTarget(component);
					}
					await fs.mkdir(path.dirname(component.finalPath), {
						recursive: true,
						mode: 448
					});
					await fs.rename(component.stagedPath, component.finalPath);
					if (component.name === "agent") {
						registerOpenClawAgentDatabase({
							agentId,
							path: path.join(finalAgentDir, "openclaw-agent.sqlite"),
							env: finalEnv
						});
						finalAgentDatabaseRegistered = true;
					}
					component.status = "promoted";
					await writePromotionJournal(journalPath, journal);
				}
				let committed;
				try {
					committed = await commitConfigFile(configTarget, expectedConfig);
				} catch (error) {
					const current = await readConfigFile().catch(() => void 0);
					if (current && hashSetupMigrationConfig(current) === journal.configHashTarget) committed = current;
					else if (current && hashSetupMigrationConfig(current) === journal.configHashBefore) throw error;
					else {
						journal.status = "indeterminate";
						retainForRecovery = true;
						await writePromotionJournal(journalPath, journal);
						throw new Error(`Migration config commit is indeterminate. Review ${journalPath} and run openclaw doctor before retrying.`, { cause: error });
					}
				}
				journal.configHashTarget = hashSetupMigrationConfig(committed);
				journal.status = "committed";
				retainForRecovery = true;
				await writePromotionJournal(journalPath, journal);
				return {
					config: committed,
					resume: createPromotionResume(journalPath, journal)
				};
			} catch (error) {
				if (retainForRecovery) throw error;
				if (finalAgentDatabaseRegistered) {
					unregisterOpenClawAgentDatabase({
						agentId,
						path: path.join(finalAgentDir, "openclaw-agent.sqlite"),
						env: finalEnv
					});
					finalAgentDatabaseRegistered = false;
				}
				if (await rollbackComponents(journal.components)) {
					journal.status = "rolled-back";
					await writePromotionJournal(journalPath, journal);
					throw error;
				}
				journal.status = "indeterminate";
				retainForRecovery = true;
				await writePromotionJournal(journalPath, journal);
				throw new Error(`Migration promotion could not be rolled back. Review ${journalPath} and run openclaw doctor before retrying.`, { cause: error });
			}
		},
		async cleanup() {
			if (retainForRecovery) return;
			disposeDatabases();
			await Promise.all([fs.rm(stagedStateDir, {
				recursive: true,
				force: true
			}), fs.rm(stagedWorkspaceDir, {
				recursive: true,
				force: true
			})]);
		}
	};
}
//#endregion
//#region src/wizard/setup.migration-finalize.ts
function withPromotionAcknowledgement(outcome, acknowledgePromotion) {
	Object.defineProperty(outcome, "acknowledgePromotion", {
		value: acknowledgePromotion,
		enumerable: false
	});
	return outcome;
}
function hasDeferredMigrationItems(plan) {
	return plan.items.some((item) => item.applyPhase === "after-promotion" && item.status === "planned");
}
function assertDeferredMigrationApplyContract(provider, plan) {
	if (hasDeferredMigrationItems(plan) && provider.deferredApply?.retrySafe !== true) throw new Error(`Migration provider "${provider.id}" cannot defer activation during onboarding because it does not declare retry-safe deferred apply.`);
}
function deferredRetryInstruction(providerId) {
	return `Some post-promotion migration activation steps are still pending. Retry only those steps with openclaw onboard --flow import --import-from ${providerId}.`;
}
function deferredMigrationFailure(plan, error) {
	const reason = formatErrorMessage(error);
	const retry = deferredRetryInstruction(plan.providerId);
	const items = plan.items.map((item) => item.applyPhase === "after-promotion" && (item.status === "planned" || item.status === "error") ? {
		...item,
		status: "warning",
		reason
	} : item);
	return {
		...plan,
		items,
		summary: summarizeMigrationItems(items),
		warnings: [.../* @__PURE__ */ new Set([...plan.warnings ?? [], retry])],
		nextSteps: [.../* @__PURE__ */ new Set([retry, ...plan.nextSteps ?? []])]
	};
}
const COMPLETED_AFTER_PROMOTION_REASON = "completed after promotion";
function isCompletedDeferredMigrationItem(item) {
	return item.status === "migrated" || item.deferredCompletion === true;
}
function buildPendingDeferredMigrationPlan(plan, result) {
	const completedItemIds = new Set(result?.items.filter((item) => item.applyPhase === "after-promotion" && isCompletedDeferredMigrationItem(item)).map((item) => item.id));
	const deferredPlan = buildSetupMigrationPhasePlan(plan, "after-promotion");
	const items = deferredPlan.items.map((item) => completedItemIds.has(item.id) ? {
		...item,
		status: "skipped",
		reason: COMPLETED_AFTER_PROMOTION_REASON,
		deferredCompletion: true
	} : item);
	return {
		...deferredPlan,
		items,
		summary: summarizeMigrationItems(items)
	};
}
function mergeDeferredMigrationResults(params) {
	if (!params.previous) return params.next;
	const previousById = new Map(params.previous.items.map((item) => [item.id, item]));
	const items = params.next.items.map((item) => item.status === "skipped" && item.reason === COMPLETED_AFTER_PROMOTION_REASON ? previousById.get(item.id) ?? item : item);
	const retry = deferredRetryInstruction(params.next.providerId);
	return {
		...params.next,
		items,
		summary: summarizeMigrationItems(items),
		warnings: [.../* @__PURE__ */ new Set([...(params.previous.warnings ?? []).filter((warning) => warning !== retry), ...params.next.warnings ?? []])],
		nextSteps: [.../* @__PURE__ */ new Set([...(params.previous.nextSteps ?? []).filter((nextStep) => nextStep !== retry), ...params.next.nextSteps ?? []])]
	};
}
function hasPendingDeferredMigrationItems(plan, result) {
	const resultById = new Map(result?.items.map((item) => [item.id, item]));
	return plan.items.some((item) => item.applyPhase === "after-promotion" && item.status === "planned" && !isCompletedDeferredMigrationItem(resultById.get(item.id) ?? item));
}
async function createPromotionConfigRuntime(config) {
	const { mutateConfigFile } = await import("./mutate-T6AGt7ip.js");
	let currentConfig = structuredClone(config);
	return {
		current: () => currentConfig,
		async mutateConfigFile(mutation) {
			const result = await mutateConfigFile(mutation);
			currentConfig = structuredClone(result.nextConfig);
			return result;
		}
	};
}
async function finalizeSetupMigrationPromotion(params) {
	const { continuation } = params.resume;
	const reportDir = path.dirname(params.resume.journalPath);
	await params.resume.copyReportArtifacts();
	const configRuntime = await createPromotionConfigRuntime(params.config);
	let deferredResult = continuation.deferredResult;
	if (hasDeferredMigrationItems(continuation.plan) && hasPendingDeferredMigrationItems(continuation.plan, deferredResult)) {
		const previousDeferredResult = deferredResult;
		const deferredPlan = buildPendingDeferredMigrationPlan(continuation.plan, previousDeferredResult);
		let preparation;
		let retryResult;
		try {
			const deferredContext = {
				config: params.config,
				configRuntime,
				stateDir: params.stateDir,
				logger: params.logger,
				reportDir,
				...continuation.source ? { source: continuation.source } : {},
				...continuation.includeSecrets !== void 0 ? { includeSecrets: continuation.includeSecrets } : {},
				...continuation.providerOptions ? { providerOptions: continuation.providerOptions } : {},
				overwrite: false
			};
			preparation = await params.provider.prepareApply?.(deferredContext);
			retryResult = mergeDeferredMigrationResults({
				previous: previousDeferredResult,
				next: await params.provider.apply(deferredContext, deferredPlan)
			});
			if (hasPendingDeferredMigrationItems(continuation.plan, retryResult)) retryResult = deferredMigrationFailure(retryResult, "activation did not complete every deferred item");
		} catch (error) {
			retryResult = mergeDeferredMigrationResults({
				previous: previousDeferredResult,
				next: deferredMigrationFailure(deferredPlan, error)
			});
		} finally {
			await preparation?.dispose?.();
		}
		deferredResult = retryResult;
		await params.resume.saveDeferredResult(deferredResult);
	}
	const finalResult = mergeSetupMigrationPhaseResults({
		plan: continuation.plan,
		staged: continuation.stagedResult,
		...deferredResult ? { deferred: deferredResult } : {}
	});
	finalResult.reportDir = reportDir;
	await writeMigrationReport(finalResult, { title: `${continuation.providerLabel} Migration Report` });
	const hasPendingActivation = hasPendingDeferredMigrationItems(continuation.plan, deferredResult);
	if (!hasPendingActivation) await params.resume.complete();
	await params.resume.cleanup();
	await params.prompter.note(params.formatMigrationResult(finalResult).join("\n"), t("wizard.migration.appliedTitle"));
	if (!continuation.continueOnboarding) await params.prompter.outro(t("wizard.migration.complete"));
	else await params.prompter.note(t("wizard.migration.continuing"), t("wizard.migration.appliedTitle"));
	return hasPendingActivation ? continuation.outcome : withPromotionAcknowledgement(continuation.outcome, params.resume.acknowledge);
}
//#endregion
//#region src/wizard/setup.migration-import.ts
const loadMigrationProviderRuntimeModule = createLazyRuntimeModule(() => import("./migration-provider-runtime-CLc7tJKd.js"));
const loadMigrationContextModule = createLazyRuntimeModule(() => import("./context-BNPGw8GN.js"));
const loadConfigPathsModule = createLazyRuntimeModule(() => import("./paths-DIpSsLs8.js"));
async function detectSetupMigrationSources(params) {
	const [{ ensureStandaloneMigrationProviderRegistryLoaded, resolvePluginMigrationProviders }, { createMigrationLogger }, { resolveStateDir }] = await Promise.all([
		loadMigrationProviderRuntimeModule(),
		loadMigrationContextModule(),
		loadConfigPathsModule()
	]);
	ensureStandaloneMigrationProviderRegistryLoaded({ cfg: params.config });
	const stateDir = resolveStateDir();
	const logger = createMigrationLogger(params.runtime);
	const detections = [];
	for (const provider of resolvePluginMigrationProviders({ cfg: params.config })) {
		if (!provider.detect) continue;
		try {
			const detection = await provider.detect({
				config: params.config,
				stateDir,
				logger
			});
			if (detection.found) detections.push({
				providerId: provider.id,
				label: detection.label ?? provider.label,
				...detection.source ? { source: detection.source } : {},
				...detection.message ? { message: detection.message } : {}
			});
		} catch (error) {
			logger.debug?.(`Migration provider ${provider.id} detection failed: ${formatErrorMessage(error)}`);
		}
	}
	return detections;
}
function resolveImportSourceDefault(params) {
	const detected = params.detections.find((detection) => detection.providerId === params.providerId);
	if (detected?.source) return detected.source;
	return params.providerId === "hermes" ? "~/.hermes" : "";
}
function formatMigrationProviderId(providerId) {
	return providerId.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
function resolveManifestMigrationProviderLabel(params) {
	return params.pluginName?.trim().replace(/\s+Migration$/i, "") || formatMigrationProviderId(params.providerId) || params.providerId;
}
function resolveManifestSetupMigrationProviders(baseConfig) {
	const snapshot = loadManifestContractSnapshot({ config: baseConfig });
	return listAvailableManifestContractPlugins({
		snapshot,
		contract: "migrationProviders",
		config: baseConfig
	}).flatMap((plugin) => (plugin.contracts?.migrationProviders ?? []).map((providerId) => {
		const provider = {
			providerId,
			label: resolveManifestMigrationProviderLabel({
				providerId,
				pluginName: plugin.name
			})
		};
		if (plugin.description) provider.description = plugin.description;
		return provider;
	}));
}
async function listSetupMigrationOptions(params) {
	const { resolvePluginMigrationProviders } = await loadMigrationProviderRuntimeModule();
	const providers = resolvePluginMigrationProviders({ cfg: params.baseConfig });
	const options = [];
	const providerIds = /* @__PURE__ */ new Set();
	const addOption = (option) => {
		if (providerIds.has(option.providerId)) return;
		providerIds.add(option.providerId);
		options.push(option);
	};
	for (const detection of params.detections) addOption({
		providerId: detection.providerId,
		label: t("wizard.migration.importFrom", { source: detection.label }),
		...detection.source || detection.message ? { hint: detection.source ?? detection.message } : {}
	});
	for (const provider of providers) addOption({
		providerId: provider.id,
		label: t("wizard.migration.importFrom", { source: provider.label }),
		hint: provider.description ?? t("wizard.migration.sourcePathHint")
	});
	for (const provider of resolveManifestSetupMigrationProviders(params.baseConfig)) addOption({
		providerId: provider.providerId,
		label: t("wizard.migration.importFrom", { source: provider.label }),
		hint: provider.description ?? t("wizard.migration.sourcePathHint")
	});
	return options;
}
async function selectSetupMigrationProvider(params) {
	const options = await listSetupMigrationOptions({
		baseConfig: params.baseConfig,
		detections: params.detections
	});
	const requestedProviderId = params.opts.importFrom?.trim();
	if (requestedProviderId) return assertListedMigrationProvider(requestedProviderId, options);
	if (options.length === 0) throw new Error("No migration providers found.");
	const prompt = {
		message: t("wizard.migration.source"),
		options: options.map((option) => ({
			value: option.providerId,
			label: option.label,
			...option.hint ? { hint: option.hint } : {}
		})),
		initialValue: params.detections[0]?.providerId ?? options[0]?.providerId
	};
	if (!params.allowBack) {
		params.prompter.disableBackNavigation?.();
		return assertListedMigrationProvider(await params.prompter.select(prompt), options);
	}
	const selection = await runWizardWithPromptNavigationScope(params.prompter, async (prompter) => await prompter.select(prompt));
	if (selection.status === "back") return;
	return assertListedMigrationProvider(selection.value, options);
}
/**
* Rejects a provider id that is absent from the listed options, naming the ids that are present.
* `openclaw migrate` already answers an unknown provider this way; onboarding has to match, because
* a typed id is far likelier to be a typo here than a genuinely missing plugin. An undefined id
* means the operator dismissed the prompt, which is a cancellation rather than a bad choice.
*/
function assertListedMigrationProvider(providerId, options) {
	if (providerId === void 0 || options.some((option) => option.providerId === providerId)) return providerId;
	const available = options.map((option) => option.providerId);
	const suffix = available.length > 0 ? ` Available providers: ${available.join(", ")}.` : " No migration providers are installed.";
	const listCommand = formatCliCommand("openclaw migrate list");
	throw new Error(`Unknown migration provider "${providerId}".${suffix} Run ${listCommand} to see the current list.`);
}
async function resolveSetupMigrationProvider(params) {
	const { ensureStandaloneMigrationProviderRegistryLoaded, resolvePluginMigrationProvider } = await loadMigrationProviderRuntimeModule();
	ensureStandaloneMigrationProviderRegistryLoaded({
		cfg: params.baseConfig,
		providerId: params.providerId
	});
	const existing = resolvePluginMigrationProvider({
		providerId: params.providerId,
		cfg: params.baseConfig
	});
	if (existing) return {
		provider: existing,
		baseConfig: params.baseConfig
	};
	throw new Error(`Migration provider "${params.providerId}" did not register after activation.`);
}
function hasCredentialCandidate(plan) {
	return plan.items.some((item) => item.kind === "auth" || item.kind === "secret" || item.sensitive === true);
}
async function createSetupMigrationPlan(params) {
	let ctx = {
		...params.ctx,
		includeSecrets: params.importSecrets
	};
	let plan = await params.provider.plan(ctx);
	if (params.nonInteractive || params.importSecrets || !hasCredentialCandidate(plan)) return {
		ctx,
		plan
	};
	if (!await params.prompter.confirm({
		message: t("wizard.migration.includeCredentials"),
		initialValue: true
	})) return {
		ctx,
		plan
	};
	ctx = {
		...ctx,
		includeSecrets: true
	};
	plan = await params.provider.plan(ctx);
	return {
		ctx,
		plan
	};
}
async function runSetupMigrationImport(params) {
	const [{ applyLocalSetupWorkspaceConfig, applySkipBootstrapConfig }, { createMigrationLogger, buildMigrationReportDir }, { assertApplySucceeded, assertConflictFreePlan, formatMigrationPreview, formatMigrationResult }, { resolveStateDir }, onboardHelpers] = await Promise.all([
		import("./onboard-config-DgCoy_f_.js"),
		loadMigrationContextModule(),
		import("./output-B0Dh6l2B.js"),
		loadConfigPathsModule(),
		import("./onboard-helpers-DC4-A9ie.js")
	]);
	const providerId = await selectSetupMigrationProvider({
		opts: params.opts,
		baseConfig: params.baseConfig,
		detections: params.detections,
		prompter: params.prompter,
		allowBack: params.allowProviderBack === true
	});
	if (!providerId) return { kind: "back" };
	params.prompter.disableBackNavigation?.();
	const workspaceInput = params.opts.workspace ?? (params.opts.nonInteractive ? params.baseConfig.agents?.defaults?.workspace ?? onboardHelpers.DEFAULT_WORKSPACE : await params.prompter.text({
		message: t("wizard.migration.targetWorkspace"),
		initialValue: params.baseConfig.agents?.defaults?.workspace ?? onboardHelpers.DEFAULT_WORKSPACE
	}));
	const workspaceDir = resolveUserPath(workspaceInput.trim() || onboardHelpers.DEFAULT_WORKSPACE);
	const stateDir = resolveStateDir();
	return await withSetupMigrationTargetLock(stateDir, async () => {
		const promotionResume = await recoverSetupMigrationPromotion({
			stateDir,
			providerId,
			readConfigFile: params.readConfigFile
		});
		if (promotionResume) {
			const committedConfig = await params.readConfigFile();
			const resolvedProvider = await resolveSetupMigrationProvider({
				providerId,
				baseConfig: committedConfig
			});
			assertDeferredMigrationApplyContract(resolvedProvider.provider, promotionResume.continuation.plan);
			return await finalizeSetupMigrationPromotion({
				provider: resolvedProvider.provider,
				resume: promotionResume,
				config: committedConfig,
				stateDir,
				logger: createMigrationLogger(params.runtime),
				prompter: params.prompter,
				formatMigrationResult
			});
		}
		const lockedBaseConfig = preserveSetupMigrationOnboardingConsents(await params.readConfigFile(), params.baseConfig);
		const freshness = await inspectSetupMigrationFreshness({
			baseConfig: lockedBaseConfig,
			stateDir,
			workspaceDir
		});
		assertFreshSetupMigrationTarget(freshness);
		const resolvedProvider = await resolveSetupMigrationProvider({
			providerId,
			baseConfig: lockedBaseConfig
		});
		const planningBaseConfig = await params.readConfigFile();
		const planningTargetSnapshotHash = await buildSetupMigrationTargetSnapshot({
			config: planningBaseConfig,
			stateDir,
			workspaceDir
		});
		const migrationLogger = createMigrationLogger(params.runtime);
		const selectedDetections = [...params.detections];
		if (resolvedProvider.provider.detect && !selectedDetections.some((detection) => detection.providerId === providerId)) try {
			const detection = await resolvedProvider.provider.detect({
				config: resolvedProvider.baseConfig,
				stateDir,
				logger: migrationLogger
			});
			if (detection.found) selectedDetections.push({
				providerId,
				label: detection.label ?? resolvedProvider.provider.label,
				...detection.source ? { source: detection.source } : {},
				...detection.message ? { message: detection.message } : {}
			});
		} catch (error) {
			migrationLogger.debug?.(`Migration provider ${providerId} detection failed: ${formatErrorMessage(error)}`);
		}
		const sourceDefault = resolveImportSourceDefault({
			providerId,
			detections: selectedDetections
		});
		const sourceDir = params.opts.importSource?.trim() || sourceDefault || (params.opts.nonInteractive ? (() => {
			throw new Error("--import-source is required for non-interactive migration import.");
		})() : await params.prompter.text({
			message: t("wizard.migration.sourceAgentHome"),
			initialValue: providerId === "hermes" ? "~/.hermes" : void 0
		}));
		let targetConfig = applyLocalSetupWorkspaceConfig(resolvedProvider.baseConfig, workspaceDir);
		if (params.opts.skipBootstrap) targetConfig = applySkipBootstrapConfig(targetConfig);
		const initialCtx = {
			config: targetConfig,
			stateDir,
			source: sourceDir,
			overwrite: false,
			logger: migrationLogger
		};
		const planned = await createSetupMigrationPlan({
			provider: resolvedProvider.provider,
			ctx: initialCtx,
			importSecrets: Boolean(params.opts.importSecrets),
			nonInteractive: Boolean(params.opts.nonInteractive),
			prompter: params.prompter
		});
		const plannedSourceSnapshotHash = await buildSetupMigrationPlanSourceSnapshot(planned.plan);
		const ctx = planned.ctx;
		const plan = planned.plan;
		assertDeferredMigrationApplyContract(resolvedProvider.provider, plan);
		await params.prompter.note(formatMigrationPreview(plan).join("\n"), t("wizard.migration.previewTitle"));
		assertConflictFreePlan(plan, providerId);
		if (!(params.opts.nonInteractive === true ? true : await params.prompter.confirm({
			message: t("wizard.migration.apply"),
			initialValue: true
		}))) throw new WizardCancelledError(t("wizard.migration.cancelled"));
		targetConfig = onboardHelpers.applyWizardMetadata(targetConfig, {
			command: "onboard",
			mode: "local"
		});
		await prepareSetupMigrationAttemptBoundary({
			currentConfig: await params.readConfigFile(),
			targetConfig,
			stateDir,
			workspaceDir,
			plan,
			expectedTargetSnapshotHash: planningTargetSnapshotHash,
			expectedSourceSnapshotHash: plannedSourceSnapshotHash
		});
		const reportDir = buildMigrationReportDir(providerId, stateDir);
		const stage = await createSetupMigrationStage({
			providerId,
			stateDir,
			workspaceDir,
			reportDir,
			targetConfig
		});
		try {
			const stagedPlan = stage.projectPlanToStage(buildSetupMigrationPhasePlan(plan, "before-promotion"));
			const stagedRuntime = ctx.runtime ? {
				...ctx.runtime,
				config: {
					...ctx.runtime.config,
					current: stage.configRuntime.current,
					mutateConfigFile: stage.configRuntime.mutateConfigFile,
					replaceConfigFile: async () => {
						throw new Error("Full config replacement is unavailable during staged migration.");
					}
				}
			} : void 0;
			const stagedResult = await resolvedProvider.provider.apply({
				...ctx,
				...stagedRuntime ? { runtime: stagedRuntime } : {},
				config: stage.getStagedConfig(),
				configRuntime: stage.configRuntime,
				stateDir: stage.staged.stateDir,
				reportDir: stage.staged.reportDir
			}, stagedPlan);
			assertApplySucceeded(stagedResult);
			const projectedStagedResult = stage.projectResultToFinal(stagedResult);
			let outcome = { kind: "no-imported-inference" };
			if (resolveAgentModelPrimaryValue(stage.getStagedConfig().agents?.defaults?.model)) {
				const verification = await offerLiveModelVerification({
					config: stage.getStagedConfig(),
					opts: params.opts,
					prompter: params.prompter,
					runtime: params.runtime,
					workspaceDir: stage.staged.workspaceDir,
					agentDir: stage.staged.agentDir,
					stateDir: stage.staged.stateDir,
					writeConfig: async (config) => {
						stage.replaceStagedConfig(config);
						return stage.getStagedConfig();
					},
					required: true
				});
				if (!verification.verified || !verification.modelRef) throw new Error("Imported inference was not verified.");
				stage.replaceStagedConfig(verification.config);
				outcome = {
					kind: "verified-inference",
					modelRef: verification.modelRef
				};
			}
			const [currentTargetSnapshotHash, currentSourceSnapshotHash] = await Promise.all([buildSetupMigrationTargetSnapshot({
				config: await params.readConfigFile(),
				stateDir,
				workspaceDir
			}), buildSetupMigrationPlanSourceSnapshot(plan)]);
			if (currentTargetSnapshotHash !== planningTargetSnapshotHash) throw new SetupMigrationTargetChangedError("Migration target changed before promotion. Review it and retry.");
			if (currentSourceSnapshotHash !== plannedSourceSnapshotHash) throw new Error("Migration source changed before promotion. Review it and retry.");
			const promoted = await stage.promote({
				expectedConfig: planningBaseConfig,
				continuation: {
					providerLabel: resolvedProvider.provider.label,
					...ctx.source ? { source: ctx.source } : {},
					...ctx.includeSecrets !== void 0 ? { includeSecrets: ctx.includeSecrets } : {},
					...ctx.providerOptions ? { providerOptions: ctx.providerOptions } : {},
					plan,
					stagedResult: projectedStagedResult,
					outcome,
					continueOnboarding: params.continueOnboarding === true
				},
				readConfigFile: params.readConfigFile,
				commitConfigFile: params.commitConfigFile
			});
			return await finalizeSetupMigrationPromotion({
				provider: resolvedProvider.provider,
				resume: promoted.resume,
				config: promoted.config,
				stateDir,
				logger: migrationLogger,
				prompter: params.prompter,
				formatMigrationResult
			});
		} finally {
			await stage.cleanup();
		}
	});
}
//#endregion
export { runSetupModelAuthStep as a, offerLiveModelVerification as i, listSetupMigrationOptions as n, runSetupMigrationImport as r, detectSetupMigrationSources as t };