openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
544 lines (543 loc) • 25.2 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import { a as normalizeWindowsPathForComparison } from "./path-guards-Cp-mGr3-.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { m as tryReadJsonSync } from "./json-files-Bq1lIlQB.js";
import { a as loadInstalledPluginIndex } from "./installed-plugin-index-wrDsjyMD.js";
import { y as normalizeBundledLookupPath } from "./discovery-D_VDsZuY.js";
import { s as isExternallyDistributedPlugin } from "./official-external-plugin-catalog-Dzu7dwBN.js";
import { i as loadInstalledPluginIndexInstallRecordsSync, n as listRecoveredManagedNpmInstallCandidates, r as loadInstalledPluginIndexInstallRecords, s as clearLoadInstalledPluginIndexInstallRecordsCache } from "./installed-plugin-index-record-reader-SXWwf_BU.js";
import { i as markRetainedManagedNpmInstall, r as hasRetainedManagedNpmInstallMarker } from "./managed-npm-retention-DB42XOHb.js";
import { n as resolveInstalledManifestRegistryIndexFingerprint } from "./manifest-registry-installed-DdhpzefH.js";
import { r as writeJsonTarget } from "./json-file-CHTTn1IF.js";
import { r as removePluginInstallRecordFromRecords } from "./installed-plugin-index-records-C06Xozeq.js";
import { t as note } from "./note-DqHk3fA1.js";
import { a as resolveBundledPluginSources } from "./bundled-sources-CU4qjQGy.js";
import { t as refreshPluginRegistry } from "./plugin-registry-refresh-W0EPIfXj.js";
import { n as maybeRepairPluginOpenClawHostLinks, r as resolveDoctorPluginNpmRoots, t as listPluginOpenClawHostLinkIssues } from "./doctor-plugin-host-links-D0NqP-RD.js";
import { a as migratePluginRegistryForDoctor, i as migrateOfficialPluginInstallProvenance, o as preflightPluginRegistryDoctorMigration, t as InvalidPluginInstallRecordStateError } from "./plugin-registry-migration-qSLTSwwn.js";
import fs from "node:fs";
import path from "node:path";
//#region src/plugins/stale-local-bundled-plugin-install-records.ts
function normalizePathForCompare(rawPath, env) {
return path.resolve(normalizeBundledLookupPath(resolveUserPath(rawPath, env)));
}
function primaryInstallRecordPath(record) {
if (typeof record.installPath === "string" && record.installPath.trim()) return {
field: "installPath",
path: record.installPath
};
if (typeof record.sourcePath === "string" && record.sourcePath.trim()) return {
field: "sourcePath",
path: record.sourcePath
};
return null;
}
function looksLikeCompiledBundledPluginPath(targetPath, pluginId) {
const segments = normalizeBundledLookupPath(targetPath).split(/[\\/]+/u);
return segments.some((segment, index) => {
return (segment === "dist" || segment === "dist-runtime") && segments[index + 1] === "extensions" && segments[index + 2] === pluginId;
});
}
function hasStaleBundledVersion(record, bundledSource) {
const recordVersion = record.version?.trim();
const bundledVersion = bundledSource.version?.trim();
return Boolean(recordVersion && bundledVersion && recordVersion !== bundledVersion);
}
/** Lists path install records that still point at stale compiled bundled plugin output. */
function listStaleLocalBundledPluginInstallRecords(params) {
const bundled = params.bundled ?? resolveBundledPluginSources({
workspaceDir: params.workspaceDir,
env: params.env
});
const stale = [];
for (const [pluginId, record] of Object.entries(params.installRecords).toSorted(([left], [right]) => left.localeCompare(right))) {
if (record.source !== "path") continue;
const bundledSource = bundled.get(pluginId);
if (!bundledSource?.localPath) continue;
if (!hasStaleBundledVersion(record, bundledSource)) continue;
const recordPath = primaryInstallRecordPath(record);
if (!recordPath) continue;
const stalePath = normalizePathForCompare(recordPath.path, params.env);
const bundledPath = normalizePathForCompare(bundledSource.localPath, params.env);
if (stalePath === bundledPath) continue;
if (!looksLikeCompiledBundledPluginPath(stalePath, pluginId)) continue;
stale.push({
pluginId,
record,
recordPathField: recordPath.field,
stalePath,
bundledPath
});
}
return stale;
}
/** Removes stale compiled bundled plugin path records from an install record map. */
function pruneStaleLocalBundledPluginInstallRecords(params) {
const stale = listStaleLocalBundledPluginInstallRecords(params);
if (stale.length === 0) return {
records: params.installRecords,
stale
};
const staleIds = new Set(stale.map((record) => record.pluginId));
return {
records: Object.fromEntries(Object.entries(params.installRecords).filter(([pluginId]) => !staleIds.has(pluginId))),
stale
};
}
//#endregion
//#region src/commands/doctor-plugin-generations.ts
const PLUGIN_REGISTRY_CHECK_ID = "core/doctor/plugin-registry";
function normalizeManagedInstallPath(filePath) {
const resolved = path.resolve(filePath);
return process.platform === "win32" ? normalizeWindowsPathForComparison(resolved) : resolved;
}
async function listStaleManagedNpmInstallGenerations(params) {
const activeRecords = await loadInstalledPluginIndexInstallRecords(params);
const candidates = listRecoveredManagedNpmInstallCandidates(params);
const candidatesByPluginId = /* @__PURE__ */ new Map();
for (const candidate of candidates) {
const entries = candidatesByPluginId.get(candidate.pluginId) ?? [];
entries.push(candidate);
candidatesByPluginId.set(candidate.pluginId, entries);
}
const stale = [];
for (const [pluginId, pluginCandidates] of candidatesByPluginId) {
const activeRecord = activeRecords[pluginId];
if (activeRecord?.source !== "npm" || !activeRecord.installPath) continue;
const activePath = normalizeManagedInstallPath(activeRecord.installPath);
if (!pluginCandidates.find((candidate) => candidate.installRecord.installPath && normalizeManagedInstallPath(candidate.installRecord.installPath) === activePath)) continue;
for (const candidate of pluginCandidates) {
const packageDir = candidate.installRecord.installPath;
if (!packageDir || normalizeManagedInstallPath(packageDir) === activePath) continue;
stale.push({
kind: "stale-managed-npm-install-generation",
pluginId,
activePackageDir: activeRecord.installPath,
packageDir,
...candidate.installRecord.resolvedVersion ? { version: candidate.installRecord.resolvedVersion } : {}
});
}
}
return stale.toSorted((left, right) => left.packageDir.localeCompare(right.packageDir));
}
/** Marks non-authoritative managed npm trees for safe cleanup after gateway shutdown. */
async function maybeRepairStaleManagedNpmInstallGenerations(params) {
const stale = await listStaleManagedNpmInstallGenerations(params);
if (stale.length === 0) return false;
if (!params.prompter.shouldRepair) {
note([
"Managed npm plugin installs have stale non-authoritative generations:",
...stale.map((generation) => `- ${generation.pluginId}: ${shortenHomePath(generation.packageDir)}${generation.version ? ` (${generation.version})` : ""}`),
`Repair with ${formatCliCommand("openclaw doctor --fix")} to retire stale generations after the gateway restarts.`
].join("\n"), "Plugin registry");
return false;
}
const retired = [];
for (const generation of stale) if (await markRetainedManagedNpmInstall({
packageDir: generation.packageDir,
pluginId: generation.pluginId,
reason: "doctor-repaired-stale-managed-npm-generation"
})) retired.push(generation);
if (retired.length === 0) return false;
clearLoadInstalledPluginIndexInstallRecordsCache();
note(["Retired stale managed npm plugin generation(s); they will be pruned after the gateway restarts:", ...retired.map((generation) => `- ${generation.pluginId}: ${shortenHomePath(generation.packageDir)}${generation.version ? ` (${generation.version})` : ""}`)].join("\n"), "Plugin registry");
return true;
}
function staleManagedNpmInstallGenerationToHealthFinding(issue) {
return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Managed npm plugin ${issue.pluginId}${issue.version ? `@${issue.version}` : ""} is a stale non-authoritative generation.`,
path: issue.packageDir,
target: issue.pluginId,
fixHint: "Run `openclaw doctor --fix` to retire the stale generation for pruning after the gateway restarts."
};
}
function staleManagedNpmInstallGenerationToRepairEffect(issue) {
return {
kind: "package",
action: "would-retire-stale-managed-npm-install-generation",
target: issue.packageDir,
dryRunSafe: false
};
}
//#endregion
//#region src/commands/doctor-plugin-registry.ts
/** Doctor repairs for stale plugin registry entries, managed npm shadows, and peer links. */
function readJsonObject(filePath) {
const parsed = tryReadJsonSync(filePath);
return isRecord(parsed) ? parsed : null;
}
function readStringMap(value) {
if (!isRecord(value)) return {};
const result = {};
for (const [key, raw] of Object.entries(value)) if (typeof raw === "string" && raw.trim()) result[key] = raw.trim();
return result;
}
function deleteObjectKey(record, key) {
if (!Object.hasOwn(record, key)) return false;
delete record[key];
return true;
}
function readPackageVersion(packageDir) {
const version = readJsonObject(path.join(packageDir, "package.json"))?.version;
return typeof version === "string" && version.trim() ? version.trim() : void 0;
}
function readPluginManifestId(packageDir) {
const id = readJsonObject(path.join(packageDir, "openclaw.plugin.json"))?.id;
return typeof id === "string" && id.trim() ? id.trim() : void 0;
}
function listStaleManagedNpmBundledPlugins(params) {
const currentBundled = loadInstalledPluginIndex({
...params,
installRecords: {}
}).plugins.filter((plugin) => plugin.origin === "bundled" && !isExternallyDistributedPlugin(plugin));
const bundledByPackage = new Map(currentBundled.map((plugin) => [plugin.packageName, plugin]));
const stale = [];
for (const npmRoot of resolveDoctorPluginNpmRoots(params)) {
const dependencies = readStringMap(readJsonObject(path.join(npmRoot, "package.json"))?.dependencies);
for (const packageName of Object.keys(dependencies).toSorted((left, right) => left.localeCompare(right))) {
if (!packageName.startsWith("@openclaw/")) continue;
const bundled = bundledByPackage.get(packageName);
if (!bundled) continue;
const packageDir = path.join(npmRoot, "node_modules", ...packageName.split("/"));
if (hasRetainedManagedNpmInstallMarker(packageDir)) continue;
const pluginId = readPluginManifestId(packageDir);
if (!pluginId || pluginId !== bundled.pluginId) continue;
stale.push({
pluginId,
packageName,
packageDir,
npmRoot,
...readPackageVersion(packageDir) ? { version: readPackageVersion(packageDir) } : {}
});
}
}
return stale;
}
function loadCurrentBundledPluginSources(params) {
const currentBundled = loadInstalledPluginIndex({
...params,
installRecords: {}
}).plugins.filter((plugin) => plugin.origin === "bundled");
return new Map(currentBundled.map((plugin) => [plugin.pluginId, {
pluginId: plugin.pluginId,
localPath: plugin.rootDir,
...plugin.packageName ? { npmSpec: plugin.packageName } : {},
...plugin.packageVersion ? { version: plugin.packageVersion } : {}
}]));
}
async function listStaleLocalBundledPluginInstallRecordShadows(params) {
return listStaleLocalBundledPluginInstallRecords({
installRecords: await loadInstalledPluginIndexInstallRecords(params),
workspaceDir: params.workspaceDir,
env: params.env,
bundled: loadCurrentBundledPluginSources(params)
});
}
function removeManagedNpmDependency(params) {
const npmPackageJsonPath = path.join(params.npmRoot, "package.json");
const packageJson = readJsonObject(npmPackageJsonPath) ?? {};
const dependencies = readStringMap(packageJson.dependencies);
delete dependencies[params.packageName];
const nextPackageJson = Object.keys(dependencies).length === 0 ? (() => {
const { dependencies: _dependencies, ...rest } = packageJson;
return rest;
})() : {
...packageJson,
dependencies
};
writeJsonTarget(npmPackageJsonPath, nextPackageJson);
removeManagedNpmPackageLockDependency(params);
fs.rmSync(params.packageDir, {
recursive: true,
force: true
});
const scopeDir = path.dirname(params.packageDir);
if (path.basename(path.dirname(scopeDir)) === "node_modules") try {
fs.rmdirSync(scopeDir);
} catch {}
}
function removeManagedNpmPackageLockDependency(params) {
const packageLockPath = path.join(params.npmRoot, "package-lock.json");
const packageLock = readJsonObject(packageLockPath);
if (!packageLock) return;
let changed = false;
const packages = packageLock.packages;
if (isRecord(packages)) {
const rootPackage = packages[""];
if (isRecord(rootPackage)) {
const rootDependencies = readStringMap(rootPackage.dependencies);
if (deleteObjectKey(rootDependencies, params.packageName)) {
changed = true;
if (Object.keys(rootDependencies).length === 0) delete rootPackage.dependencies;
else rootPackage.dependencies = rootDependencies;
}
}
changed = deleteObjectKey(packages, `node_modules/${params.packageName}`) || changed;
}
const dependencies = packageLock.dependencies;
if (isRecord(dependencies)) changed = deleteObjectKey(dependencies, params.packageName) || changed;
if (changed) writeJsonTarget(packageLockPath, packageLock);
}
/** Removes managed npm packages that shadow current bundled plugins when repair is enabled. */
function maybeRepairStaleManagedNpmBundledPlugins(params) {
const stale = listStaleManagedNpmBundledPlugins(params);
if (stale.length === 0) return null;
if (!params.prompter.shouldRepair) {
note([
"Managed npm plugin packages shadow bundled plugins:",
...stale.map((plugin) => `- ${plugin.pluginId}: ${plugin.packageName}${plugin.version ? `@${plugin.version}` : ""}`),
`Repair with ${formatCliCommand("openclaw doctor --fix")} to remove stale managed npm packages and rebuild the plugin registry.`
].join("\n"), "Plugin registry");
return null;
}
let installRecords = params.installRecords ?? loadInstalledPluginIndexInstallRecordsSync(params);
const removedPluginIds = [...new Set(stale.map((plugin) => plugin.pluginId))].toSorted((left, right) => left.localeCompare(right));
for (const pluginId of removedPluginIds) installRecords = removePluginInstallRecordFromRecords(installRecords, pluginId);
for (const plugin of stale) removeManagedNpmDependency(plugin);
note(["Removed stale managed npm plugin package(s) shadowing bundled plugins:", ...stale.map((plugin) => `- ${plugin.pluginId}: ${plugin.packageName}${plugin.version ? `@${plugin.version}` : ""}`)].join("\n"), "Plugin registry");
return {
installRecords,
removedPluginIds
};
}
/** Removes local install records that shadow current bundled plugin sources. */
async function maybeRepairStaleLocalBundledPluginInstallRecords(params) {
const stale = await listStaleLocalBundledPluginInstallRecordShadows(params);
if (stale.length === 0) return [];
if (!params.prompter.shouldRepair) {
note([
"Local bundled plugin install records shadow bundled plugins:",
...stale.map((record) => `- ${record.pluginId}: ${shortenHomePath(record.stalePath)}`),
`Repair with ${formatCliCommand("openclaw doctor --fix")} to remove stale local install records and rebuild the plugin registry.`
].join("\n"), "Plugin registry");
return [];
}
note(["Removed stale local bundled plugin install record(s) shadowing bundled plugins:", ...stale.map((record) => `- ${record.pluginId}: ${shortenHomePath(record.stalePath)}`)].join("\n"), "Plugin registry");
return stale.map((record) => record.pluginId);
}
async function loadRepairedPluginInstallRecords(params, pluginIds, baselineRecords) {
let records = baselineRecords ?? await loadInstalledPluginIndexInstallRecords(params);
for (const pluginId of pluginIds) records = removePluginInstallRecordFromRecords(records, pluginId);
return migrateOfficialPluginInstallProvenance(records);
}
async function detectPluginRegistryHealthIssues(params) {
const preflight = preflightPluginRegistryDoctorMigration(params);
const issues = [];
if (preflight.action === "migrate") issues.push({
kind: "registry-missing-or-stale",
path: preflight.filePath
});
for (const plugin of listStaleManagedNpmBundledPlugins(params)) issues.push({
kind: "stale-managed-npm-bundled-plugin",
pluginId: plugin.pluginId,
packageName: plugin.packageName,
packageDir: plugin.packageDir,
npmRoot: plugin.npmRoot,
...plugin.version ? { version: plugin.version } : {}
});
for (const record of await listStaleLocalBundledPluginInstallRecordShadows(params)) issues.push({
kind: "stale-local-bundled-plugin-install-record",
pluginId: record.pluginId,
stalePath: record.stalePath
});
issues.push(...await listStaleManagedNpmInstallGenerations(params));
const hostLinkAudit = await listPluginOpenClawHostLinkIssues(params);
for (const issue of hostLinkAudit.peerLinkIssues) issues.push({
kind: "managed-npm-openclaw-peer-link",
packageName: issue.packageName,
packageDir: issue.packageDir,
reason: issue.reason
});
for (const failure of hostLinkAudit.packageReadFailures) issues.push({
kind: "managed-npm-package-unreadable",
packageDir: failure.packageDir,
reason: failure.reason
});
for (const issue of hostLinkAudit.registeredPeerLinkIssues) issues.push({
kind: "registered-npm-openclaw-host-link",
packageName: issue.packageName,
packageDir: issue.packageDir,
reason: issue.reason
});
for (const failure of hostLinkAudit.registeredPackageReadFailures) issues.push({
kind: "registered-npm-package-unreadable",
packageDir: failure.packageDir,
reason: failure.reason
});
return issues;
}
function pluginRegistryIssueToHealthFinding(issue) {
switch (issue.kind) {
case "registry-missing-or-stale": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: "Persisted plugin registry is missing or stale.",
path: issue.path,
fixHint: "Run `openclaw doctor --fix` to rebuild the plugin registry from enabled plugins."
};
case "stale-managed-npm-bundled-plugin": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Managed npm package ${issue.packageName}${issue.version ? `@${issue.version}` : ""} shadows bundled plugin ${issue.pluginId}.`,
path: issue.packageDir,
target: issue.pluginId,
fixHint: "Run `openclaw doctor --fix` to remove stale managed npm packages and rebuild the plugin registry."
};
case "stale-local-bundled-plugin-install-record": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Local install record for bundled plugin ${issue.pluginId} points at a stale path.`,
path: issue.stalePath,
target: issue.pluginId,
fixHint: "Run `openclaw doctor --fix` to remove stale local install records and rebuild the plugin registry."
};
case "managed-npm-openclaw-peer-link": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Managed npm package ${issue.packageName} has a broken OpenClaw peer link: ${issue.reason}.`,
path: issue.packageDir,
target: issue.packageName,
fixHint: "Run `openclaw doctor --fix` to relink managed npm plugin packages."
};
case "registered-npm-openclaw-host-link": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Registered npm plugin ${issue.packageName} has a broken OpenClaw host link: ${issue.reason}.`,
path: issue.packageDir,
target: issue.packageName,
fixHint: "Run `openclaw doctor --fix` to relink the installed npm plugin package."
};
case "managed-npm-package-unreadable": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Managed npm package could not be inspected: ${issue.reason}.`,
path: issue.packageDir,
fixHint: "Restore access to the package files, then run `openclaw doctor` again."
};
case "registered-npm-package-unreadable": return {
checkId: PLUGIN_REGISTRY_CHECK_ID,
severity: "warning",
message: `Registered npm plugin package could not be inspected: ${issue.reason}.`,
path: issue.packageDir,
fixHint: "Restore access to the package files, then run `openclaw doctor` again."
};
case "stale-managed-npm-install-generation": return staleManagedNpmInstallGenerationToHealthFinding(issue);
}
return assertNeverPluginRegistryIssue(issue);
}
function pluginRegistryIssueToRepairEffect(issue) {
switch (issue.kind) {
case "registry-missing-or-stale": return {
kind: "state",
action: "would-rebuild-plugin-registry",
target: issue.path,
dryRunSafe: false
};
case "stale-managed-npm-bundled-plugin": return {
kind: "package",
action: "would-remove-stale-managed-npm-bundled-plugin",
target: issue.packageDir,
dryRunSafe: false
};
case "stale-local-bundled-plugin-install-record": return {
kind: "state",
action: "would-remove-stale-local-bundled-plugin-install-record",
target: issue.pluginId,
dryRunSafe: false
};
case "managed-npm-openclaw-peer-link": return {
kind: "package",
action: "would-relink-managed-npm-openclaw-peer",
target: issue.packageDir,
dryRunSafe: false
};
case "registered-npm-openclaw-host-link": return {
kind: "package",
action: "would-relink-registered-npm-openclaw-host",
target: issue.packageDir,
dryRunSafe: false
};
case "managed-npm-package-unreadable": return {
kind: "package",
action: "requires-managed-npm-package-readability-repair",
target: issue.packageDir,
dryRunSafe: false
};
case "registered-npm-package-unreadable": return {
kind: "package",
action: "requires-registered-npm-package-readability-repair",
target: issue.packageDir,
dryRunSafe: false
};
case "stale-managed-npm-install-generation": return staleManagedNpmInstallGenerationToRepairEffect(issue);
}
return assertNeverPluginRegistryIssue(issue);
}
function assertNeverPluginRegistryIssue(issue) {
throw new Error(`Unhandled plugin registry issue kind: ${String(issue.kind)}`);
}
/**
* Runs plugin registry doctor repairs and refreshes the persisted plugin index when needed.
*
* Stale bundled shadows are removed before registry migration so the rebuilt index resolves the
* current bundled source instead of an obsolete managed/local install record.
*/
async function maybeRepairPluginRegistryState(params) {
let preflight;
try {
preflight = preflightPluginRegistryDoctorMigration(params);
} catch (error) {
if (!(error instanceof InvalidPluginInstallRecordStateError)) throw error;
note(error.message, "Plugin registry");
return { config: params.config };
}
clearLoadInstalledPluginIndexInstallRecordsCache();
const migrationParams = {
...params,
config: params.config
};
const staleManagedNpmBundledPluginRepair = maybeRepairStaleManagedNpmBundledPlugins(params);
const removedStaleLocalBundledPluginIds = await maybeRepairStaleLocalBundledPluginInstallRecords(params);
await maybeRepairStaleManagedNpmInstallGenerations(params);
const repairedPluginOpenClawHostLinks = await maybeRepairPluginOpenClawHostLinks(params);
const stalePluginIdsToRemove = [.../* @__PURE__ */ new Set([...staleManagedNpmBundledPluginRepair?.removedPluginIds ?? [], ...removedStaleLocalBundledPluginIds])];
if (!params.prompter.shouldRepair) {
if (preflight.action === "migrate") note(["Persisted plugin registry is missing or stale.", `Repair with ${formatCliCommand("openclaw doctor --fix")} to rebuild ${shortenHomePath(preflight.filePath)} from enabled plugins.`].join("\n"), "Plugin registry");
return { config: params.config };
}
const installRecords = await loadRepairedPluginInstallRecords(params, stalePluginIdsToRemove, staleManagedNpmBundledPluginRepair?.installRecords);
if (preflight.action !== "skip-existing") {
const result = await migratePluginRegistryForDoctor({
...migrationParams,
installRecords
});
if (result.migrated) {
const total = result.current.plugins.length;
const enabled = result.current.plugins.filter((plugin) => plugin.enabled).length;
note(`Plugin registry rebuilt: ${enabled}/${total} enabled plugins indexed.`, "Plugin registry");
}
return {
config: params.config,
...result.migrated ? { pluginInventoryChanged: true } : {}
};
}
const index = await refreshPluginRegistry({
...migrationParams,
reason: "migration",
installRecords
});
const total = index.plugins.length;
const enabled = index.plugins.filter((plugin) => plugin.enabled).length;
note(`Plugin registry refreshed: ${enabled}/${total} enabled plugins indexed.`, "Plugin registry");
const indexChanged = resolveInstalledManifestRegistryIndexFingerprint(preflight.current) !== resolveInstalledManifestRegistryIndexFingerprint(index);
return {
config: params.config,
...indexChanged || repairedPluginOpenClawHostLinks ? { pluginInventoryChanged: true } : {}
};
}
//#endregion
export { pluginRegistryIssueToRepairEffect as a, pluginRegistryIssueToHealthFinding as i, maybeRepairPluginRegistryState as n, pruneStaleLocalBundledPluginInstallRecords as o, maybeRepairStaleManagedNpmBundledPlugins as r, detectPluginRegistryHealthIssues as t };