openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
131 lines (130 loc) • 8.01 kB
JavaScript
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 };