UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

401 lines (400 loc) 20.3 kB
import { s as readConfigFileSnapshot, y as copyRuntimeConfigWriteApplication } from "./io.runtime-B9iJRs3w.js"; import { r as isPathInside } from "./path-guards-Cp-mGr3-.js"; import { p as resolvePluginNpmProjectsDir, s as resolveDefaultPluginNpmDir } from "./install-paths-ChnxikBv.js"; import { r as resolveInstalledPluginIndexInstallOwner } from "./installed-plugin-index-install-owner-Bd-Byre8.js"; import { i as getPluginInstallRecordMapEntry, l as setPluginInstallRecordMapEntry, n as copyPluginInstallRecordMap, r as createPluginInstallRecordMap } from "./plugin-install-record-map-B44vZyl2.js"; import { r as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-SXWwf_BU.js"; import { a as resolveRetainedManagedNpmInstallMarkerPath, i as markRetainedManagedNpmInstall, n as clearRetainedManagedNpmInstallMarker, o as resolveRetainedManagedNpmInstallPackageInfo } from "./managed-npm-retention-DB42XOHb.js"; import { a as readPersistedInstalledPluginIndex } from "./installed-plugin-index-store-MVV6aa7C.js"; import { b as resolveConfigWriteAfterWrite } from "./runtime-snapshot-BaQikjTR.js"; import { a as transformConfigFileWithRetry, r as replaceConfigFile } from "./mutate-ZNN4iFCn.js"; import "./config-Cs0XXL3x.js"; import { i as restorePersistedInstalledPluginIndexIfCurrent } from "./installed-plugin-index-store-write-DRg854w2.js"; import { c as writePersistedInstalledPluginIndexInstallRecordsWithLease, o as withoutPluginInstallRecords, t as PLUGIN_INSTALLS_CONFIG_PATH } from "./installed-plugin-index-records-C06Xozeq.js"; import { t as withPluginLifecycleLease } from "./plugin-lifecycle-lease-BtAFH872.js"; import { n as recordPluginPackageUninstallPlan } from "./uninstall-package-plan-BcbmXONg.js"; import { a as planPluginUninstall } from "./uninstall-CTOrqce4.js"; import fs from "node:fs"; import { isDeepStrictEqual } from "node:util"; import path from "node:path"; //#region src/plugins/install-record-commit.ts function mergeUnsetPaths(left, right) { const merged = [...left ?? [], ...right ?? []]; return merged.length > 0 ? merged : void 0; } /** Return whether config still contains legacy/transient plugin install records. */ function hasPendingPluginInstallRecords(config) { return Object.keys(config.plugins?.installs ?? {}).length > 0; } function pluginInstallRecordMapsEqual(left, right) { const leftEntries = Object.entries(left); return leftEntries.length === Object.keys(right).length && leftEntries.every(([pluginId, record]) => Object.hasOwn(right, pluginId) && isDeepStrictEqual(getPluginInstallRecordMapEntry(right, pluginId), record)); } /** Find pending install records that match the base config and can be stripped as unchanged. */ function unchangedPendingPluginInstallRecordIds(config, baseConfig) { const pendingInstalls = config.plugins?.installs ?? {}; return Object.entries(baseConfig.plugins?.installs ?? {}).filter(([pluginId, baseInstall]) => isDeepStrictEqual(getPluginInstallRecordMapEntry(pendingInstalls, pluginId), baseInstall)).map(([pluginId]) => pluginId); } /** Remove pending plugin install records from config, optionally only for selected ids. */ function stripPendingPluginInstallRecords(config, pluginIds) { if (!pluginIds) return withoutPluginInstallRecords(config); const removeIds = new Set(pluginIds); if (removeIds.size === 0 || !config.plugins?.installs) return config; const remainingInstalls = createPluginInstallRecordMap(); for (const [pluginId, record] of Object.entries(config.plugins.installs)) if (!removeIds.has(pluginId)) setPluginInstallRecordMapEntry(remainingInstalls, pluginId, record); if (Object.keys(remainingInstalls).length === 0) return withoutPluginInstallRecords(config); return { ...config, plugins: { ...config.plugins, installs: remainingInstalls } }; } const PLUGIN_SOURCE_CHANGED_RESTART_REASON = "plugin source changed"; function mergeAfterWrite(writeOptions, afterWrite) { if (afterWrite === void 0) return writeOptions; return copyRuntimeConfigWriteApplication(writeOptions, { ...writeOptions, afterWrite }); } function isMissingInstallPathError(error) { const code = error.code; return code === "ENOENT" || code === "ENOTDIR"; } function resolveExistingInstallPath(installPath) { const resolvedPath = path.resolve(installPath); try { return fs.realpathSync(resolvedPath); } catch (error) { if (isMissingInstallPathError(error)) return resolvedPath; throw error; } } function installPathsOverlap(left, right) { const resolvedLeft = resolveExistingInstallPath(left); const resolvedRight = resolveExistingInstallPath(right); return resolvedLeft === resolvedRight || isPathInside(resolvedLeft, resolvedRight) || isPathInside(resolvedRight, resolvedLeft); } function resolveRetainedManagedNpmInstallMarkerTarget(params) { if (params.previousRecord?.source !== "npm") return null; const previousInstallPath = params.previousRecord.installPath?.trim(); const nextInstallPath = params.nextRecord?.installPath?.trim(); if (!previousInstallPath) return null; if (params.nextRecord && (!nextInstallPath || installPathsOverlap(previousInstallPath, nextInstallPath))) return null; if (params.nextRecord?.source !== "npm") { const packageInfo = resolveRetainedManagedNpmInstallPackageInfo(previousInstallPath); if (!packageInfo) return null; try { const configuredNpmRoot = path.resolve(resolveDefaultPluginNpmDir()); const npmRoot = fs.realpathSync(configuredNpmRoot); const configuredProjectRoot = path.resolve(packageInfo.projectRoot); const projectRoot = fs.realpathSync(configuredProjectRoot); const packageDir = fs.realpathSync(previousInstallPath); if (path.relative(configuredNpmRoot, configuredProjectRoot) !== path.relative(npmRoot, projectRoot) || path.relative(configuredProjectRoot, path.resolve(previousInstallPath)) !== path.relative(projectRoot, packageDir)) return null; if (projectRoot === npmRoot) return previousInstallPath; const projectsRoot = fs.realpathSync(resolvePluginNpmProjectsDir(npmRoot)); return path.dirname(projectRoot) === projectsRoot ? previousInstallPath : null; } catch (error) { if (isMissingInstallPathError(error)) return null; throw error; } } const installs = createPluginInstallRecordMap(); setPluginInstallRecordMapEntry(installs, params.pluginId, params.previousRecord); const plan = planPluginUninstall(recordPluginPackageUninstallPlan({ config: { plugins: { installs } }, pluginId: params.pluginId, deleteFiles: true }, { runtimePluginIds: [] })); if (!plan.ok || !plan.directoryRemoval || plan.directoryRemoval.cleanup?.kind !== "npm") return null; if (nextInstallPath && installPathsOverlap(previousInstallPath, nextInstallPath)) return null; return previousInstallPath; } function resolveNpmInstallRecordPackageName(record) { if (record.source !== "npm" || !record.installPath?.trim()) return null; return resolveRetainedManagedNpmInstallPackageInfo(record.installPath)?.packageName ?? null; } function findReplacementNpmRecordForRemovedRecord(params) { const previousPackageName = resolveNpmInstallRecordPackageName(params.previousRecord); if (!previousPackageName) return null; for (const nextRecord of Object.values(params.nextInstallRecords)) if (resolveNpmInstallRecordPackageName(nextRecord) === previousPackageName) return nextRecord; return null; } async function markRetiredManagedNpmInstallRecords(params) { const markedPreviousPluginIds = /* @__PURE__ */ new Set(); const activeInstallPaths = Object.values(params.nextInstallRecords).flatMap((record) => { const installPath = record.installPath?.trim(); return installPath ? [installPath] : []; }); const markRetiredInstall = async (pluginId, previousRecord, nextRecord) => { const previousInstallPath = previousRecord?.installPath?.trim(); if (previousInstallPath && activeInstallPaths.some((installPath) => installPathsOverlap(previousInstallPath, installPath))) return; const packageDir = resolveRetainedManagedNpmInstallMarkerTarget({ pluginId, previousRecord, nextRecord }); if (!packageDir) return; const markerPath = resolveRetainedManagedNpmInstallMarkerPath(packageDir); const markerAlreadyExisted = fs.existsSync(markerPath); if (await markRetainedManagedNpmInstall({ packageDir, pluginId, reason: nextRecord?.source === "npm" ? "replaced-by-managed-npm-generation-update" : nextRecord ? "replaced-by-plugin-source-change" : "removed-managed-npm-install-retained" }) && !markerAlreadyExisted) params.createdMarkerPaths.push(markerPath); markedPreviousPluginIds.add(pluginId); }; for (const [pluginId, nextRecord] of Object.entries(params.nextInstallRecords)) await markRetiredInstall(pluginId, getPluginInstallRecordMapEntry(params.previousInstallRecords, pluginId), nextRecord); for (const [pluginId, previousRecord] of Object.entries(params.previousInstallRecords)) { if (markedPreviousPluginIds.has(pluginId) || getPluginInstallRecordMapEntry(params.nextInstallRecords, pluginId)) continue; await markRetiredInstall(pluginId, previousRecord, findReplacementNpmRecordForRemovedRecord({ previousRecord, nextInstallRecords: params.nextInstallRecords }) ?? void 0); } } async function removeCreatedRetainedManagedNpmInstallMarkers(markerPaths) { for (const markerPath of markerPaths) await fs.promises.rm(markerPath, { force: true }); } async function clearActiveRetainedManagedNpmInstallMarkers(nextInstallRecords, clearedMarkers) { for (const record of Object.values(nextInstallRecords)) { if (record.source !== "npm" || !record.installPath?.trim()) continue; let markerPath; try { markerPath = resolveRetainedManagedNpmInstallMarkerPath(record.installPath); } catch { continue; } let contents; try { contents = await fs.promises.readFile(markerPath, "utf8"); } catch (error) { if (error.code === "ENOENT") continue; throw error; } if (await clearRetainedManagedNpmInstallMarker(record.installPath)) clearedMarkers.push({ markerPath, contents }); } } async function restoreClearedRetainedManagedNpmInstallMarkers(markerSnapshots) { for (const snapshot of markerSnapshots) { await fs.promises.mkdir(path.dirname(snapshot.markerPath), { recursive: true }); await fs.promises.writeFile(snapshot.markerPath, snapshot.contents, "utf8"); } } /** Recheck staged enablement at its config writer, after any intervening plugin update. */ async function assertPluginConfigActivationConsent(params) { const records = params.nextInstallRecords ?? await loadInstalledPluginIndexInstallRecords(); if (Object.keys(records).length === 0) return; const { resolvePluginMetadataSnapshot } = await import("./plugin-metadata-snapshot-aBISjjrE.js"); const { resolvePluginCapabilityConsent } = await import("./capability-consent-0zWUyIwq.js"); const { resolvePluginControlPlaneWorkspace } = await import("./control-plane-workspace-BBn83JfT.js"); const snapshot = await readConfigFileSnapshot(); const metadataForConfig = (config) => resolvePluginMetadataSnapshot({ config, allowCurrent: false, workspaceDir: resolvePluginControlPlaneWorkspace({ config }).workspaceDir }); const previous = metadataForConfig(snapshot.config); const next = metadataForConfig(params.nextConfig); const previouslyEnabled = new Set(previous.index.plugins.filter((plugin) => snapshot.valid && plugin.enabled).map((plugin) => plugin.pluginId)); for (const plugin of next.index.plugins) { if (!plugin.enabled || plugin.origin === "bundled") continue; const owner = resolveInstalledPluginIndexInstallOwner(plugin) ?? plugin.pluginId; const record = records[owner]; const previousRecord = params.previousInstallRecords?.[owner]; if (params.previousInstallRecords !== void 0 && (!previousRecord || record?.acceptedSurface !== void 0) && !isDeepStrictEqual(previousRecord, record) || !previouslyEnabled.has(plugin.pluginId)) await resolvePluginCapabilityConsent({ config: params.nextConfig, pluginId: plugin.pluginId, metadata: next }); } } async function commitPluginInstallRecordsWithWriter(params) { return await withPluginLifecycleLease({}, async (lease) => { let tentativeWrite; const retainedMarkerPaths = []; const clearedMarkerSnapshots = []; try { const storeOptions = { filePath: lease.databasePath }; const prepared = await params.prepareInstallRecords(storeOptions); await params.beforePersistentEffect?.(); tentativeWrite = await writePersistedInstalledPluginIndexInstallRecordsWithLease(prepared.nextInstallRecords, { ...storeOptions, config: params.nextConfig, lease }); if (params.recheckStagedActivation) { const nextIndex = await readPersistedInstalledPluginIndex(storeOptions); if (!nextIndex || nextIndex.plugins.some((plugin) => plugin.enabled && plugin.origin !== "bundled")) await assertPluginConfigActivationConsent({ nextConfig: params.nextConfig, previousInstallRecords: prepared.previousInstallRecords, nextInstallRecords: prepared.nextInstallRecords }); } await markRetiredManagedNpmInstallRecords({ previousInstallRecords: prepared.previousInstallRecords, nextInstallRecords: prepared.nextInstallRecords, createdMarkerPaths: retainedMarkerPaths }); await clearActiveRetainedManagedNpmInstallMarkers(prepared.nextInstallRecords, clearedMarkerSnapshots); const installRecordsChanged = !pluginInstallRecordMapsEqual(prepared.previousInstallRecords, prepared.nextInstallRecords); const writeOptions = copyRuntimeConfigWriteApplication(params.writeOptions, { ...params.writeOptions, ...params.beforePersistentEffect ? { beforeCommit: async () => { await params.writeOptions?.beforeCommit?.(); await params.beforePersistentEffect?.(); } } : {}, ...installRecordsChanged && params.writeOptions?.afterWrite === void 0 ? { afterWrite: { mode: "restart", reason: PLUGIN_SOURCE_CHANGED_RESTART_REASON } } : {}, unsetPaths: mergeUnsetPaths(params.writeOptions?.unsetPaths, [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)]) }); return { committed: await params.commit(params.nextConfig, writeOptions), nextInstallRecords: prepared.nextInstallRecords }; } catch (error) { const tentative = tentativeWrite; if (tentative) try { if (await restorePersistedInstalledPluginIndexIfCurrent(tentative.previous, tentative.revision, { filePath: lease.databasePath, lease })) { await restoreClearedRetainedManagedNpmInstallMarkers(clearedMarkerSnapshots); await removeCreatedRetainedManagedNpmInstallMarkers(retainedMarkerPaths); } } catch (rollbackError) { throw new Error("Failed to commit plugin install records and could not roll back tentative plugin state", { cause: rollbackError }); } throw error; } }); } /** Persist plugin install records and commit the matching config update to disk. */ async function commitPluginInstallRecordsWithConfig(params) { await commitPluginInstallRecordsWithWriter({ prepareInstallRecords: async (storeOptions) => ({ previousInstallRecords: params.previousInstallRecords ?? await loadInstalledPluginIndexInstallRecords(storeOptions), nextInstallRecords: params.nextInstallRecords }), nextConfig: params.nextConfig, beforePersistentEffect: params.beforePersistentEffect, ...params.writeOptions ? { writeOptions: params.writeOptions } : {}, commit: async (nextConfig, writeOptions) => { return await replaceConfigFile({ nextConfig, ...params.baseHash !== void 0 ? { baseHash: params.baseHash } : {}, ...writeOptions ? { writeOptions } : {} }); } }); } /** Persist plugin install records without rewriting the user-authored config file. */ async function commitPluginInstallRecordsOnly(params) { await commitPluginInstallRecordsWithWriter({ prepareInstallRecords: async (storeOptions) => ({ previousInstallRecords: params.previousInstallRecords ?? await loadInstalledPluginIndexInstallRecords(storeOptions), nextInstallRecords: params.nextInstallRecords }), nextConfig: params.nextConfig, commit: async () => { await params.verifyConfigFresh?.(); } }); } /** Commit config while migrating any pending install records into the install index. */ async function commitConfigWriteWithPendingPluginInstalls(params) { const sourceInstallRecords = params.sourceConfig?.plugins?.installs ?? {}; const nextPendingConfig = params.sourceConfig ? stripPendingPluginInstallRecords(params.nextConfig, unchangedPendingPluginInstallRecordIds(params.nextConfig, { plugins: { installs: sourceInstallRecords } })) : params.nextConfig; if (Object.keys(sourceInstallRecords).length === 0 && !hasPendingPluginInstallRecords(nextPendingConfig)) { const committed = await withPluginLifecycleLease({}, async () => { await assertPluginConfigActivationConsent({ nextConfig: params.nextConfig }); return params.writeOptions ? await params.commit(params.nextConfig, params.writeOptions) : await params.commit(params.nextConfig); }); return { config: params.nextConfig, installRecords: {}, movedInstallRecords: false, persistedHash: committed?.persistedHash ?? null }; } const pendingInstallRecords = nextPendingConfig.plugins?.installs ?? {}; const strippedConfig = withoutPluginInstallRecords(params.nextConfig); const result = await commitPluginInstallRecordsWithWriter({ prepareInstallRecords: async (storeOptions) => { const previousInstallRecords = await loadInstalledPluginIndexInstallRecords(storeOptions); const nextInstallRecords = copyPluginInstallRecordMap(sourceInstallRecords); for (const records of [previousInstallRecords, pendingInstallRecords]) for (const [pluginId, record] of Object.entries(records)) setPluginInstallRecordMapEntry(nextInstallRecords, pluginId, record); return { previousInstallRecords, nextInstallRecords }; }, nextConfig: strippedConfig, recheckStagedActivation: true, ...params.writeOptions ? { writeOptions: params.writeOptions } : {}, commit: params.commit }); return { config: strippedConfig, installRecords: result.nextInstallRecords, movedInstallRecords: true, persistedHash: result.committed?.persistedHash ?? null }; } /** Replace the config file after moving pending plugin install records into the install index. */ async function commitConfigWithPendingPluginInstalls(params) { return await commitConfigWriteWithPendingPluginInstalls({ nextConfig: params.nextConfig, ...params.writeOptions ? { writeOptions: params.writeOptions } : {}, commit: async (nextConfig, writeOptions) => { return await replaceConfigFile({ nextConfig, ...params.baseHash !== void 0 ? { baseHash: params.baseHash } : {}, ...writeOptions ? { writeOptions } : {} }); } }); } /** Transform config with retry support while preserving plugin install index consistency. */ async function transformConfigWithPendingPluginInstalls(params) { const commit = async ({ nextConfig, snapshot, baseHash, writeOptions }) => { const requestedAfterWrite = params.afterWrite ?? params.writeOptions?.afterWrite; const committed = await commitConfigWriteWithPendingPluginInstalls({ nextConfig, sourceConfig: snapshot.sourceConfig, ...writeOptions ? { writeOptions: mergeAfterWrite(writeOptions, params.afterWrite) } : {}, commit: async (config, commitWriteOptions) => { return await replaceConfigFile({ nextConfig: config, snapshot, writeOptions: commitWriteOptions ?? {}, ...baseHash !== void 0 ? { baseHash } : {} }); } }); const afterWrite = resolveConfigWriteAfterWrite(requestedAfterWrite ?? (committed.movedInstallRecords ? { mode: "restart", reason: PLUGIN_SOURCE_CHANGED_RESTART_REASON } : void 0)); return { config: committed.config, persistedHash: committed.persistedHash, afterWrite }; }; return await withPluginLifecycleLease({}, async () => { return await transformConfigFileWithRetry({ ...params, commit }); }); } //#endregion export { hasPendingPluginInstallRecords as a, unchangedPendingPluginInstallRecordIds as c, commitPluginInstallRecordsWithConfig as i, commitConfigWriteWithPendingPluginInstalls as n, stripPendingPluginInstallRecords as o, commitPluginInstallRecordsOnly as r, transformConfigWithPendingPluginInstalls as s, commitConfigWithPendingPluginInstalls as t };