UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

380 lines (379 loc) 22.9 kB
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 };