UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

131 lines (130 loc) 8.01 kB
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js"; import { l as readConfigFileSnapshotForWrite } from "./io.runtime-B9iJRs3w.js"; import "./utils-P__uGsPB.js"; import { c as tracePluginLifecyclePhaseAsync } from "./discovery-D_VDsZuY.js"; import { r as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-SXWwf_BU.js"; import "./config-Cs0XXL3x.js"; import "./installed-plugin-index-records-C06Xozeq.js"; import { a as supportsInstallConfigSingleTopLevelIncludeShape, i as selectInstallMutationWriteOptions, r as resolveInstallConfigMutationPreflights } from "./install-persistence-iNWG2xIJ.js"; import { t as resolvePluginInstallInvalidConfigPolicy } from "./plugin-install-config-policy-BKE_FN73.js"; import { n as listPersistedBundledPluginRecoveryLocations } from "./plugins-location-bridges-Sagv4A2-.js"; //#region src/cli/plugins-install-config.ts function resolveFullyBlockedConfigMutationReason(snapshot) { if (snapshot.pluginMutation.mode !== "blocked" || snapshot.hookMutation.mode !== "blocked") return null; if (snapshot.pluginMutation.reason === snapshot.hookMutation.reason) return snapshot.pluginMutation.reason; return `Config plugin and hook mutations are both blocked. ${snapshot.pluginMutation.reason} ${snapshot.hookMutation.reason}`; } function buildInvalidPluginInstallConfigError(message) { return Object.assign(new Error(message), { code: "INVALID_CONFIG" }); } function assertPluginConfigMutationAllowed(preflight) { if (preflight.mode === "blocked") throw buildInvalidPluginInstallConfigError(preflight.reason); } function supportsPluginRecoveryIncludeShape(parsed) { if (Object.hasOwn(parsed, "$include")) return false; return supportsInstallConfigSingleTopLevelIncludeShape(parsed.plugins); } function extractMissingPluginLoadPath(issue) { if (issue.path !== "plugins.load.paths") return null; const markerIndex = issue.message.indexOf("plugin path not found:"); if (markerIndex < 0) return null; return issue.message.slice(markerIndex + 22).trim() || null; } function isOwnedMissingPluginLoadPathIssue(issue, ownedLoadPaths, env = process.env) { const missingPath = extractMissingPluginLoadPath(issue); return missingPath !== null && ownedLoadPaths.has(resolveUserPath(missingPath, env)); } function isAllowedPluginRecoveryIssue(issue, request, ownedLoadPaths) { const pluginId = request.bundledPluginId?.trim(); if (!pluginId) return false; return issue.path === `channels.${pluginId}` && issue.message === `unknown channel id: ${pluginId}` || issue.path.startsWith("channels.") && issue.message.startsWith(`invalid config for plugin ${pluginId}:`) || isOwnedMissingPluginLoadPathIssue(issue, ownedLoadPaths) || issue.path === `plugins.entries.${pluginId}` && issue.message.includes("requires compiled runtime output") || issue.path === "tools.web.search.provider" && issue.message.includes(`plugin "${pluginId}"`); } function collectRequestedPluginInstallPaths(cfg, installRecords, request, env) { const pluginId = request.bundledPluginId?.trim(); if (!pluginId) return /* @__PURE__ */ new Set(); const paths = /* @__PURE__ */ new Set(); const record = installRecords[pluginId] ?? cfg.plugins?.installs?.[pluginId]; for (const value of [record?.sourcePath, record?.installPath]) if (typeof value === "string" && value.trim()) paths.add(resolveUserPath(value, env)); return paths; } async function collectRequestedPluginLocationBridgePaths(request, env) { const pluginId = request.bundledPluginId?.trim(); if (!pluginId) return /* @__PURE__ */ new Set(); const locations = await listPersistedBundledPluginRecoveryLocations({ env }); return new Set(locations.filter((location) => location.pluginId === pluginId).flatMap((location) => location.loadPaths.map((loadPath) => resolveUserPath(loadPath, env)))); } function removeOwnedMissingPluginLoadPaths(cfg, issues, ownedLoadPaths, env) { const missingPaths = /* @__PURE__ */ new Set(); for (const issue of issues) { const missingPath = extractMissingPluginLoadPath(issue); if (!missingPath) continue; const resolved = resolveUserPath(missingPath, env); if (ownedLoadPaths.has(resolved)) missingPaths.add(resolved); } const paths = cfg.plugins?.load?.paths; if (missingPaths.size === 0 || !Array.isArray(paths)) return cfg; const nextPaths = paths.filter((entry) => typeof entry !== "string" || !missingPaths.has(resolveUserPath(entry, env))); if (nextPaths.length === paths.length) return cfg; return { ...cfg, plugins: { ...cfg.plugins, load: { ...cfg.plugins?.load, paths: nextPaths } } }; } async function resolveRequestedPluginInstallPaths(cfg, issues, request, env) { if (!issues.some((issue) => extractMissingPluginLoadPath(issue) !== null)) return /* @__PURE__ */ new Set(); const ownedLoadPaths = collectRequestedPluginInstallPaths(cfg, await loadInstalledPluginIndexInstallRecords(), request, env); if (issues.some((issue) => extractMissingPluginLoadPath(issue) !== null && !isOwnedMissingPluginLoadPathIssue(issue, ownedLoadPaths, env))) for (const loadPath of await collectRequestedPluginLocationBridgePaths(request, env)) ownedLoadPaths.add(loadPath); return ownedLoadPaths; } async function loadConfigFromSnapshotForInstall(request, prepared) { const { snapshot, writeOptions } = prepared; const mutationWriteOptions = selectInstallMutationWriteOptions(writeOptions); if (resolvePluginInstallInvalidConfigPolicy(request) !== "allow-plugin-recovery") throw buildInvalidPluginInstallConfigError("Config invalid; run `openclaw doctor --fix` before installing plugins."); const parsed = snapshot.parsed ?? {}; if (!snapshot.exists || Object.keys(parsed).length === 0) throw buildInvalidPluginInstallConfigError("Config file could not be parsed; run `openclaw doctor` to repair it."); const ownedLoadPaths = await resolveRequestedPluginInstallPaths(snapshot.config, snapshot.issues, request, process.env); if (snapshot.legacyIssues.length > 0 || snapshot.issues.length === 0 || snapshot.issues.some((issue) => !isAllowedPluginRecoveryIssue(issue, request, ownedLoadPaths))) throw buildInvalidPluginInstallConfigError(`Config invalid outside the plugin recovery path for ${request.bundledPluginId ?? "the requested plugin"}; run \`openclaw doctor --fix\` before reinstalling it.`); if (!supportsPluginRecoveryIncludeShape(parsed)) throw buildInvalidPluginInstallConfigError("Config plugin recovery uses an unsupported $include shape; use a single-file top-level plugins include or run `openclaw doctor --fix` before reinstalling it."); const { hookMutation, pluginMutation } = resolveInstallConfigMutationPreflights({ parsed, snapshotPath: snapshot.path, writeOptions: mutationWriteOptions }); assertPluginConfigMutationAllowed(pluginMutation); return { config: removeOwnedMissingPluginLoadPaths(snapshot.config, snapshot.issues, ownedLoadPaths, process.env), baseHash: snapshot.hash, writeOptions: mutationWriteOptions, hookMutation, pluginMutation }; } /** Read and authorize install configuration only after mutation-free request preflight. */ async function loadConfigForInstall(request) { const prepared = await tracePluginLifecyclePhaseAsync("config read", () => readConfigFileSnapshotForWrite(), { command: "install" }); const { snapshot, writeOptions } = prepared; const mutationWriteOptions = selectInstallMutationWriteOptions(writeOptions); if (!snapshot.valid) return await loadConfigFromSnapshotForInstall(request, prepared); const parsed = snapshot.parsed ?? {}; const { hookMutation, pluginMutation } = resolveInstallConfigMutationPreflights({ parsed, snapshotPath: snapshot.path, writeOptions: mutationWriteOptions }); if (request.installKind === "plugin") assertPluginConfigMutationAllowed(pluginMutation); return { config: snapshot.sourceConfig, baseHash: snapshot.hash, writeOptions: mutationWriteOptions, hookMutation, pluginMutation }; } //#endregion export { resolveFullyBlockedConfigMutationReason as n, loadConfigForInstall as t };