UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

330 lines (329 loc) 16.1 kB
import { t as formatDocsLink } from "./links-CsLBrRff.js"; import { r as theme } from "./theme-vjDs9tao.js"; import { a as tracePluginLifecyclePhaseAsync } from "./discovery-BDSfxwT6.js"; import { h as shortenHomeInString } from "./utils-CCC-BEJH.js"; import { n as defaultRuntime } from "./runtime-B4lgFmsS.js"; import { i as getRuntimeConfig, u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js"; import { n as assertConfigWriteAllowedInCurrentMode } from "./nix-mode-write-guard-DMwPAosm.js"; import { i as replaceConfigFile } from "./config-C9RxTsn1.js"; import { a as formatMissingPluginMessage } from "./error-format-COoNML-C.js"; import { n as collectConfiguredRuntimePluginIds, r as resolveConfiguredRuntimePluginInstallCandidate } from "./configured-runtime-plugin-installs-CFV2V0q_.js"; //#region src/cli/plugins-cli.runtime.ts function createModuleLoader(load) { let promise; return () => promise ??= load(); } const loadPluginsConfigState = createModuleLoader(() => import("./config-state-Cuzf4qZq.js")); const loadPluginsStatus = createModuleLoader(() => import("./status-JfK93cyu.js")); const loadPluginsCommandHelpers = createModuleLoader(() => import("./plugins-command-helpers-BaQRBcXg.js")); const loadPluginsRegistryRefresh = createModuleLoader(() => import("./plugins-registry-refresh-CHxkaZjs.js")); function countEnabledPlugins(plugins) { return plugins.filter((plugin) => plugin.enabled).length; } function formatRegistryState(state) { if (state === "fresh") return theme.success(state); if (state === "stale") return theme.warn(state); return theme.warn(state); } function reportMissingPlugin(id) { defaultRuntime.error(formatMissingPluginMessage({ id, includeSearch: true })); return defaultRuntime.exit(1); } function matchesPluginId(plugin, id) { return plugin.id === id; } function isConfigSelectedShadowDiagnostic(entry) { return entry.level === "warn" && typeof entry.message === "string" && entry.message.includes("duplicate plugin id resolved by explicit config-selected plugin"); } function isErroredConfigSelectedShadowDiagnostic(params) { if (!params.entry.pluginId || !isConfigSelectedShadowDiagnostic(params.entry)) return false; return params.plugins.some((plugin) => plugin.id === params.entry.pluginId && plugin.origin === "config" && plugin.status === "error"); } function formatConfiguredRuntimePluginInstallSpec(params) { const clawhubSpec = params.clawhubSpec?.trim(); const npmSpec = params.npmSpec?.trim(); if (clawhubSpec && params.defaultChoice !== "npm") return clawhubSpec; return npmSpec ?? clawhubSpec ?? params.pluginId; } function pluginIdListIncludes(list, pluginId) { return Array.isArray(list) && list.some((entry) => entry.trim() === pluginId); } function formatBlockedRuntimePluginGuidance(params) { const pluginId = params.pluginId; const alternative = pluginId === "acpx" ? "disable ACP/acpx in acp config" : "change the runtime policy to \"openclaw\""; if (params.cfg.plugins?.enabled === false) return `Enable plugin loading and the "${pluginId}" plugin, or ${alternative}.`; if (pluginIdListIncludes(params.cfg.plugins?.deny, pluginId)) return `Remove "${pluginId}" from plugins.deny and enable the "${pluginId}" plugin, or ${alternative}.`; if (params.cfg.plugins?.entries?.[pluginId]?.enabled === false) return `Set plugins.entries.${pluginId}.enabled=true or remove that disabled entry, or ${alternative}.`; } function formatDisabledRuntimePluginGuidance(params) { const allow = params.cfg.plugins?.allow; const alternative = params.pluginId === "acpx" ? "disable ACP/acpx in acp config" : "change the runtime policy to \"openclaw\""; if (Array.isArray(allow) && allow.length > 0 && !allow.includes(params.pluginId)) return `Add "${params.pluginId}" to plugins.allow and enable the plugin, or ${alternative}.`; return `Enable the "${params.pluginId}" plugin, or ${alternative}.`; } function collectConfiguredRuntimePluginWarnings(params) { const enabledPluginIds = new Set(params.plugins.filter((plugin) => plugin.enabled !== false && plugin.status !== "disabled").map((plugin) => plugin.id)); return collectConfiguredRuntimePluginIds(params.cfg, { includeImplicitRuntimePreferences: false }).flatMap((runtimeId) => { const candidate = resolveConfiguredRuntimePluginInstallCandidate(runtimeId); if (!candidate || enabledPluginIds.has(runtimeId)) return []; const disabledPluginRecord = params.plugins.find((plugin) => plugin.id === runtimeId); const blockedGuidance = formatBlockedRuntimePluginGuidance({ cfg: params.cfg, pluginId: runtimeId }); if (blockedGuidance) return [`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is blocked by plugin configuration. ${blockedGuidance}`]; if (disabledPluginRecord) return [`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is disabled. ${formatDisabledRuntimePluginGuidance({ cfg: params.cfg, pluginId: runtimeId })}`]; const installSpec = formatConfiguredRuntimePluginInstallSpec(candidate); return [`- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but no enabled "${runtimeId}" plugin was found. Run "openclaw doctor --fix" to install ${installSpec}, or install it manually with "openclaw plugins install ${installSpec}".`]; }); } /** Enable a plugin in config and refresh the registry snapshot for the changed policy. */ async function runPluginsEnableCommand(idInput) { let id = idInput; assertConfigWriteAllowedInCurrentMode(); const { enableExplicitlySelectedPluginInConfig } = await import("./enable-Bgd4ved5.js"); const { normalizePluginId } = await loadPluginsConfigState(); const { buildPluginRegistrySnapshotReport } = await loadPluginsStatus(); const { applySlotSelectionForPlugin, logSlotWarnings } = await loadPluginsCommandHelpers(); const { refreshPluginRegistryAfterConfigMutation } = await loadPluginsRegistryRefresh(); const snapshot = await readConfigFileSnapshot(); const cfg = snapshot.sourceConfig ?? snapshot.config; const report = buildPluginRegistrySnapshotReport({ config: cfg }); id = normalizePluginId(id); if (!report.plugins.some((plugin) => matchesPluginId(plugin, id))) return reportMissingPlugin(id); const enableResult = enableExplicitlySelectedPluginInConfig(cfg, id, { updateChannelConfig: false }); let next = enableResult.config; const slotResult = applySlotSelectionForPlugin(next, id); next = slotResult.config; await replaceConfigFile({ nextConfig: next, ...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {} }); await refreshPluginRegistryAfterConfigMutation({ config: next, reason: "policy-changed", invalidateRuntimeCache: false, policyPluginIds: [enableResult.pluginId], logger: { warn: (message) => defaultRuntime.log(theme.warn(message)) } }); logSlotWarnings(slotResult.warnings); if (enableResult.enabled) { defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`); return; } defaultRuntime.log(theme.warn(`Plugin "${id}" could not be enabled (${enableResult.reason ?? "unknown reason"}).`)); } /** Disable a plugin in config and refresh the registry snapshot for the changed policy. */ async function runPluginsDisableCommand(idInput) { let id = idInput; assertConfigWriteAllowedInCurrentMode(); const { normalizePluginId } = await loadPluginsConfigState(); const { buildPluginRegistrySnapshotReport } = await loadPluginsStatus(); const { setPluginEnabledInConfig } = await import("./plugins-config-BCnatBXr.js"); const { refreshPluginRegistryAfterConfigMutation } = await loadPluginsRegistryRefresh(); const snapshot = await readConfigFileSnapshot(); const cfg = snapshot.sourceConfig ?? snapshot.config; const report = buildPluginRegistrySnapshotReport({ config: cfg }); id = normalizePluginId(id); if (!report.plugins.some((plugin) => matchesPluginId(plugin, id))) return reportMissingPlugin(id); const next = setPluginEnabledInConfig(cfg, id, false, { updateChannelConfig: false }); await replaceConfigFile({ nextConfig: next, ...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {} }); await refreshPluginRegistryAfterConfigMutation({ config: next, reason: "policy-changed", invalidateRuntimeCache: false, policyPluginIds: [id], logger: { warn: (message) => defaultRuntime.log(theme.warn(message)) } }); defaultRuntime.log(`Disabled plugin "${id}". Restart the gateway to apply.`); } async function runPluginsInstallAction(raw, opts) { await tracePluginLifecyclePhaseAsync("install command", async () => { const { runPluginInstallCommand } = await import("./plugins-install-command-4srI9WVU.js"); await runPluginInstallCommand({ raw, opts, invalidateRuntimeCache: false }); }, { command: "install" }); } /** Inspect or refresh the persisted plugin registry index. */ async function runPluginsRegistryCommand(opts) { const { inspectPluginRegistry, refreshPluginRegistry } = await import("./plugin-registry-BnD8Rx7H.js"); const cfg = getRuntimeConfig(); if (opts.refresh) { const index = await refreshPluginRegistry({ config: cfg, reason: "manual" }); if (opts.json) { defaultRuntime.writeJson({ refreshed: true, registry: index }); return; } const total = index.plugins.length; const enabled = countEnabledPlugins(index.plugins); defaultRuntime.log(`Plugin registry refreshed: ${enabled}/${total} enabled plugins indexed.`); return; } const inspection = await inspectPluginRegistry({ config: cfg }); if (opts.json) { defaultRuntime.writeJson({ state: inspection.state, refreshReasons: inspection.refreshReasons, persisted: inspection.persisted, current: inspection.current }); return; } const currentTotal = inspection.current.plugins.length; const currentEnabled = countEnabledPlugins(inspection.current.plugins); const persistedTotal = inspection.persisted?.plugins.length ?? 0; const persistedEnabled = inspection.persisted ? countEnabledPlugins(inspection.persisted.plugins) : 0; const lines = [ `${theme.muted("State:")} ${formatRegistryState(inspection.state)}`, `${theme.muted("Current:")} ${currentEnabled}/${currentTotal} enabled plugins`, `${theme.muted("Persisted:")} ${persistedEnabled}/${persistedTotal} enabled plugins` ]; if (inspection.refreshReasons.length > 0) { lines.push(`${theme.muted("Refresh reasons:")} ${inspection.refreshReasons.join(", ")}`); lines.push(`${theme.muted("Repair:")} ${theme.command("openclaw plugins registry --refresh")}`); } defaultRuntime.log(lines.join("\n")); } /** Print plugin install-tree, compatibility, and plugin-owned config diagnostics. */ async function runPluginsDoctorCommand() { const { buildPluginCompatibilityNotices, buildPluginDiagnosticsReport, formatPluginCompatibilityNotice } = await loadPluginsStatus(); const { collectStalePluginConfigWarnings, isStalePluginAutoRepairBlocked, scanStalePluginConfig } = await import("./stale-plugin-config-Bh2vlH0D.js"); const cfg = getRuntimeConfig(); const configSnapshot = await readConfigFileSnapshot().catch(() => null); const sourceCfg = configSnapshot?.sourceConfig ?? configSnapshot?.config ?? cfg; const report = buildPluginDiagnosticsReport({ config: cfg, effectiveOnly: true }); const errors = report.plugins.filter((p) => p.status === "error"); const diags = report.diagnostics.filter((d) => d.level === "error"); const shadowed = report.diagnostics.filter((entry) => isErroredConfigSelectedShadowDiagnostic({ entry, plugins: report.plugins })); const compatibility = buildPluginCompatibilityNotices({ report }); const stalePluginConfigWarnings = collectStalePluginConfigWarnings({ hits: scanStalePluginConfig(sourceCfg ?? cfg, process.env), doctorFixCommand: "openclaw doctor --fix", autoRepairBlocked: isStalePluginAutoRepairBlocked(sourceCfg ?? cfg, process.env) }); const configuredRuntimePluginWarnings = collectConfiguredRuntimePluginWarnings({ cfg: sourceCfg ?? cfg, env: process.env, plugins: report.plugins }); const hasInstallTreeIssues = errors.length > 0 || diags.length > 0 || shadowed.length > 0 || compatibility.length > 0; const pluginConfigWarnings = [...stalePluginConfigWarnings, ...configuredRuntimePluginWarnings]; if (!hasInstallTreeIssues && pluginConfigWarnings.length === 0) { defaultRuntime.log("No plugin issues detected."); return; } const lines = []; if (errors.length > 0) { lines.push(theme.error("Plugin errors:")); for (const entry of errors) { const phase = entry.failurePhase ? ` [${entry.failurePhase}]` : ""; lines.push(`- ${entry.id}${phase}: ${entry.error ?? "failed to load"} (${entry.source})`); } } if (diags.length > 0) { if (lines.length > 0) lines.push(""); lines.push(theme.warn("Diagnostics:")); for (const diag of diags) { const target = diag.pluginId ? `${diag.pluginId}: ` : ""; lines.push(`- ${target}${diag.message}`); } } if (shadowed.length > 0) { if (lines.length > 0) lines.push(""); lines.push(theme.warn("Plugin source shadowing:")); for (const diag of shadowed) { const active = report.plugins.find((plugin) => plugin.id === diag.pluginId); const target = diag.pluginId ? `${diag.pluginId}: ` : ""; lines.push(`- ${target}${diag.message}`); if (active) { lines.push(` active: ${shortenHomeInString(active.source)} (${active.origin})`); if (active.status === "error") lines.push(` active status: error${active.error ? `: ${active.error}` : ""}`); } if (diag.source) lines.push(` shadowed: ${shortenHomeInString(diag.source)}`); lines.push(" repair:"); lines.push(" openclaw plugins inspect " + (diag.pluginId ?? "<plugin-id>")); lines.push(" edit or remove the config-selected plugin source"); lines.push(" openclaw plugins registry --refresh"); lines.push(" openclaw gateway restart --force"); } } if (compatibility.length > 0) { if (lines.length > 0) lines.push(""); lines.push(theme.warn("Compatibility:")); for (const notice of compatibility) { const marker = notice.severity === "warn" ? theme.warn("warn") : theme.muted("info"); lines.push(`- ${formatPluginCompatibilityNotice(notice)} [${marker}]`); } } if (pluginConfigWarnings.length > 0) { if (lines.length > 0) lines.push(""); lines.push(theme.warn("Plugin configuration:")); lines.push(...pluginConfigWarnings); } if (!hasInstallTreeIssues && pluginConfigWarnings.length > 0) { if (lines.length > 0) lines.push(""); lines.push("No plugin install-tree issues detected; configuration warnings remain."); } const docs = formatDocsLink("/plugin", "docs.openclaw.ai/plugin"); lines.push(""); lines.push(`${theme.muted("Docs:")} ${docs}`); defaultRuntime.log(lines.join("\n")); } /** List plugins from a configured marketplace manifest. */ async function runPluginMarketplaceListCommand(source, opts) { const { listMarketplacePlugins } = await import("./marketplace-gXNAm0yn.js"); const { createPluginInstallLogger } = await loadPluginsCommandHelpers(); const result = await listMarketplacePlugins({ marketplace: source, logger: createPluginInstallLogger() }); if (!result.ok) { defaultRuntime.error(result.error); return defaultRuntime.exit(1); } if (opts.json) { defaultRuntime.writeJson({ source: result.sourceLabel, name: result.manifest.name, version: result.manifest.version, plugins: result.manifest.plugins }); return; } if (result.manifest.plugins.length === 0) { defaultRuntime.log(`No plugins found in marketplace ${result.sourceLabel}.`); return; } defaultRuntime.log(`${theme.heading("Marketplace")} ${theme.muted(result.manifest.name ?? result.sourceLabel)}`); for (const plugin of result.manifest.plugins) { const suffix = plugin.version ? theme.muted(` v${plugin.version}`) : ""; const desc = plugin.description ? ` - ${theme.muted(plugin.description)}` : ""; defaultRuntime.log(`${theme.command(plugin.name)}${suffix}${desc}`); } } //#endregion export { runPluginMarketplaceListCommand, runPluginsDisableCommand, runPluginsDoctorCommand, runPluginsEnableCommand, runPluginsInstallAction, runPluginsRegistryCommand };