openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
380 lines (379 loc) • 22.9 kB
JavaScript
import { f as withPluginCache, i as createPluginCache } from "./plugin-cache-DGWspMEc.js";
import { n as isPathInside, o as safeRealpathSync } from "./path-safety-Bi0ppMWC.js";
import { m as tryReadJsonSync } from "./json-files-Bq1lIlQB.js";
import { i as resolveBundledPluginsDir } from "./bundled-dir-BEfbEeCF.js";
import { n as resolveOpenClawDevSourceRoot } from "./dev-source-root-DCu97Qvh.js";
import { o as getPackageManifestMetadata } from "./manifest-ByRdkf9X.js";
import { _ as resolveInstalledPluginIndexPolicyHash, d as resolvePluginDoctorContractArtifactPath, f as extractPluginInstallRecordsFromInstalledPluginIndex, i as isInstalledPluginEnabled, n as getInstalledPluginRecord, o as loadInstalledPluginIndexWithDiscovery, r as hasInstalledPluginIndexWorkspaceScopeMismatch, u as hasOptionalMissingPluginManifestFile } from "./installed-plugin-index-wrDsjyMD.js";
import { l as normalizePluginsConfig } from "./config-state-BkU1frVq.js";
import { S as hashJson, T as safeHashFile, g as listBundledSourceOverlayDirs, t as discoverConfiguredPluginLoadPaths, v as buildLegacyBundledRootPath, w as safeFileSignature } from "./discovery-D_VDsZuY.js";
import "./path-safety-BT3PFs5V.js";
import { n as resolvePluginSourceRoots } from "./roots-BBmaCv0d.js";
import { r as resolveInstalledPluginIndexInstallOwner, t as isInstalledPluginIndexInstallOwnerAmbiguous } from "./installed-plugin-index-install-owner-Bd-Byre8.js";
import { n as loadPluginManifestRegistryCore } from "./manifest-registry-DCCgYk7q.js";
import { i as loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader-SXWwf_BU.js";
import { n as hasConfigPathActivationMetadataMigration, r as hasMissingConfigPathActivationMetadata } from "./installed-plugin-index-config-path-scope-BAeCI-f_.js";
import { n as getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot-CmSX4G3W.js";
import { n as resolvePluginControlPlaneWorkspace, t as appendPluginControlPlaneWorkspaceDiagnostic } from "./control-plane-workspace-BWhgt2gz.js";
import { o as readPersistedInstalledPluginIndexSync } from "./installed-plugin-index-store-MVV6aa7C.js";
import { n as hasMissingInstalledPluginOwnerMetadata } from "./installed-plugin-package-ownership-DGTVMT4h.js";
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import path from "node:path";
//#region src/plugins/installed-plugin-index-invalidation.ts
function diffInstalledPluginIndexInvalidationReasons(previous, current) {
const reasons = /* @__PURE__ */ new Set();
if (previous.version !== current.version) reasons.add("missing");
if (previous.hostContractVersion !== current.hostContractVersion) reasons.add("host-contract-changed");
if (previous.compatRegistryVersion !== current.compatRegistryVersion) reasons.add("compat-registry-changed");
if (previous.migrationVersion !== current.migrationVersion) reasons.add("migration");
if (previous.policyHash !== current.policyHash) reasons.add("policy-changed");
if (hashJson(previous.installRecords ?? {}) !== hashJson(current.installRecords ?? {})) reasons.add("source-changed");
const previousByPluginId = new Map(previous.plugins.map((plugin) => [plugin.pluginId, plugin]));
const currentByPluginId = new Map(current.plugins.map((plugin) => [plugin.pluginId, plugin]));
for (const [pluginId, previousPlugin] of previousByPluginId) {
const currentPlugin = currentByPluginId.get(pluginId);
if (!currentPlugin) {
reasons.add("source-changed");
continue;
}
if (previousPlugin.rootDir !== currentPlugin.rootDir || previousPlugin.manifestPath !== currentPlugin.manifestPath || previousPlugin.source !== currentPlugin.source || previousPlugin.setupSource !== currentPlugin.setupSource || resolveInstalledPluginIndexInstallOwner(previousPlugin) !== resolveInstalledPluginIndexInstallOwner(currentPlugin) || isInstalledPluginIndexInstallOwnerAmbiguous(previousPlugin) !== isInstalledPluginIndexInstallOwnerAmbiguous(currentPlugin) || previousPlugin.installRecordHash !== currentPlugin.installRecordHash) reasons.add("source-changed");
if (previousPlugin.enabled !== currentPlugin.enabled) reasons.add("policy-changed");
if (hasConfigPathActivationMetadataMigration({
previous: previousPlugin,
current: currentPlugin
})) reasons.add("migration");
if (previousPlugin.manifestHash !== currentPlugin.manifestHash || previousPlugin.doctorContractHash !== currentPlugin.doctorContractHash) reasons.add("stale-manifest");
if (previousPlugin.packageVersion !== currentPlugin.packageVersion || previousPlugin.packageJson?.path !== currentPlugin.packageJson?.path || previousPlugin.packageJson?.hash !== currentPlugin.packageJson?.hash) reasons.add("stale-package");
}
for (const pluginId of currentByPluginId.keys()) if (!previousByPluginId.has(pluginId)) {
if (currentByPluginId.get(pluginId)?.enabled === false) continue;
reasons.add("source-changed");
}
return Array.from(reasons).toSorted((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/plugins/plugin-registry-comparison.ts
function isContainedPluginPath(rootPath, targetPath, cache) {
const resolveProjectedPath = (inputPath) => {
const target = path.resolve(inputPath);
for (let cursor = target;; cursor = path.dirname(cursor)) try {
fs.lstatSync(cursor);
const realCursor = safeRealpathSync(cursor, cache);
return realCursor ? path.resolve(realCursor, path.relative(cursor, target)) : null;
} catch {
if (cursor === path.dirname(cursor)) return null;
}
};
const root = resolveProjectedPath(rootPath);
const target = resolveProjectedPath(targetPath);
return Boolean(root && target && isPathInside(root, target));
}
function resolvePluginRegistryRecordContent(plugin, comparePackageJsonPath) {
const { doctorContractFile: _doctorContractFile, manifestFile: _manifestFile, packageBuild, packageJson, ...record } = plugin;
const stableRecord = Object.assign(record, packageBuild?.bundledDist === void 0 ? {} : { packageBuild: { bundledDist: packageBuild.bundledDist } });
if (!packageJson || !comparePackageJsonPath) return stableRecord;
const { fileSignature: _fileSignature, path: packageJsonPath, ...stablePackageJson } = packageJson;
return Object.assign(stableRecord, { packageJson: Object.assign(stablePackageJson, { path: packageJsonPath }) });
}
function resolvePluginRegistryContent(index, comparePackageJsonPath, excludedPlugins) {
const { generatedAtMs: _generatedAtMs, refreshReason: _refreshReason, warning: _warning, ...content } = index;
const excludedRoots = [...excludedPlugins?.values() ?? []].map((root) => path.resolve(root));
const exclusionPathCache = /* @__PURE__ */ new Map();
return {
...content,
diagnostics: excludedPlugins ? content.diagnostics.filter((diagnostic) => !(diagnostic.pluginId && excludedPlugins.has(diagnostic.pluginId) || diagnostic.source && excludedRoots.some((root) => isContainedPluginPath(root, diagnostic.source, exclusionPathCache)))) : content.diagnostics,
installRecords: excludedPlugins ? Object.fromEntries(Object.entries(content.installRecords).filter(([pluginId]) => !excludedPlugins.has(pluginId))) : content.installRecords,
plugins: content.plugins.filter((plugin) => !excludedPlugins?.has(plugin.pluginId)).map((plugin) => resolvePluginRegistryRecordContent(plugin, comparePackageJsonPath))
};
}
function diffPluginRegistryRecords(persisted, derived, comparePackageJsonPath, excludedPlugins) {
const persistedPlugins = new Map(persisted.plugins.map((plugin) => [plugin.pluginId, plugin]));
const derivedPlugins = new Map(derived.plugins.map((plugin) => [plugin.pluginId, plugin]));
return [.../* @__PURE__ */ new Set([
...persistedPlugins.keys(),
...derivedPlugins.keys(),
...Object.keys(persisted.installRecords),
...Object.keys(derived.installRecords),
...persisted.diagnostics.flatMap((diagnostic) => diagnostic.pluginId ? [diagnostic.pluginId] : []),
...derived.diagnostics.flatMap((diagnostic) => diagnostic.pluginId ? [diagnostic.pluginId] : [])
])].filter((pluginId) => !excludedPlugins.has(pluginId)).toSorted((left, right) => left.localeCompare(right)).flatMap((pluginId) => {
const persistedPlugin = persistedPlugins.get(pluginId);
const derivedPlugin = derivedPlugins.get(pluginId);
const persistedContent = [
persistedPlugin ? resolvePluginRegistryRecordContent(persistedPlugin, comparePackageJsonPath) : void 0,
persisted.installRecords[pluginId],
persisted.diagnostics.filter((diagnostic) => diagnostic.pluginId === pluginId)
];
const derivedContent = [
derivedPlugin ? resolvePluginRegistryRecordContent(derivedPlugin, comparePackageJsonPath) : void 0,
derived.installRecords[pluginId],
derived.diagnostics.filter((diagnostic) => diagnostic.pluginId === pluginId)
];
return isDeepStrictEqual(persistedContent, derivedContent) ? [] : [{
pluginId,
persistedSource: persistedPlugin?.source ?? persistedPlugin?.manifestPath ?? null,
derivedSource: derivedPlugin?.source ?? derivedPlugin?.manifestPath ?? null
}];
});
}
//#endregion
//#region src/plugins/plugin-registry-snapshot.ts
function resolveControlPlaneRegistryParams(params) {
if (!params.config) return params;
const workspace = resolvePluginControlPlaneWorkspace({
config: params.config,
env: params.env,
workspaceDir: params.workspaceDir
});
const diagnostics = appendPluginControlPlaneWorkspaceDiagnostic(params.diagnostics ?? [], workspace);
return {
...params,
...diagnostics.length > 0 ? { diagnostics } : {},
...workspace.workspaceDir !== void 0 ? { workspaceDir: workspace.workspaceDir } : {}
};
}
function canReuseCurrentPluginMetadataSnapshot(params) {
return params.allowCurrent !== false && params.preferPersisted !== false && params.stateDir === void 0 && params.filePath === void 0 && params.pluginIndexFilePath === void 0 && params.installRecords === void 0 && params.candidates === void 0 && params.diagnostics === void 0 && params.discovery === void 0 && params.now === void 0;
}
function loadCurrentPluginRegistrySnapshotResult(params) {
if (!canReuseCurrentPluginMetadataSnapshot(params)) return;
const current = getCurrentPluginMetadataSnapshot({
config: params.config,
env: params.env ?? process.env,
...params.workspaceDir !== void 0 ? { workspaceDir: params.workspaceDir } : {}
});
if (!current) return;
return {
snapshot: current.index,
source: current.registrySource ?? (current.registryDiagnostics.length > 0 ? "derived" : "provided"),
diagnostics: current.registryDiagnostics,
...current.discovery ? { discovery: current.discovery } : {},
manifestRegistry: current.manifestRegistry
};
}
function fileContentMatches(filePath, hash, signature, trustSignature = true) {
const current = safeFileSignature(filePath);
if (!current) return false;
if (trustSignature && signature?.ctimeMs !== void 0 && current.size === signature.size && current.mtimeMs === signature.mtimeMs && current.ctimeMs === signature.ctimeMs) return true;
return safeHashFile({
filePath,
diagnostics: [],
required: false
}) === hash;
}
function hasStaleDoctorContractFile(plugin, rootExists) {
if (!rootExists && !plugin.enabled) return false;
const contractPath = resolvePluginDoctorContractArtifactPath(plugin.rootDir);
return contractPath ? !plugin.doctorContractHash || !fileContentMatches(contractPath, plugin.doctorContractHash, plugin.doctorContractFile) : plugin.doctorContractHash !== void 0 || plugin.doctorContractFile !== void 0;
}
function hasStalePersistedPluginFiles(index) {
const realpathCache = /* @__PURE__ */ new Map();
return index.plugins.some((plugin) => {
if (!isContainedPluginPath(plugin.rootDir, plugin.rootDir, realpathCache)) return true;
const rootExists = fs.existsSync(plugin.rootDir);
if (!rootExists && plugin.enabled) return true;
for (const artifactPath of [
plugin.source,
plugin.setupSource,
plugin.manifestPath
]) if (artifactPath && !isContainedPluginPath(plugin.rootDir, artifactPath, realpathCache)) return true;
if (plugin.enabled && ((plugin.source ? !fs.existsSync(plugin.source) : false) || (plugin.setupSource ? !fs.existsSync(plugin.setupSource) : false))) return true;
if (!hasOptionalMissingPluginManifestFile(plugin)) {
if (!fs.existsSync(plugin.manifestPath)) {
if (plugin.enabled) return true;
} else if (!fileContentMatches(plugin.manifestPath, plugin.manifestHash, plugin.manifestFile)) return true;
}
if (hasStaleDoctorContractFile(plugin, rootExists)) return true;
if (!plugin.packageJson) return false;
const packageJsonPath = path.resolve(plugin.rootDir, plugin.packageJson.path);
if (!isContainedPluginPath(plugin.rootDir, packageJsonPath, realpathCache)) return true;
if (!fs.existsSync(packageJsonPath)) return plugin.enabled;
if (!isRealPathInside(plugin.rootDir, packageJsonPath, realpathCache)) return true;
return !fileContentMatches(packageJsonPath, plugin.packageJson.hash, plugin.packageJson.fileSignature, plugin.origin === "bundled");
});
}
function isRealPathInside(parentPath, childPath, cache) {
const parent = safeRealpathSync(parentPath, cache);
const child = safeRealpathSync(childPath, cache);
return Boolean(parent && child && isPathInside(parent, child));
}
function hasMismatchedPersistedBundledRoot(index, env) {
const bundledRoot = resolveBundledPluginsDir(env);
if (!bundledRoot) return false;
const realpathCache = /* @__PURE__ */ new Map();
const overlays = listBundledSourceOverlayDirs({
bundledRoot,
env
});
const legacyRoot = buildLegacyBundledRootPath(bundledRoot);
const sourceCheckout = legacyRoot && fs.existsSync(path.join(path.dirname(legacyRoot), ".git")) && fs.existsSync(path.join(path.dirname(legacyRoot), "pnpm-workspace.yaml")) && fs.existsSync(path.join(path.dirname(legacyRoot), "src"));
return index.plugins.some((plugin) => {
if (plugin.origin !== "bundled") return false;
if (!plugin.enabled && !fs.existsSync(plugin.rootDir)) return ![
bundledRoot,
...overlays,
...legacyRoot ? [legacyRoot] : []
].some((root) => isContainedPluginPath(root, plugin.rootDir, realpathCache));
if (isRealPathInside(bundledRoot, plugin.rootDir, realpathCache)) {
const sourcePluginRoot = legacyRoot && path.join(legacyRoot, path.relative(safeRealpathSync(bundledRoot, realpathCache) ?? bundledRoot, safeRealpathSync(plugin.rootDir, realpathCache) ?? plugin.rootDir));
return Boolean(sourcePluginRoot && (overlays.some((root) => isRealPathInside(root, sourcePluginRoot, realpathCache)) || sourceCheckout && getPackageManifestMetadata(tryReadJsonSync(path.join(sourcePluginRoot, "package.json")) ?? void 0)?.build?.bundledDist === false));
}
return !overlays.some((root) => isRealPathInside(root, plugin.rootDir, realpathCache)) && !(plugin.packageBuild?.bundledDist === false && legacyRoot && isRealPathInside(legacyRoot, plugin.rootDir, realpathCache));
});
}
function hasRecoveredInstallRecordsMissingFromPersistedIndex(index, params, env) {
const installRecords = loadInstalledPluginIndexInstallRecordsSync({
env,
...params.stateDir ? { stateDir: params.stateDir } : {},
...params.filePath ? { filePath: params.filePath } : params.pluginIndexFilePath ? { filePath: params.pluginIndexFilePath } : {}
});
return Object.keys(installRecords).some((pluginId) => !index.installRecords?.[pluginId]);
}
function requiresDerivedRegistryValidation(index, params, env, hasStalePluginFiles) {
return hasStalePluginFiles() || hasInstalledPluginIndexWorkspaceScopeMismatch(index, params.workspaceDir) || params.candidates !== void 0 || params.discovery !== void 0 || params.diagnostics !== void 0 || params.installRecords !== void 0 || resolveOpenClawDevSourceRoot(env) !== null || normalizePluginsConfig(params.config?.plugins).loadPaths.length > 0 || hasMissingConfigPathActivationMetadata(index) || hasMissingInstalledPluginOwnerMetadata(index, env) || index.diagnostics.some(({ pluginId, source }) => Boolean(pluginId && source && path.isAbsolute(source) && !fs.existsSync(source))) || hasMismatchedPersistedBundledRoot(index, env) || hasRecoveredInstallRecordsMissingFromPersistedIndex(index, params, env) || hasConfiguredGlobalSourcePluginMissingFromPersistedIndex(params, index, env);
}
function hasConfiguredGlobalSourcePluginMissingFromPersistedIndex(params, index, env) {
const plugins = normalizePluginsConfig(params.config?.plugins);
const persistedPluginIds = new Set(index.plugins.map((plugin) => plugin.pluginId));
const missingConfiguredPluginIds = new Set([
...Object.keys(plugins.entries),
...plugins.allow,
...Object.values(plugins.slots).filter((pluginId) => pluginId != null)
].filter((pluginId) => !persistedPluginIds.has(pluginId)));
if (missingConfiguredPluginIds.size === 0) return false;
const globalExtensionsRoot = resolvePluginSourceRoots({
workspaceDir: params.workspaceDir,
env
}).global;
const discovery = discoverConfiguredPluginLoadPaths({
loadPaths: [globalExtensionsRoot],
workspaceDir: params.workspaceDir,
env
});
return loadPluginManifestRegistryCore({
config: params.config,
workspaceDir: params.workspaceDir,
env,
candidates: discovery.candidates,
diagnostics: discovery.diagnostics,
installRecords: extractPluginInstallRecordsFromInstalledPluginIndex(index)
}).plugins.some((plugin) => missingConfiguredPluginIds.has(plugin.id));
}
function loadPluginRegistrySnapshotWithMetadata(params = {}) {
if (params.index) return {
snapshot: params.index,
source: "provided",
diagnostics: []
};
const current = loadCurrentPluginRegistrySnapshotResult(params);
if (current) return current;
const env = params.env ?? process.env;
if (!(params.preferPersisted !== false)) {
const derived = loadInstalledPluginIndexWithDiscovery({
...params,
installRecords: params.installRecords ?? {}
});
return {
snapshot: derived.index,
source: "derived",
diagnostics: [],
discovery: derived.discovery,
manifestRegistry: derived.manifestRegistry
};
}
const diagnostics = [];
const persistedIndex = readPersistedInstalledPluginIndexSync(params);
let stalePluginFiles;
const hasStalePluginFiles = () => stalePluginFiles ??= persistedIndex ? hasStalePersistedPluginFiles(persistedIndex) : false;
if (!persistedIndex) diagnostics.push({
level: "info",
code: "persisted-registry-missing",
message: "Persisted plugin registry is missing or invalid; using derived plugin index."
});
else if (params.config && persistedIndex.policyHash !== resolveInstalledPluginIndexPolicyHash(params.config, params.env)) diagnostics.push({
level: "warn",
code: "persisted-registry-stale-policy",
message: "Persisted plugin registry policy does not match current config; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry."
});
else if (!requiresDerivedRegistryValidation(persistedIndex, params, env, hasStalePluginFiles)) return {
snapshot: persistedIndex,
source: "persisted",
diagnostics
};
const derived = loadInstalledPluginIndexWithDiscovery({
...params,
...params.filePath && !params.pluginIndexFilePath ? { pluginIndexFilePath: params.filePath } : {}
});
const comparePackageJsonPath = params.candidates !== void 0 || params.discovery !== void 0 || hasStalePluginFiles();
const excludedMissingDisabledPlugins = /* @__PURE__ */ new Map();
if (persistedIndex && params.candidates === void 0 && params.discovery === void 0 && params.installRecords === void 0 && !hasStalePluginFiles() && !hasMismatchedPersistedBundledRoot(persistedIndex, env)) {
const derivedPluginIds = new Set(derived.index.plugins.map((plugin) => plugin.pluginId));
for (const plugin of persistedIndex.plugins) if (!plugin.enabled && !derivedPluginIds.has(plugin.pluginId)) excludedMissingDisabledPlugins.set(plugin.pluginId, plugin.rootDir);
}
const contentMatches = persistedIndex && diagnostics.length === 0 && isDeepStrictEqual(resolvePluginRegistryContent(persistedIndex, comparePackageJsonPath, excludedMissingDisabledPlugins), resolvePluginRegistryContent(derived.index, comparePackageJsonPath, excludedMissingDisabledPlugins));
if (persistedIndex && contentMatches) {
const packageMetadataMatches = isDeepStrictEqual(resolvePluginRegistryContent(persistedIndex, true), resolvePluginRegistryContent(derived.index, true));
return {
snapshot: persistedIndex,
source: "persisted",
diagnostics,
discovery: derived.discovery,
...packageMetadataMatches ? { manifestRegistry: derived.manifestRegistry } : {}
};
} else if (persistedIndex && diagnostics.length === 0) {
const differences = diffPluginRegistryRecords(persistedIndex, derived.index, comparePackageJsonPath, excludedMissingDisabledPlugins);
diagnostics.push({
level: "warn",
code: "persisted-registry-stale-source",
message: "Persisted plugin registry no longer matches current plugin discovery or metadata; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry.",
...differences.length > 0 ? { differences } : {}
});
}
return {
snapshot: derived.index,
source: "derived",
diagnostics,
discovery: derived.discovery,
manifestRegistry: derived.manifestRegistry
};
}
function loadPluginRegistrySnapshot(params = {}) {
return loadPluginRegistrySnapshotWithMetadata(params).snapshot;
}
function getPluginRecord(params) {
return getInstalledPluginRecord(loadPluginRegistrySnapshot(params), params.pluginId);
}
function isPluginEnabled(params) {
return isInstalledPluginEnabled(loadPluginRegistrySnapshot(params), params.pluginId, params.config);
}
async function inspectPluginRegistry(params = {}) {
return withPluginCache(createPluginCache(), () => {
const inspectionParams = resolveControlPlaneRegistryParams(params);
const persisted = readPersistedInstalledPluginIndexSync(inspectionParams);
const result = loadPluginRegistrySnapshotWithMetadata({
...inspectionParams,
allowCurrent: false
});
if (!persisted) return {
state: "missing",
refreshReasons: ["missing"],
differences: [],
persisted: null,
current: result.snapshot
};
const fresh = result.source === "persisted";
const differences = result.diagnostics.flatMap((diagnostic) => diagnostic.differences ?? []);
const refreshReasons = fresh ? [] : [...diffInstalledPluginIndexInvalidationReasons(persisted, result.snapshot)];
if (!fresh && refreshReasons.length === 0) refreshReasons.push(result.diagnostics.some((diagnostic) => diagnostic.code === "persisted-registry-stale-policy") ? "policy-changed" : "source-changed");
return {
state: fresh ? "fresh" : "stale",
refreshReasons,
differences,
persisted,
current: result.snapshot
};
});
}
//#endregion
export { loadPluginRegistrySnapshotWithMetadata as a, loadPluginRegistrySnapshot as i, inspectPluginRegistry as n, resolveControlPlaneRegistryParams as o, isPluginEnabled as r, diffInstalledPluginIndexInvalidationReasons as s, getPluginRecord as t };