UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,138 lines • 54.5 kB
import { o as resolveUserPath } from "../../home-dir-Dc6nN6HB.mjs";
import { c as isRecord, i as asOptionalObjectRecord } from "../../record-coerce-TIsquoVR.mjs";
import { w as root } from "../../fs-safe-BBzelG8F.mjs";
import { n as normalizeAgentId } from "../../agent-id-D1-Q6vR4.mjs";
import { a as reclaimDefinitelyStaleFileLock } from "../../file-lock-CkzOvhYo.mjs";
import "../../string-coerce-runtime-BbIJhy7G.mjs";
import "../../routing-DvCc0aVb.mjs";
import "../../runtime-doctor-migrations-CUBqqiG5.mjs";
import { n as legacyStateFileExists } from "../../doctor-state-migration-fs-B7t6EgYJ.mjs";
import "../../memory-core-host-engine-fs-Cyf5zJaK.mjs";
import { c as SHORT_TERM_META_NAMESPACE, f as configureMemoryCoreDreamingState, g as normalizeMemoryCoreWorkspaceKey, i as DREAMING_SESSION_INGESTION_SEEN_NAMESPACE, l as SHORT_TERM_PHASE_SIGNAL_NAMESPACE, r as DREAMING_SESSION_INGESTION_FILES_NAMESPACE, t as DREAMING_DAILY_INGESTION_NAMESPACE, u as SHORT_TERM_RECALL_NAMESPACE, v as readMemoryCoreWorkspaceEntries } from "../../dreaming-state-CMZlTCcj.mjs";
import { n as MANAGED_DREAMING_DECLARATION_KEY, t as classifyDreamingCronJob } from "../../dreaming-cron-classifier-DF0hZAQ0.mjs";
import { t as readDoctorAgentEntries } from "../../doctor-agent-config-BdQb9tJb.mjs";
import path from "node:path";
import fs from "node:fs/promises";
import { isDeepStrictEqual } from "node:util";
import crypto from "node:crypto";
import { walkDirectory } from "@openclaw/fs-safe/walk";
//#region extensions/memory-core/src/migration/doctor-dreaming-cron.ts
async function planDreamingCronRepair(inventory, config) {
	const constants = await import("../../plugin-sdk/memory-core-host-status.js");
	const { resolveMemoryDreamingPluginConfig } = await import("../../plugin-sdk/memory-core-host-runtime-core.js");
	const enabled = constants.resolveMemoryDeepDreamingConfig({
		cfg: config,
		pluginConfig: resolveMemoryDreamingPluginConfig(config)
	}).enabled;
	const changes = [];
	const warnings = [];
	const partitions = /* @__PURE__ */ new Map();
	for (const job of inventory.jobs) {
		if (!job.definition) {
			warnings.push(`Could not inspect cron job ${job.id} in ${job.storeKey}: invalid definition JSON; row retained.`);
			continue;
		}
		const kind = classifyDreamingCronJob(job.definition, constants);
		if (!kind) continue;
		if (kind === "ambiguous") {
			warnings.push(`Dreaming cron job ${job.id} in ${job.storeKey} retains a historical dreaming tag with an authored payload; review its ownership manually. Row retained.`);
			continue;
		}
		if (job.invalidReason) {
			warnings.push(`Dreaming cron job ${job.id} in ${job.storeKey} needs cron repair (${job.invalidReason}); row retained.`);
			continue;
		}
		const entries = partitions.get(job.storeKey) ?? [];
		entries.push({
			job,
			kind
		});
		partitions.set(job.storeKey, entries);
	}
	for (const entries of partitions.values()) {
		if (!enabled) {
			changes.push(...entries.map(({ job }) => ({
				job,
				definition: null
			})));
			continue;
		}
		const survivor = entries.toSorted((left, right) => {
			if (left.kind !== right.kind) {
				const priority = {
					declared: 0,
					legacy: 1,
					phase: 2
				};
				return priority[left.kind] - priority[right.kind];
			}
			const leftCreated = left.job.definition?.createdAtMs;
			const rightCreated = right.job.definition?.createdAtMs;
			const ageOrder = (typeof leftCreated === "number" ? leftCreated : Number.MAX_SAFE_INTEGER) - (typeof rightCreated === "number" ? rightCreated : Number.MAX_SAFE_INTEGER);
			if (ageOrder !== 0) return ageOrder;
			return left.job.id.localeCompare(right.job.id);
		})[0];
		if (!survivor?.job.definition) continue;
		const raw = survivor.job.definition;
		const definition = {
			...raw,
			declarationKey: MANAGED_DREAMING_DECLARATION_KEY
		};
		const payload = isRecord(raw.payload) ? raw.payload : {};
		const delivery = isRecord(raw.delivery) ? raw.delivery : {};
		definition.sessionTarget = "isolated";
		if (payload.kind !== "agentTurn" || survivor.kind === "phase") {
			const { text: _legacyText, ...retainedPayload } = payload;
			definition.payload = {
				...retainedPayload,
				kind: "agentTurn",
				message: constants.MEMORY_DREAMING_SYSTEM_EVENT_TEXT,
				lightContext: true
			};
		} else definition.payload = {
			...payload,
			lightContext: true
		};
		definition.delivery = {
			...delivery,
			mode: "none"
		};
		if (survivor.kind === "phase") {
			if (raw.name === constants.LEGACY_MEMORY_LIGHT_DREAMING_CRON_NAME || raw.name === constants.LEGACY_MEMORY_REM_DREAMING_CRON_NAME) definition.name = constants.MANAGED_MEMORY_DREAMING_CRON_NAME;
			if (typeof raw.description === "string") definition.description = raw.description.replace(constants.LEGACY_MEMORY_LIGHT_DREAMING_CRON_TAG, constants.MANAGED_MEMORY_DREAMING_CRON_TAG).replace(constants.LEGACY_MEMORY_REM_DREAMING_CRON_TAG, constants.MANAGED_MEMORY_DREAMING_CRON_TAG);
		}
		if (!isDeepStrictEqual(definition, raw)) changes.push({
			job: survivor.job,
			definition
		});
		for (const { job } of entries) if (job.id !== survivor.job.id) changes.push({
			job,
			definition: null
		});
	}
	return {
		changes,
		warnings,
		enabled
	};
}
function deferredCronInspection(error) {
	return `Dreaming cron inspection remains pending; existing cron rows were retained. Retry with "openclaw doctor --fix". ${String(error)}`;
}
const dreamingCronMigration = {
	id: "memory-core-dreaming-cron",
	label: "memory-core dreaming cron ownership",
	doctorOnly: true,
	phase: "after-session-repair",
	collectBackupResources: () => [],
	async detectLegacyState({ context, config }) {
		if (!context.inspectCronJobs) return null;
		let inventory;
		try {
			inventory = await context.inspectCronJobs();
		} catch (error) {
			return { preview: [deferredCronInspection(error)] };
		}
		const plan = await planDreamingCronRepair(inventory, config);
		const preview = [...plan.changes.length ? [`Repair ${plan.changes.length} historical dreaming cron row(s) after a verified SQLite backup.`] : [], ...plan.warnings];
		return preview.length ? { preview } : null;
	},
	async migrateLegacyState({ context, config }) {
		if (!context.inspectCronJobs || !context.repairCronJobs) return {
			changes: [],
			warnings: ["Dreaming cron repair requires a host with offline Doctor cron maintenance support."],
			warningDisposition: "recoverable"
		};
		let inventory;
		try {
			inventory = await context.inspectCronJobs();
		} catch (error) {
			return {
				changes: [],
				warnings: [deferredCronInspection(error)],
				warningDisposition: "recoverable"
			};
		}
		const plan = await planDreamingCronRepair(inventory, config);
		if (!plan.changes.length) return {
			changes: [],
			warnings: plan.warnings,
			warningDisposition: "recoverable"
		};
		const result = await context.repairCronJobs(inventory, plan.changes);
		return {
			changes: result.changed ? [plan.enabled ? `Repaired ${result.changed} historical dreaming cron row(s); survivor IDs, order, and runtime state preserved. SQLite backup: ${result.backupPath}.` : `Retired ${result.changed} managed dreaming cron row(s) because dreaming is disabled. SQLite backup: ${result.backupPath}.`] : [],
			warnings: plan.warnings,
			warningDisposition: "recoverable"
		};
	}
};
//#endregion
//#region extensions/memory-core/src/migration/doctor-workspaces.ts
async function resolveConfiguredWorkspaces(config, env) {
	const { resolveMemoryDreamingWorkspaces } = await import("../../plugin-sdk/memory-core-host-status.js");
	return resolveMemoryDreamingWorkspaces(config, { env }).map((entry) => entry.workspaceDir);
}
//#endregion
//#region extensions/memory-core/src/migration/doctor-dreaming-state.ts
const RETIRED_DREAMING_SOURCES = [
	{
		label: "daily ingestion",
		fileName: "daily-ingestion.json",
		namespaces: [DREAMING_DAILY_INGESTION_NAMESPACE]
	},
	{
		label: "session ingestion",
		fileName: "session-ingestion.json",
		namespaces: [DREAMING_SESSION_INGESTION_FILES_NAMESPACE, DREAMING_SESSION_INGESTION_SEEN_NAMESPACE]
	},
	{
		label: "short-term recall",
		fileName: "short-term-recall.json",
		namespaces: [SHORT_TERM_RECALL_NAMESPACE],
		metaKey: "recall"
	},
	{
		label: "phase signals",
		fileName: "phase-signals.json",
		namespaces: [SHORT_TERM_PHASE_SIGNAL_NAMESPACE],
		metaKey: "phase"
	}
];
async function unsupportedDreamingSources(params) {
	configureMemoryCoreDreamingState(params.context.openPluginStateKeyedStore);
	const warnings = [];
	for (const workspaceDir of await resolveConfiguredWorkspaces(params.config, params.env)) {
		const sources = [];
		for (const source of RETIRED_DREAMING_SOURCES) {
			const filePath = path.join(workspaceDir, "memory", ".dreams", source.fileName);
			if (await legacyStateFileExists(filePath)) sources.push({
				...source,
				filePath
			});
		}
		if (sources.length === 0) continue;
		const acknowledgements = await readMemoryCoreWorkspaceEntries({
			namespace: "legacy-dreaming-source-acknowledgements",
			workspaceDir
		});
		const metadata = await readMemoryCoreWorkspaceEntries({
			namespace: SHORT_TERM_META_NAMESPACE,
			workspaceDir
		});
		for (const source of sources) {
			if (acknowledgements.some((entry) => entry.key === `legacy-source:${source.label}`) || source.metaKey && metadata.some((entry) => entry.key === source.metaKey)) continue;
			if ((await Promise.all(source.namespaces.map((namespace) => readMemoryCoreWorkspaceEntries({
				namespace,
				workspaceDir
			})))).some((rows) => rows.length > 0)) continue;
			warnings.push(`Memory Core ${source.label}: upgrades from pre-July-2026 dreaming JSON are no longer migrated (${source.filePath}). No canonical SQLite state was found; an empty ingestion store cannot be distinguished from unmigrated state. Restore a backup produced by a July 2026 or newer release, or back up and move this retired file aside after verifying the SQLite state, then rerun openclaw doctor --fix. The file was left unchanged.`);
		}
	}
	return warnings;
}
const dreamingStateMigration = {
	id: "memory-core-dreams-json-to-sqlite",
	label: "Memory Core unsupported dreaming JSON",
	collectBackupResources: () => [],
	async detectLegacyState(params) {
		const warnings = await unsupportedDreamingSources(params);
		return warnings.length > 0 ? { preview: warnings.map((warning) => `- ${warning}`) } : null;
	},
	async migrateLegacyState(params) {
		return {
			changes: [],
			warnings: await unsupportedDreamingSources(params)
		};
	}
};
//#endregion
//#region extensions/memory-core/src/migration/doctor-host-event-sources.ts
function legacyMemoryHostEventPatterns(relativePath) {
	const baseName = path.basename(relativePath).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
	return {
		archivePattern: new RegExp(`^${baseName}\\.migrated(?:\\.([2-9]|[1-9][0-9]+))?$`, "u"),
		claimPattern: new RegExp(`^\\.${baseName}\\.doctor-importing(?:\\.([2-9]|[1-9][0-9]+))?$`, "u")
	};
}
function memoryHostWorkspacePrefix(workspaceDir) {
	return crypto.createHash("sha256").update(normalizeMemoryCoreWorkspaceKey(workspaceDir)).digest("hex").slice(0, 24);
}
async function collectLegacyMemoryHostEventSources(config, env) {
	const { resolveMemoryHostEventLogPath } = await import("../../plugin-sdk/memory-host-events.js");
	const sources = [];
	const seenWorkspaces = /* @__PURE__ */ new Set();
	for (const workspaceDir of await resolveConfiguredWorkspaces(config, env)) {
		let canonicalWorkspaceDir = path.resolve(workspaceDir);
		let filePath = resolveMemoryHostEventLogPath(canonicalWorkspaceDir);
		try {
			const workspaceRoot = await root(workspaceDir, {
				hardlinks: "reject",
				maxBytes: Number.MAX_SAFE_INTEGER,
				mkdir: false,
				symlinks: "reject"
			});
			canonicalWorkspaceDir = workspaceRoot.rootReal;
			if (seenWorkspaces.has(canonicalWorkspaceDir)) continue;
			seenWorkspaces.add(canonicalWorkspaceDir);
			filePath = resolveMemoryHostEventLogPath(canonicalWorkspaceDir);
			const relativePath = path.relative(canonicalWorkspaceDir, filePath);
			const directoryRelativePath = path.dirname(relativePath);
			const { archivePattern, claimPattern } = legacyMemoryHostEventPatterns(relativePath);
			const entries = await fs.readdir(path.join(workspaceRoot.rootReal, directoryRelativePath));
			const candidates = [];
			for (const entry of entries) {
				if (entry === path.basename(relativePath)) {
					candidates.push({
						entry,
						storage: "active",
						generation: void 0
					});
					continue;
				}
				const claim = claimPattern.exec(entry);
				const match = claim ?? archivePattern.exec(entry);
				if (match) candidates.push({
					entry,
					storage: claim ? "claim" : "archive",
					generation: BigInt(match[1] ?? "1")
				});
			}
			candidates.sort((left, right) => {
				if (left.generation === void 0) return 1;
				if (right.generation === void 0) return -1;
				return left.generation < right.generation ? -1 : left.generation > right.generation ? 1 : 0;
			});
			for (const candidate of candidates) {
				const candidateRelativePath = path.join(directoryRelativePath, candidate.entry);
				filePath = path.join(canonicalWorkspaceDir, candidateRelativePath);
				if (!(await workspaceRoot.stat(candidateRelativePath)).isFile) continue;
				const generationKey = (candidate.generation?.toString())?.padStart(20, "0");
				sources.push({
					kind: "ready",
					workspaceDir: canonicalWorkspaceDir,
					filePath,
					relativePath: candidateRelativePath,
					root: workspaceRoot,
					storage: candidate.storage,
					...candidate.storage === "active" ? {} : {
						archiveRelativePath: `${relativePath}.migrated${candidate.generation === 1n ? "" : `.${candidate.generation}`}`,
						generationKey
					}
				});
			}
		} catch (error) {
			const code = error.code;
			if (code === "ENOENT" || code === "ENOTDIR" || code === "not-found") continue;
			seenWorkspaces.add(canonicalWorkspaceDir);
			sources.push({
				kind: "rejected",
				workspaceDir: canonicalWorkspaceDir,
				filePath,
				reason: `Skipped unsafe Memory Core host event source ${filePath}: ${String(error)}. Check permissions and use regular files and directories inside the workspace, then rerun openclaw doctor --fix. For shared notes, use canonical paths in memory.search.extraPaths; this does not migrate legacy events.`
			});
		}
	}
	return sources;
}
async function resolveMemoryHostEventArchivePath(source) {
	const { resolveMemoryHostEventLogPath } = await import("../../plugin-sdk/memory-host-events.js");
	const activeRelativePath = path.relative(source.workspaceDir, resolveMemoryHostEventLogPath(source.workspaceDir));
	const directoryPath = path.join(source.root.rootReal, path.dirname(activeRelativePath));
	const { archivePattern, claimPattern } = legacyMemoryHostEventPatterns(activeRelativePath);
	let latestGeneration = 0n;
	for (const entry of await fs.readdir(directoryPath)) {
		const match = archivePattern.exec(entry) ?? claimPattern.exec(entry);
		if (!match) continue;
		const generation = BigInt(match[1] ?? "1");
		if (generation > latestGeneration) latestGeneration = generation;
	}
	const generation = latestGeneration + 1n;
	const generationText = generation.toString();
	if (generationText.length > 20) throw new RangeError("Memory Core host event archive generation is too large");
	const generationSuffix = generation === 1n ? "" : `.${generation}`;
	return {
		archiveRelativePath: `${activeRelativePath}.migrated${generationSuffix}`,
		claimRelativePath: path.join(path.dirname(activeRelativePath), `.${path.basename(activeRelativePath)}.doctor-importing${generationSuffix}`),
		generationKey: generationText.padStart(20, "0")
	};
}
//#endregion
//#region extensions/memory-core/src/migration/doctor-host-events.ts
const MEMORY_HOST_EVENTS_NAMESPACE = "memory-host.events";
const MEMORY_HOST_EVENT_CURSORS_NAMESPACE = "memory-host.event-cursors";
const MEMORY_HOST_EVENT_MIGRATION_CHECKPOINTS_NAMESPACE = "memory-host.event-migration-checkpoints";
const MAX_MEMORY_HOST_EVENTS = 1e4;
const MAX_MEMORY_HOST_EVENT_CURSORS = 1e3;
const MAX_MEMORY_HOST_EVENT_MIGRATION_CHECKPOINTS = 1e4;
const MAX_LEGACY_MEMORY_HOST_EVENT_VALUE_BYTES = 65536;
const LEGACY_MEMORY_HOST_SEQUENCE_BASE = Number.MIN_SAFE_INTEGER;
function openMemoryHostCheckpointStore(context) {
	return context.openPluginStateKeyedStore({
		namespace: MEMORY_HOST_EVENT_MIGRATION_CHECKPOINTS_NAMESPACE,
		maxEntries: MAX_MEMORY_HOST_EVENT_MIGRATION_CHECKPOINTS,
		overflowPolicy: "reject-new"
	});
}
function memoryHostMigrationCheckpointKey(source) {
	if (!source.generationKey) throw new Error(`Missing Memory Core host event archive generation for ${source.filePath}`);
	return `${memoryHostWorkspacePrefix(source.workspaceDir)}:archive:${source.generationKey}`;
}
function isMemoryHostMigrationCheckpoint(value) {
	return value?.kind === "migration-checkpoint" && typeof value.contentHash === "string" && Number.isSafeInteger(value.recordCount) && value.recordCount >= 0 && Number.isSafeInteger(value.sequenceBase) && Number.isSafeInteger(value.size) && value.size >= 0;
}
async function memoryHostEventSourceNeedsMigration(params) {
	if (params.source.storage !== "archive") return true;
	const checkpoint = await openMemoryHostCheckpointStore(params.context).lookup(memoryHostMigrationCheckpointKey(params.source));
	if (!isMemoryHostMigrationCheckpoint(checkpoint)) return true;
	const raw = await params.source.root.readText(params.source.relativePath);
	return Buffer.byteLength(raw, "utf8") !== checkpoint.size || crypto.createHash("sha256").update(raw).digest("hex") !== checkpoint.contentHash;
}
async function moveLegacyMemoryHostEventFile(source, target) {
	const { statRegularFileSync } = await import("../../plugin-sdk/file-access-runtime.js");
	return source.root.move(source.relativePath, target, { assertBeforeMutation: () => {
		if (statRegularFileSync(source.filePath).missing) throw new Error("Memory Core host event file no longer exists");
	} });
}
async function finalizeLegacyMemoryHostEventSource(params) {
	const archivedRelativePath = params.source.archiveRelativePath;
	if (!archivedRelativePath) throw new Error(`Missing Memory Core host event archive path for ${params.source.filePath}`);
	try {
		await moveLegacyMemoryHostEventFile(params.source, archivedRelativePath);
		params.changes.push(`Archived Memory Core host events legacy source -> ${path.join(params.source.workspaceDir, archivedRelativePath)}`);
		return true;
	} catch (error) {
		params.warnings.push(`Failed archiving Memory Core host events legacy source: ${String(error)}`);
		return false;
	}
}
async function restoreClaimedMemoryHostEventSource(params) {
	try {
		if (!await params.source.root.exists(params.source.relativePath)) return;
		if (!await params.source.root.exists(params.activeRelativePath)) {
			await moveLegacyMemoryHostEventFile(params.source, params.activeRelativePath);
			return;
		}
		params.warnings.push(`Left claimed Memory Core host events at ${params.source.filePath} because an old writer recreated the active source`);
	} catch (error) {
		params.warnings.push(`Failed restoring claimed Memory Core host events ${params.source.filePath}: ${String(error)}`);
	}
}
async function migrateLegacyMemoryHostEventSource(params) {
	const blocked = (warning) => {
		params.warnings.push(warning);
		return "blocked";
	};
	const { normalizeMemoryHostEventRecordForStorage, resolveMemoryHostEventLogPath } = await import("../../plugin-sdk/memory-host-events.js");
	const activeRelativePath = path.relative(params.source.workspaceDir, resolveMemoryHostEventLogPath(params.source.workspaceDir));
	let source = params.source;
	let claimFinalized = source.storage === "archive";
	if (source.storage === "active") {
		const generation = await resolveMemoryHostEventArchivePath(source);
		await moveLegacyMemoryHostEventFile(source, generation.claimRelativePath);
		source = {
			...source,
			filePath: path.join(source.workspaceDir, generation.claimRelativePath),
			relativePath: generation.claimRelativePath,
			storage: "claim",
			archiveRelativePath: generation.archiveRelativePath,
			generationKey: generation.generationKey
		};
	}
	try {
		if (!source.generationKey) throw new Error(`Missing Memory Core host event generation for ${source.filePath}`);
		const warningStart = params.warnings.length;
		const raw = await source.root.readText(source.relativePath);
		const prefix = memoryHostWorkspacePrefix(source.workspaceDir);
		const records = [];
		for (const [lineIndex, line] of raw.split(/\r?\n/u).entries()) {
			const trimmed = line.trim();
			if (!trimmed) continue;
			let event;
			try {
				event = JSON.parse(trimmed);
			} catch (error) {
				params.warnings.push(`Skipped malformed Memory Core host event at ${source.filePath}:${lineIndex + 1}: ${String(error)}`);
				continue;
			}
			const normalizedEvent = normalizeMemoryHostEventRecordForStorage(event);
			if (!normalizedEvent) {
				params.warnings.push(`Skipped invalid Memory Core host event at ${source.filePath}:${lineIndex + 1}`);
				continue;
			}
			const canonicalEvent = JSON.stringify(normalizedEvent);
			const parsedTimestamp = Date.parse(normalizedEvent.timestamp);
			const recordedAt = Number.isSafeInteger(parsedTimestamp) ? parsedTimestamp : 0;
			const ordinal = records.length;
			const digest = crypto.createHash("sha256").update(canonicalEvent).digest("hex");
			const value = {
				kind: "event",
				event: normalizedEvent,
				recordedAt,
				sequence: LEGACY_MEMORY_HOST_SEQUENCE_BASE + ordinal + 1
			};
			if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_LEGACY_MEMORY_HOST_EVENT_VALUE_BYTES) {
				params.warnings.push(`Skipped oversized Memory Core host event at ${source.filePath}:${lineIndex + 1}`);
				continue;
			}
			records.push({
				digest,
				ordinal,
				value
			});
		}
		if (params.warnings.length > warningStart) return blocked("Left Memory Core host events legacy source in place because invalid rows still require repair");
		const checkpointStore = openMemoryHostCheckpointStore(params.context);
		const checkpointKey = memoryHostMigrationCheckpointKey(source);
		const checkpointValue = await checkpointStore.lookup(checkpointKey);
		const previousCheckpoint = isMemoryHostMigrationCheckpoint(checkpointValue) ? checkpointValue : void 0;
		if (source.storage === "archive" && previousCheckpoint) {
			const bytes = Buffer.from(raw, "utf8");
			const prefixHash = crypto.createHash("sha256").update(bytes.subarray(0, previousCheckpoint.size)).digest("hex");
			if (bytes.length < previousCheckpoint.size || prefixHash !== previousCheckpoint.contentHash || records.length < previousCheckpoint.recordCount) {
				params.warnings.push(`Skipped Memory Core host event recovery because ${source.filePath} changed other than by append; left the archive in place`);
				return "warning";
			}
		}
		const firstCandidateOrdinal = source.storage === "archive" && previousCheckpoint ? previousCheckpoint.recordCount : 0;
		const candidateRecords = records.slice(firstCandidateOrdinal);
		const store = params.context.openPluginStateKeyedStore({
			namespace: MEMORY_HOST_EVENTS_NAMESPACE,
			maxEntries: MAX_MEMORY_HOST_EVENTS
		});
		const existingEntries = await store.entries();
		const existingKeys = new Set(existingEntries.map((entry) => entry.key));
		const latestLegacySequence = existingEntries.reduce((latest, entry) => entry.value.sequence < 0 ? Math.max(latest, entry.value.sequence) : latest, LEGACY_MEMORY_HOST_SEQUENCE_BASE);
		const legacyKeyPrefix = `${prefix}:event:0:s:`;
		const existingLegacyEntries = existingEntries.filter((entry) => entry.key.startsWith(legacyKeyPrefix) && !(entry.value.sequence >= 0));
		const existingByIdentity = new Map(existingLegacyEntries.flatMap((entry) => {
			const parts = entry.key.split(":");
			return parts.length === 8 ? [[`${parts[5]}:${parts[6]}:${parts[7]}`, entry]] : [];
		}));
		const existingSourceBase = candidateRecords.flatMap((record) => {
			const identity = `${source.generationKey}:${record.ordinal.toString().padStart(16, "0")}:${record.digest}`;
			const existing = existingByIdentity.get(identity);
			return existing ? [existing.value.sequence - (record.ordinal + 1)] : [];
		})[0];
		const laterGenerationExists = existingLegacyEntries.some((entry) => {
			const generationKey = entry.key.split(":")[5];
			return generationKey !== void 0 && generationKey > source.generationKey;
		});
		if (source.storage === "archive" && !previousCheckpoint && existingSourceBase === void 0 && laterGenerationExists) return blocked(`Skipped Memory Core host event recovery because ${source.filePath} has no durable checkpoint and later generations are already imported; left the archive in place`);
		const sourceSequenceBase = previousCheckpoint?.sequenceBase ?? existingSourceBase ?? latestLegacySequence;
		let nextSequence = latestLegacySequence;
		const sequencedRecords = candidateRecords.map((record) => {
			const ordinalKey = record.ordinal.toString().padStart(16, "0");
			const identity = `${source.generationKey}:${ordinalKey}:${record.digest}`;
			const existing = existingByIdentity.get(identity);
			const sequence = existing ? existing.value.sequence : previousCheckpoint ? nextSequence += 1 : sourceSequenceBase + record.ordinal + 1;
			const sequenceKey = (sequence - LEGACY_MEMORY_HOST_SEQUENCE_BASE).toString().padStart(16, "0");
			return {
				key: existing ? existing.key : `${legacyKeyPrefix}${sequenceKey}:${identity}`,
				value: {
					...record.value,
					sequence
				}
			};
		});
		const sourceKeys = new Set(sequencedRecords.map((record) => record.key));
		if (sequencedRecords.some((record) => !Number.isSafeInteger(record.value.sequence) || record.value.sequence >= 0)) return blocked("Skipped Memory Core host event migration because legacy sequence capacity is exhausted; left legacy source in place");
		const nativeCount = existingEntries.filter((entry) => entry.value.sequence >= 0).length;
		const legacyRetentionLimit = Math.max(0, MAX_MEMORY_HOST_EVENTS - nativeCount);
		const combinedLegacy = [...existingEntries.filter((entry) => entry.value.sequence < 0 && !sourceKeys.has(entry.key)), ...sequencedRecords].map((entry) => ({
			key: entry.key,
			sequence: entry.value.sequence
		})).toSorted((left, right) => left.sequence - right.sequence || left.key.localeCompare(right.key));
		const desiredLegacyKeys = new Set(legacyRetentionLimit === 0 ? [] : combinedLegacy.slice(-legacyRetentionLimit).map((record) => record.key));
		let retainedRecords = sequencedRecords.filter((record) => desiredLegacyKeys.has(record.key));
		const capacity = params.context.getPluginStateCapacity?.();
		if (!capacity) return blocked("Skipped Memory Core host event migration because plugin-wide SQLite capacity is unavailable; left legacy source in place");
		const pluginRemainingCapacity = Math.max(0, capacity.maxEntries - capacity.liveEntries);
		const cursorStore = params.context.openPluginStateKeyedStore({
			namespace: MEMORY_HOST_EVENT_CURSORS_NAMESPACE,
			maxEntries: MAX_MEMORY_HOST_EVENT_CURSORS
		});
		const cursorKey = `${prefix}:cursor`;
		const existingCursor = await cursorStore.lookup(cursorKey);
		const cursorCapacity = candidateRecords.length > 0 && existingCursor?.kind !== "cursor" ? 1 : 0;
		if (cursorCapacity > pluginRemainingCapacity) return blocked("Skipped Memory Core host event migration because SQLite plugin state has no room for its workspace cursor; left legacy source in place");
		const checkpointCapacity = checkpointValue ? 0 : 1;
		if (checkpointCapacity > 0 && (checkpointStore.count ? await checkpointStore.count() : (await checkpointStore.entries()).length) >= MAX_MEMORY_HOST_EVENT_MIGRATION_CHECKPOINTS) return blocked("Skipped Memory Core host event migration because durable raw-archive checkpoint capacity is exhausted; left legacy source in place");
		if (cursorCapacity + checkpointCapacity > pluginRemainingCapacity) return blocked("Skipped Memory Core host event migration because SQLite plugin state has no room for its raw-archive checkpoint; left legacy source in place");
		const importEntries = params.context.importPluginStateEntries;
		if (candidateRecords.length > 0 && !importEntries) return blocked("Skipped Memory Core host event migration because retention-aware SQLite import is unavailable; left legacy source in place");
		let retainedKeys = new Set(retainedRecords.map((record) => record.key));
		let missing = retainedRecords.filter((record) => !existingKeys.has(record.key));
		let replaceableLegacyRows = existingEntries.filter((entry) => entry.value.sequence < 0 && !retainedKeys.has(entry.key)).length;
		const reservedCapacity = cursorCapacity + checkpointCapacity;
		const eventCapacity = pluginRemainingCapacity - reservedCapacity + replaceableLegacyRows;
		const retentionDeficit = Math.max(0, missing.length - eventCapacity);
		if (retentionDeficit > 0) {
			retainedRecords = retainedRecords.slice(Math.min(retentionDeficit, retainedRecords.length));
			retainedKeys = new Set(retainedRecords.map((record) => record.key));
			missing = retainedRecords.filter((record) => !existingKeys.has(record.key));
			replaceableLegacyRows = existingEntries.filter((entry) => entry.value.sequence < 0 && !retainedKeys.has(entry.key)).length;
		}
		const availableEventCapacity = pluginRemainingCapacity - reservedCapacity + replaceableLegacyRows;
		if (missing.length > availableEventCapacity) return blocked(`Skipped Memory Core host event migration because SQLite plugin state has room for ${availableEventCapacity} of ${missing.length} missing rows after reserving its cursor and raw-archive checkpoint; left legacy source in place`);
		if (cursorCapacity > 0) {
			const lastSequence = existingEntries.reduce((maximum, entry) => Number.isSafeInteger(entry.value.sequence) ? Math.max(maximum, entry.value.sequence) : maximum, 0);
			await cursorStore.register(cursorKey, {
				kind: "cursor",
				lastSequence
			});
			if ((await cursorStore.lookup(cursorKey))?.kind !== "cursor") return blocked("Skipped Memory Core host event migration because its workspace cursor could not be verified; left legacy source in place");
		}
		importEntries?.({
			namespace: MEMORY_HOST_EVENTS_NAMESPACE,
			maxEntries: MAX_MEMORY_HOST_EVENTS
		}, missing.map((record) => ({
			key: record.key,
			value: record.value,
			createdAt: record.value.sequence
		})));
		const importedKeys = new Set((await store.entries()).map((entry) => entry.key));
		const missingKey = retainedRecords.find((record) => !importedKeys.has(record.key))?.key;
		if (missingKey) return blocked(`Skipped archiving Memory Core host events because SQLite verification missed ${missingKey}`);
		if (source.storage === "archive") {
			if (missing.length > 0) params.changes.push(`Recovered ${missing.length} later Memory Core host event row(s) from ${source.filePath}`);
		} else {
			params.changes.push(records.length === 0 ? "Retired empty Memory Core host events legacy source" : `Migrated Memory Core host events -> SQLite plugin state (${missing.length} new row(s))`);
			claimFinalized = await finalizeLegacyMemoryHostEventSource({
				source,
				changes: params.changes,
				warnings: params.warnings
			});
			if (!claimFinalized) {
				params.changes.pop();
				return "blocked";
			}
		}
		const checkpoint = {
			kind: "migration-checkpoint",
			contentHash: crypto.createHash("sha256").update(raw).digest("hex"),
			recordCount: records.length,
			sequenceBase: sourceSequenceBase,
			size: Buffer.byteLength(raw, "utf8")
		};
		await checkpointStore.register(checkpointKey, checkpoint);
		const registeredCheckpoint = await checkpointStore.lookup(checkpointKey);
		if (!isMemoryHostMigrationCheckpoint(registeredCheckpoint) || registeredCheckpoint.contentHash !== checkpoint.contentHash || registeredCheckpoint.recordCount !== checkpoint.recordCount || registeredCheckpoint.sequenceBase !== checkpoint.sequenceBase || registeredCheckpoint.size !== checkpoint.size) return blocked(`Failed verifying Memory Core host event raw-archive checkpoint for ${source.filePath}`);
		if (source.storage !== "archive" && await source.root.exists(activeRelativePath)) params.warnings.push("An old writer recreated the Memory Core host event source; rerun openclaw doctor --fix to import the retained rows");
		return "completed";
	} finally {
		if (params.source.storage === "active" && !claimFinalized) await restoreClaimedMemoryHostEventSource({
			source,
			activeRelativePath,
			warnings: params.warnings
		});
	}
}
const hostEventsStateMigration = {
	id: "memory-core-host-events-jsonl-to-sqlite",
	label: "Memory Core host events",
	doctorOnly: true,
	async collectBackupResources(params) {
		const sources = await collectLegacyMemoryHostEventSources(params.config, params.env);
		const resources = [];
		for (const source of sources) {
			if (source.kind !== "ready") continue;
			resources.push({
				path: source.filePath,
				kind: "file"
			});
			if (source.storage === "active") {
				const generation = await resolveMemoryHostEventArchivePath(source);
				resources.push({
					path: path.join(source.workspaceDir, generation.claimRelativePath),
					kind: "file"
				}, {
					path: path.join(source.workspaceDir, generation.archiveRelativePath),
					kind: "file"
				});
			} else if (source.archiveRelativePath) resources.push({
				path: path.join(source.workspaceDir, source.archiveRelativePath),
				kind: "file"
			});
		}
		return resources;
	},
	async detectLegacyState(params) {
		const preview = [];
		for (const source of await collectLegacyMemoryHostEventSources(params.config, params.env)) if (source.kind === "rejected" || await memoryHostEventSourceNeedsMigration({
			source,
			context: params.context
		})) preview.push(source.kind === "ready" ? `- Memory Core host events: ${source.filePath} -> SQLite plugin state (${MEMORY_HOST_EVENTS_NAMESPACE})` : `- ${source.reason}`);
		return preview.length > 0 ? { preview } : null;
	},
	async migrateLegacyState(params) {
		const changes = [];
		const warnings = [];
		let advisoryWarningCount = 0;
		const blockedWorkspaces = /* @__PURE__ */ new Set();
		for (const source of await collectLegacyMemoryHostEventSources(params.config, params.env)) {
			if (blockedWorkspaces.has(source.workspaceDir)) continue;
			if (source.kind === "rejected") {
				warnings.push(source.reason);
				blockedWorkspaces.add(source.workspaceDir);
				continue;
			}
			if (!await memoryHostEventSourceNeedsMigration({
				source,
				context: params.context
			})) continue;
			const warningStart = warnings.length;
			const result = await migrateLegacyMemoryHostEventSource({
				source,
				context: params.context,
				changes,
				warnings
			});
			if (result === "warning") advisoryWarningCount += warnings.length - warningStart;
			if (result !== "completed") blockedWorkspaces.add(source.workspaceDir);
		}
		return {
			changes,
			warnings,
			...warnings.length > 0 && advisoryWarningCount === warnings.length ? { warningDisposition: "recoverable" } : {}
		};
	}
};
//#endregion
//#region extensions/memory-core/src/migration/doctor-memory-sidecar.ts
const LEGACY_MEMORY_SIDECAR_SUFFIXES = [
	"",
	"-wal",
	"-shm",
	"-journal"
];
async function existingPaths(candidates) {
	return (await Promise.all(candidates.map(async (filePath) => await legacyStateFileExists(filePath) ? filePath : null))).filter((filePath) => filePath !== null);
}
function existingLegacySidecarPaths(basePath) {
	return existingPaths(LEGACY_MEMORY_SIDECAR_SUFFIXES.map((suffix) => `${basePath}${suffix}`));
}
function formatLegacyVectorRows(count) {
	return count === void 0 ? "legacy vector rows" : `${count} vector row(s)`;
}
function resolveConfiguredAgentIds(config) {
	const { keyed, listed } = readDoctorAgentEntries(config);
	const listedIds = listed.flatMap((value) => {
		const id = asOptionalObjectRecord(value)?.id;
		return typeof id === "string" ? [id] : [];
	});
	const ids = new Set([...Object.keys(keyed ?? {}), ...listedIds].map(normalizeAgentId));
	return ids.size > 0 ? [...ids] : [normalizeAgentId(void 0)];
}
function readAgentMemorySearch(config, agentId) {
	const { keyed, listed } = readDoctorAgentEntries(config);
	const keyedEntry = Object.entries(keyed ?? {}).find(([id]) => normalizeAgentId(id) === agentId)?.[1];
	const keyedSearch = asOptionalObjectRecord(asOptionalObjectRecord(asOptionalObjectRecord(keyedEntry)?.memory)?.search);
	if (keyedSearch) return keyedSearch;
	const entry = listed.map(asOptionalObjectRecord).find((candidate) => normalizeAgentId(typeof candidate?.id === "string" ? candidate.id : void 0) === agentId);
	return asOptionalObjectRecord(asOptionalObjectRecord(entry?.memory)?.search);
}
function readMemorySearchLayers(config, agentId) {
	const cfg = asOptionalObjectRecord(config);
	return [
		readAgentMemorySearch(config, agentId),
		asOptionalObjectRecord(asOptionalObjectRecord(cfg?.memory)?.search),
		asOptionalObjectRecord(cfg?.memorySearch)
	].filter((value) => value !== void 0);
}
function readStoreLayers(searchLayers) {
	return searchLayers.flatMap((search) => {
		const store = asOptionalObjectRecord(search.store);
		return store ? [store] : [];
	});
}
function firstDefined(layers, key) {
	return layers.find((layer) => layer[key] !== void 0)?.[key];
}
function readMemorySearchMigrationOptions(config, agentId) {
	const searchLayers = readMemorySearchLayers(config, agentId);
	const stores = readStoreLayers(searchLayers);
	const nestedOption = (section, key) => stores.map((store) => asOptionalObjectRecord(store[section])?.[key]).find((value) => value !== void 0);
	const tokenizer = nestedOption("fts", "tokenizer");
	const provider = firstDefined(searchLayers, "provider");
	const enabled = nestedOption("vector", "enabled");
	const extensionPath = nestedOption("vector", "extensionPath");
	return {
		ftsTokenizer: tokenizer === "unicode61" || tokenizer === "trigram" ? tokenizer : void 0,
		vectorEnabled: !(typeof provider === "string" && provider.trim() === "none") && enabled !== false,
		vectorExtensionPath: typeof extensionPath === "string" && extensionPath.trim() ? extensionPath.trim() : void 0
	};
}
function readLegacyMemorySearchStorePaths(config, agentId) {
	return [...new Set(readStoreLayers(readMemorySearchLayers(config, agentId)).flatMap((store) => typeof store.path === "string" && store.path.trim() ? [store.path.trim()] : []))];
}
async function isCanonicalAgentDatabaseSymlink(params) {
	try {
		if (!(await fs.lstat(params.legacyPath)).isSymbolicLink()) return false;
		for (const suffix of LEGACY_MEMORY_SIDECAR_SUFFIXES.slice(1)) try {
			await fs.lstat(`${params.legacyPath}${suffix}`);
			return false;
		} catch (err) {
			if (!err || typeof err !== "object" || !("code" in err) || err.code !== "ENOENT") return false;
		}
		const [legacyTarget, canonicalTarget] = await Promise.all([fs.realpath(params.legacyPath), fs.realpath(params.agentDatabasePath)]);
		return legacyTarget === canonicalTarget;
	} catch {
		return false;
	}
}
async function collectLegacyMemorySidecarSources(params) {
	const agentIds = new Set(resolveConfiguredAgentIds(params.config));
	const legacyDir = path.join(params.stateDir, "memory");
	const retrySidecars = [];
	try {
		const entries = await fs.readdir(legacyDir, { withFileTypes: true });
		for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".sqlite")) {
			const stem = entry.name.slice(0, -7);
			const retryIndex = stem.indexOf(".retry-");
			const rawAgentId = retryIndex === -1 ? stem : stem.slice(0, retryIndex);
			const agentId = normalizeAgentId(rawAgentId);
			if (retryIndex !== -1 && rawAgentId === agentId && agentIds.has(agentId)) retrySidecars.push({
				agentId,
				legacyPath: path.join(legacyDir, entry.name)
			});
		}
	} catch {}
	const migrationEnv = {
		...params.env,
		OPENCLAW_STATE_DIR: params.stateDir
	};
	const sources = [];
	const seen = /* @__PURE__ */ new Set();
	async function addSource(agentId, legacyPath) {
		const normalizedPath = path.resolve(legacyPath);
		const key = `${agentId}\0${normalizedPath}`;
		if (seen.has(key) || !await legacyStateFileExists(normalizedPath)) return;
		seen.add(key);
		const { resolveOpenClawAgentSqlitePath } = await import("../../plugin-sdk/sqlite-runtime.js");
		const agentDatabasePath = resolveOpenClawAgentSqlitePath({
			agentId,
			env: migrationEnv
		});
		if (await isCanonicalAgentDatabaseSymlink({
			legacyPath: normalizedPath,
			agentDatabasePath
		})) return;
		sources.push({
			agentId,
			legacyPath: normalizedPath,
			stateDir: params.stateDir,
			agentDatabasePath
		});
	}
	for (const agentId of agentIds) {
		for (const configuredPath of readLegacyMemorySearchStorePaths(params.config, agentId)) await addSource(agentId, resolveUserPath(configuredPath.replaceAll("{agentId}", agentId), migrationEnv));
		await addSource(agentId, path.join(legacyDir, `${agentId}.sqlite`));
	}
	for (const retrySidecar of retrySidecars) await addSource(retrySidecar.agentId, retrySidecar.legacyPath);
	return sources;
}
async function archiveLegacyMemorySidecar(params) {
	const existingSources = await existingLegacySidecarPaths(params.source.legacyPath);
	if (existingSources.length === 0) return;
	const existingArchives = await existingPaths(existingSources.map((sourcePath) => `${sourcePath}.migrated`));
	if (existingArchives.length > 0) {
		params.warnings.push(`Left migrated Memory Core legacy memory index sidecar in place because ${existingArchives[0]} already exists`);
		return;
	}
	const renamed = [];
	for (const sourcePath of existingSources) {
		const archivedPath = `${sourcePath}.migrated`;
		try {
			await fs.rename(sourcePath, archivedPath);
			renamed.push({
				sourcePath,
				archivedPath
			});
		} catch (err) {
			for (const entry of renamed.toReversed()) try {
				if (await legacyStateFileExists(entry.archivedPath) && !await legacyStateFileExists(entry.sourcePath)) await fs.rename(entry.archivedPath, entry.sourcePath);
			} catch (rollbackErr) {
				params.warnings.push(`Failed restoring Memory Core legacy memory index sidecar ${entry.archivedPath}: ${String(rollbackErr)}`);
			}
			params.warnings.push(`Failed archiving Memory Core legacy memory index sidecar ${sourcePath}: ${String(err)}; restored ${renamed.length} already archived file(s)`);
			return;
		}
	}
	params.changes.push(`Archived Memory Core legacy memory index sidecar -> ${params.source.legacyPath}.migrated`);
}
async function preserveLegacyMemorySidecarRetryPath(params) {
	const retryPath = path.join(params.source.stateDir, "memory", `${params.source.agentId}.sqlite`);
	if (path.resolve(retryPath) === path.resolve(params.source.legacyPath)) return;
	const resolvedSourcePath = path.resolve(params.source.legacyPath);
	const sourceName = path.basename(resolvedSourcePath);
	if (path.dirname(resolvedSourcePath) === path.resolve(params.source.stateDir, "memory") && sourceName.startsWith(`${params.source.agentId}.retry-`) && sourceName.endsWith(".sqlite")) return;
	const targetBasePath = (await existingLegacySidecarPaths(retryPath)).length === 0 ? retryPath : path.join(params.source.stateDir, "memory", `${params.source.agentId}.retry-${crypto.createHash("sha256").update(path.resolve(params.source.legacyPath)).digest("hex").slice(0, 12)}.sqlite`);
	if (await legacyStateFileExists(targetBasePath)) return;
	const existingSources = (await existingLegacySidecarPaths(params.source.legacyPath)).map((sourcePath) => ({
		sourcePath,
		targetPath: `${targetBasePath}${sourcePath.slice(params.source.legacyPath.length)}`
	}));
	if (existingSources.length === 0) return;
	await fs.mkdir(path.dirname(targetBasePath), { recursive: true });
	const copied = [];
	try {
		for (const entry of existingSources) {
			await fs.copyFile(entry.sourcePath, entry.targetPath, fs.constants.COPYFILE_EXCL);
			copied.push(entry.targetPath);
		}
	} catch (err) {
		for (const targetPath of copied) try {
			await fs.rm(targetPath, { force: true });
		} catch {}
		params.warnings.push(`Failed copying Memory Core legacy memory index sidecar retry path ${params.source.legacyPath} -> ${retryPath}: ${String(err)}`);
		return;
	}
	params.changes.push(`Copied Memory Core legacy memory index sidecar retry path -> ${targetBasePath}`);
}
/**
* List the sidecar files when every persisted byte is absent: the main file is
* zero bytes and no WAL/journal sidecar holds content. Returns null when any
* file carries bytes, because WAL frames alone can contain legacy rows.
*/
async function listEmptyLegacySidecarFiles(legacyPath) {
	const emptyFiles = [];
	for (const suffix of LEGACY_MEMORY_SIDECAR_SUFFIXES) {
		const candidate = `${legacyPath}${suffix}`;
		try {
			const stat = await fs.stat(candidate);
			if (!stat.isFile()) continue;
			if (stat.size > 0) return null;
			emptyFiles.push(candidate);
		} catch (err) {
			if (err.code !== "ENOENT") return null;
		}
	}
	return emptyFiles.length > 0 ? emptyFiles : null;
}
async function migrateLegacyMemorySidecarSource(params) {
	const emptySidecarFiles = await listEmptyLegacySidecarFiles(params.source.legacyPath);
	if (emptySidecarFiles) {
		let removedAll = true;
		for (const emptyPath of emptySidecarFiles) try {
			await fs.rm(emptyPath, { force: true });
		} catch {
			removedAll = false;
		}
		if (removedAll) {
			params.changes.push(`Removed empty Memory Core legacy memory index sidecar placeholder: ${params.source.legacyPath}`);
			return false;
		}
	}
	const { importLegacyMemorySidecarIndex, LegacyMemoryDerivedRowsConflictError } = await import("../../doctor-memory-sidecar-import-CPNdU29R.mjs");
	const { ensureMemoryIndexSchema, loadSqliteVecExtension } = await import("../../plugin-sdk/memory-core-host-engine-schema.js");
	const { ensureOpenClawAgentDatabaseSchema, openNodeSqliteDatabase } = await import("../../plugin-sdk/sqlite-runtime.js");
	await fs.mkdir(path.dirname(params.source.agentDatabasePath), { recursive: true });
	const db = openNodeSqliteDatabase(params.source.agentDatabasePath, { allowExtension: true });
	try {
		const migrationEnv = {
			...params.env,
			OPENCLAW_STATE_DIR: params.source.stateDir
		};
		ensureOpenClawAgentDatabaseSchema(db, {
			agentId: params.source.agentId,
			env: migrationEnv,
			path: params.source.agentDatabasePath,
			register: true
		});
		const { ftsTokenizer, vectorEnabled, vectorExtensionPath } = readMemorySearchMigrationOptions(params.config, params.source.agentId);
		ensureMemoryIndexSchema({
			db,
			cacheEnabled: true,
			ftsEnabled: true,
			ftsTokenizer
		});
		const loadedVector = vectorEnabled ? await loadSqliteVecExtension({
			db,
			extensionPath: vectorExtensionPath ? resolveUserPath(vectorExtensionPath, params.env) : void 0
		}) : {
			ok: false,
			error: "vector search is disabled"
		};
		let result;
		try {
			result = importLegacyMemorySidecarIndex({
				db,
				legacySidecarDatabasePath: params.source.legacyPath,
				copyVectorRows: vectorEnabled && loadedVector.ok,
				requireVectorRows: vectorEnabled
			});
		} catch (err) {
			if (err instanceof LegacyMemoryDerivedRowsConflictError) {
				params.changes.push(`Resolved Memory Core legacy memory index conflict for agent ${params.source.agentId} by keeping canonical per-agent SQLite rows`);
				return true;
			}
			await preserveLegacyMemorySidecarRetryPath(params);
			params.warnings.push(`Skipped Memory Core legacy memory index import for agent ${params.source.agentId} because legacy rows could not be imported: ${String(err)}`);
			return false;
		}
		if (!result.imported) {
			await preserveLegacyMemorySidecarRetryPath(params);
			if (result.reason === "legacy-schema-missing") params.warnings.push(`Skipped Memory Core legacy memory index import for agent ${params.source.agentId} because the sidecar schema is not a legacy memory index`);
			return false;
		}
		ensureMemoryIndexSchema({
			db,
			cacheEnabled: true,
			ftsEnabled: true,
			ftsTokenizer
		});
		params.changes.push(`Migrated Memory Core legacy memory index for agent ${params.source.agentId} -> per-agent SQLite (${result.sources} source(s), ${result.chunks} chunk(s), ${result.cacheEntries} cache row(s))`);
		if (!result.vectorEntriesImported) {
			await preserveLegacyMemorySidecarRetryPath(params);
			const vectorReason = loadedVector.ok ? "legacy vector table could not be validated" : loadedVector.error ?? "unknown sqlite-vec load error";
			params.warnings.push(`Left Memory Core legacy memory index sidecar in place for agent ${params.source.agentId} because ${formatLegacyVectorRows(result.vectorEntries)} still require sqlite-vec: ${vectorReason}`);
			return false;
		}
		return true;
	} finally {
		db.close();
	}
}
const memorySidecarStateMigration = {
	id: "memory-core-legacy-sidecar-index-to-agent-sqlite",
	label: "Memory Core legacy memory index sidecar",
	async collectBackupResources(params) {
		const sources = await collectLegacyMemorySidecarSources(params);
		const resources = [];
		for (const source of sources) {
			resources.push({
				path: source.agentDatabasePath,
				kind: "sqlite"
			});
			for (const sourcePath of await existingLegacySidecarPaths(source.legacyPath)) resources.push({
				path: sourcePath,
				kind: "file"
			}, {
				path: `${sourcePath}.migrated`,
				kind: "file"
			});
			for (const name of [`${source.agentId}.sqlite`, `${source.agentId}.retry-${crypto.createHash("sha256").update(path.resolve(source.legacyPath)).digest("hex").slice(0, 12)}.sqlite`]) for (const suffix of LEGACY_MEMORY_SIDECAR_SUFFIXES) resources.push({
				path: path.join(params.stateDir, "memory", `${name}${suffix}`),
				kind: "file"
			});
		}
		return resources;
	},
	async detectLegacyState(params) {
		const sources = await collectLegacyMemorySidecarSources(params);
		if (sources.length === 0) return null;
		return { preview: sources.map((source) => `- Memory Core legacy memory index: ${source.legacyPath} -> ${source.agentDatabasePath}`) };
	},
	async migrateLegacyState(params) {
		const changes = [];
		const warnings = [];
		const groups = /* @__PURE__ */ new Map();
		for (const source of await collectLegacyMemorySidecarSources(params)) {
			const group = groups.get(source.legacyPath) ?? [];
			group.push(source);
			groups.set(source.legacyPath, group);
		}
		for (const sources of groups.values()) {
			let archiveReady = true;
			for (const source of sources) try {
				const sourceArchiveReady = await migrateLegacyMemorySidecarSource({
					source,
					config: params.config,
					env: params.env,
					changes,
					warnings
				});
				archiveReady &&= sourceArchiveReady;
			} catch (err) {
				archiveReady = false;
				await preserveLegacyMemorySidecarRetryPath({
					source,
					changes,
					warnings
				});
				warnings.push(`Skipped Memory Core legacy memory index import for agent ${source.agentId} because the sidecar could not be imported: ${String(err)}`);
			}
			if (archiveReady && sources[0]) await archiveLegacyMemorySidecar({
				source: sources[0],
				changes,
				warnings
			});
		}
		return {
			changes,
			warnings,
			...warnings.length > 0 ? { warningDisposition: "recoverable" } : {}
		};
	}
};
const RETIRED_QMD_GLOBAL_LOCK_NAME = "embed.lock.lock";
const RETIRED_QMD_AGENT_LOCK_NAME = "qmd-write.lock.lock";
async function collectRetiredQmdFileLocks(stateDir) {
	const { entries } = await walkDirectory(stateDir, {
		maxDepth: 3,
		symlinks: "skip",
		descend: (entry) => entry.depth === 1 && (entry.name === "qmd" || entry.name === "agents") || entry.depth === 2 && path.dirname(entry.relativePath) === "agents" && entry.name === normalizeAgentId(entry.name),
		include: (entry) => entry.kind === "file" && (entry.relativePath === path.join("qmd", RETIRED_QMD_GLOBAL_LOCK_NAME) || entry.depth === 3 && entry.name === RETIRED_QMD_AGENT_LOCK_NAME)
	});
	return entries.toSorted((left, right) => left.depth - right.depth || left.path.localeCompare(right.path)).map((entry) => entry.path);
}
async function collectRetiredQmdWorkspaceHomes(stateDir) {
	const { entries } = await walkDirectory(path.join(stateDir, "agents"), {
		maxDepth: 2,
		symlinks: "skip",
		descend: (entry) => entry.depth === 1 && entry.name === normalizeAgentId(entry.name),
		include: (entry) => entry.depth === 2 && entry.kind === "directory" && entry.name === "qmd"
	});
	const homes = [];
	for (const entry of entries) if ((await fs.readdir(entry.path).catch(() => void 0))?.length === 0) homes.push(entry.path);
	return homes.toSorted((left, right) => left.localeCompare(right));
}
function createQmdRetirementMigration(kind) {
	const workspace = kind === "workspace";
	const collect = workspace ? collectRetiredQmdWorkspaceHomes : collectRetiredQmdFileLocks;
	return {
		id: workspace ? "memory-core-qmd-workspace-retired" : "memory-core-qmd-file-locks-to-sqlite-leases",
		label: workspace ? "Memory Core retired QMD workspaces" : "Memory Core retired QMD file locks",
		...workspace ? { doctorOnly: true } : {},
		collectBackupResources: () => [],
		async detectLegacyState(params) {
			const paths = await collect(params.stateDir);
			return paths.length > 0 ? { preview: paths.map((filePath) => workspace ? `- Empty retired Memory Core QMD workspace: ${filePath} -> remove empty directory` : `- Retired Memory Core QMD file lock: ${filePath} -> remove only if definitely stale (coordination now uses SQLite leases)`) } : null;
		},
		async migrateLegacyState(params) {
			const changes = [];
			const warnings = [];
			for (const filePath of await collect(params.stateDir)) try {
				if (workspace) {
					await fs.rmdir(filePath);
					changes.push(`Removed empty retired Memory Core QMD workspace: ${filePath}`);
				} else {
					const result = await reclaimDefinitelyStaleFileLock(filePath);
					if (result === "removed") changes.push(`Removed retired Memory Core QMD file lock: ${filePath}`);
					else if (result === "retained") warnings.push(`Retained retired Memory Core QMD file lock because its owner is live or ambiguous: ${filePath}`);
				}
			} catch (err) {
				warnings.push(workspace ? `Skipped retired Memory Core QMD workspace cleanup. Run openclaw doctor --fix to retry. ${filePath}: ${String(err)}` : `Failed removing retired Memory Core QMD file lock ${filePath}: ${String(err)}`);
			}
			return {
				changes,
				warnings,
				...workspace && warnings.length > 0 ? { warningDisposition: "recoverable" } : {}
			};
		}
	};
}
//#endregion
//#region extensions/memory-core/doctor-contract-api.ts
const stateMigrations = [
	hostEventsStateMigration,
	dreamingStateMigration,
	dreamingCronMigration,
	memorySidecarStateMigration,
	createQmdRetirementMigration("workspace"),
	createQmdRetirementMigration("locks")
];
//#endregion
export { stateMigrations };