UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

700 lines (699 loc) 31.8 kB
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js"; import { t as exitCliAfterOutput } from "./one-shot-exit-BkFPnjbM.js"; import { n as getRuntimeConfig, s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js"; import { p as shortenHomeInString } from "./utils-P__uGsPB.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import { n as emitDiagnosticsTimelineEvent } from "./diagnostics-timeline-CRX1LXmg.js"; import { c as tracePluginLifecyclePhaseAsync } from "./discovery-D_VDsZuY.js"; import { F as resolvePluginInstallSources } from "./official-external-plugin-catalog-Dzu7dwBN.js"; import { n as formatConfigIssueLines } from "./issue-format-CQvxWNW8.js"; import { n as assertConfigWriteAllowedInCurrentMode } from "./nix-mode-write-guard-uwcnAyQN.js"; import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js"; import "./config-Cs0XXL3x.js"; import { t as formatDocsLink } from "./links-ClIwBcy4.js"; import { r as theme } from "./theme-vjDs9tao.js"; import { a as formatMissingPluginMessage } from "./error-format-Cae7EwnT.js"; import { r as formatCliJsonFailure, t as ExpectedCliError } from "./failure-output-Dc5ce9_F.js"; import { t as withPluginLifecycleLease } from "./plugin-lifecycle-lease-BtAFH872.js"; import { n as resolvePluginCapabilityConsentCliOptions } from "./plugin-capability-consent-HWTOHYVK.js"; import { a as resolveConfiguredRuntimePluginInstallCandidate, i as collectConfiguredRuntimePluginIds } from "./configured-runtime-plugin-installs-DWgjbBZ4.js"; //#region src/cli/plugins-cli.runtime.ts function createModuleLoader(load) { let promise; return () => promise ??= load(); } const loadPluginsConfigState = createModuleLoader(() => import("./config-state-Cs_nb6C9.js")); const loadPluginsStatus = createModuleLoader(() => import("./status-CrqpxX2s.js")); const loadPluginSlotSelection = createModuleLoader(() => import("./slot-selection-CFQ-_w1_.js")); const loadPluginsCommandHelpers = createModuleLoader(() => import("./plugins-command-helpers-LeelLCXJ.js")); const loadPluginsRegistryRefresh = createModuleLoader(() => import("./registry-refresh-x5mIxBEc.js")); function countEnabledPlugins(plugins) { return plugins.filter((plugin) => plugin.enabled).length; } function formatRegistryState(state) { return state === "fresh" ? theme.success(state) : theme.warn(state); } function reportMissingPlugin(id) { defaultRuntime.error(formatMissingPluginMessage({ id, includeSearch: true })); return defaultRuntime.exit(1); } 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) { return resolvePluginInstallSources({ npmSpec: params.npmSpec, clawhubSpec: params.clawhubSpec })[0]?.spec ?? 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, opts = {}) { assertConfigWriteAllowedInCurrentMode(); return await withPluginLifecycleLease({}, async () => await runPluginsEnableCommandUnlocked(idInput, opts)); } async function runPluginsEnableCommandUnlocked(idInput, opts) { let id = idInput; assertConfigWriteAllowedInCurrentMode(); const { enableExplicitlySelectedPluginInConfig } = await import("./enable-BVl2BAfY.js"); const { normalizePluginId } = await loadPluginsConfigState(); const { buildPluginRegistrySnapshotReport } = await loadPluginsStatus(); const snapshot = await readConfigFileSnapshot(); const cfg = snapshot.sourceConfig ?? snapshot.config; const report = buildPluginRegistrySnapshotReport({ config: cfg }); id = normalizePluginId(id); const plugin = report.plugins.find((entry) => entry.id === id); if (!plugin) return reportMissingPlugin(id); const enableResult = enableExplicitlySelectedPluginInConfig(cfg, id, { updateChannelConfig: false }); if (!enableResult.enabled) { defaultRuntime.error(`Plugin "${id}" could not be enabled (${enableResult.reason ?? "unknown reason"}).`); return defaultRuntime.exit(1); } if (!plugin.enabled || opts.acceptCapabilities) { const { resolvePluginCapabilityConsent } = await import("./capability-consent-0zWUyIwq.js"); const { ManagedPluginLifecycleError } = await import("./management-lifecycle-error-B21174pO.js"); const consent = resolvePluginCapabilityConsentCliOptions({ acceptCapabilities: opts.acceptCapabilities, action: "enable" }); try { await resolvePluginCapabilityConsent({ config: cfg, pluginId: id, ...consent }); } catch (error) { if (!(error instanceof ManagedPluginLifecycleError) || !error.capabilityConsent) throw error; defaultRuntime.error(error.message); return defaultRuntime.exit(1); } } const { applySlotSelectionForPlugin } = await loadPluginSlotSelection(); const { logSlotWarnings } = await loadPluginsCommandHelpers(); const { refreshPluginRegistryAfterConfigMutation } = await loadPluginsRegistryRefresh(); let next = enableResult.config; const slotResult = applySlotSelectionForPlugin(next, id); next = slotResult.config; await replaceConfigFile({ nextConfig: next, ...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {}, writeOptions: { explicitSetPaths: [[ "plugins", "entries", enableResult.pluginId ]] } }); await refreshPluginRegistryAfterConfigMutation({ config: next, reason: "policy-changed", invalidateRuntimeCache: false, policyPluginIds: [enableResult.pluginId], logger: { warn: (message) => defaultRuntime.log(theme.warn(message)) } }); logSlotWarnings(slotResult.warnings); defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`); } /** Disable a plugin in config and refresh the registry snapshot for the changed policy. */ async function runPluginsDisableCommand(idInput) { assertConfigWriteAllowedInCurrentMode(); return await withPluginLifecycleLease({}, async () => await runPluginsDisableCommandUnlocked(idInput)); } async function runPluginsDisableCommandUnlocked(idInput) { let id = idInput; assertConfigWriteAllowedInCurrentMode(); const { normalizePluginId } = await loadPluginsConfigState(); const { buildPluginRegistrySnapshotReport } = await loadPluginsStatus(); const { setPluginEnabledInConfig } = await import("./plugins-config-BpdMFLm_.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) => plugin.id === id)) return reportMissingPlugin(id); const next = setPluginEnabledInConfig(cfg, id, false, { updateChannelConfig: false }); await replaceConfigFile({ nextConfig: next, ...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {}, writeOptions: { explicitSetPaths: [[ "plugins", "entries", id ]] } }); 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-CEmIHzqR.js"); await runPluginInstallCommand({ raw, opts, allowInstallPolicyWarningPrompt: true, invalidateRuntimeCache: false }); }, { command: "install" }); } /** Inspect or refresh the persisted plugin registry index. */ async function runPluginsRegistryCommand(opts) { const { inspectPluginRegistry } = await import("./plugin-registry-Wg5DnZIf.js"); const formatDifferences = (differences) => { const formatSource = (source) => source ? sanitizeTerminalText(shortenHomeInString(source)) : "missing"; return differences.map((difference) => `${sanitizeTerminalText(difference.pluginId)}: persisted ${formatSource(difference.persistedSource)}; derived ${formatSource(difference.derivedSource)}`); }; if (opts.refresh) { const { refreshPluginRegistry } = await import("./plugin-registry-refresh-DMhVcmTh.js"); return await withPluginLifecycleLease({}, async () => { const config = getRuntimeConfig(); const index = await refreshPluginRegistry({ config, reason: "manual" }); const inspection = await inspectPluginRegistry({ config }); if (inspection.state !== "fresh") { const message = [ "Plugin registry refresh could not verify the persisted replacement.", ...formatDifferences(inspection.differences).map((difference) => `- ${difference}`), "Stop plugin package changes, then run `openclaw plugins registry --refresh` again." ].join("\n"); if (opts.json) { defaultRuntime.writeJson({ ...formatCliJsonFailure(message), refreshed: false, state: inspection.state, refreshReasons: inspection.refreshReasons, differences: inspection.differences }); exitCliAfterOutput(defaultRuntime, 1); } throw new Error(message); } if (opts.json) { defaultRuntime.writeJson({ refreshed: true, state: inspection.state, refreshReasons: inspection.refreshReasons, differences: inspection.differences, registry: index }); return; } const total = index.plugins.length; const enabled = countEnabledPlugins(index.plugins); defaultRuntime.log(`Plugin registry refreshed: ${enabled}/${total} enabled plugins indexed.`); }); } const inspection = await inspectPluginRegistry({ config: getRuntimeConfig() }); if (opts.json) { defaultRuntime.writeJson({ state: inspection.state, refreshReasons: inspection.refreshReasons, differences: inspection.differences, 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(...formatDifferences(inspection.differences).map((difference) => `- ${difference}`)); 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(opts = {}) { const { buildPluginCompatibilityNotices, buildPluginDiagnosticsReport, formatPluginCompatibilityNotice } = await loadPluginsStatus(); const { collectStalePluginConfigWarnings, isStalePluginAutoRepairBlocked, scanStalePluginConfig } = await import("./stale-plugin-config-DPFXm3GS.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((entry) => !isConfigSelectedShadowDiagnostic(entry)); const shadowed = report.diagnostics.filter((entry) => isErroredConfigSelectedShadowDiagnostic({ entry, plugins: report.plugins })); const compatibility = buildPluginCompatibilityNotices({ report }); const pluginConfigWarnings = /* @__PURE__ */ new Set([ ...formatConfigIssueLines((configSnapshot?.warnings ?? []).filter(({ path }) => path === "plugins" || path.startsWith("plugins."))), ...collectStalePluginConfigWarnings({ hits: scanStalePluginConfig(sourceCfg, process.env), doctorFixCommand: "openclaw doctor --fix", autoRepairBlocked: isStalePluginAutoRepairBlocked(sourceCfg, process.env) }), ...collectConfiguredRuntimePluginWarnings({ cfg: sourceCfg, plugins: report.plugins }) ]); const hasInstallTreeIssues = [ errors, diags, shadowed ].some(({ length }) => length > 0) || compatibility.some(({ severity }) => severity === "warn"); const doctorOk = !hasInstallTreeIssues && pluginConfigWarnings.size === 0; process.exitCode = doctorOk ? 0 : 1; if (opts.json) { defaultRuntime.writeJson({ ok: doctorOk, pluginErrors: errors.map((entry) => ({ id: entry.id, ...entry.failurePhase ? { failurePhase: entry.failurePhase } : {}, error: shortenHomeInString(entry.error ?? "failed to load"), source: shortenHomeInString(entry.source) })), diagnostics: diags.map((entry) => ({ level: entry.level, ...entry.pluginId ? { pluginId: entry.pluginId } : {}, message: shortenHomeInString(entry.message), ...entry.source ? { source: shortenHomeInString(entry.source) } : {} })), sourceShadowing: shadowed.map((entry) => { const active = report.plugins.find((plugin) => plugin.id === entry.pluginId); return { ...entry.pluginId ? { pluginId: entry.pluginId } : {}, message: shortenHomeInString(entry.message), ...active ? { active: { source: shortenHomeInString(active.source), origin: active.origin, status: active.status, ...active.error ? { error: shortenHomeInString(active.error) } : {} } } : {}, ...entry.source ? { shadowedSource: shortenHomeInString(entry.source) } : {}, repair: [ `openclaw plugins inspect ${entry.pluginId ?? "<plugin-id>"}`, "edit or remove the config-selected plugin source", "openclaw plugins registry --refresh", "openclaw gateway restart --force" ] }; }), compatibility: compatibility.map((notice) => ({ ...notice, message: shortenHomeInString(notice.message) })), configurationWarnings: Array.from(pluginConfigWarnings, shortenHomeInString) }); return; } const healthyMessage = "Plugin discovery, module loading, compatibility, and configuration checks passed. Run \"openclaw health\" to check the running Gateway, including runtime quarantines and fallbacks."; if (!hasInstallTreeIssues && pluginConfigWarnings.size === 0 && compatibility.length === 0) { defaultRuntime.log(healthyMessage); 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.size > 0) { if (lines.length > 0) lines.push(""); lines.push(theme.warn("Plugin configuration:"), ...pluginConfigWarnings); } if (!hasInstallTreeIssues) { const summary = pluginConfigWarnings.size ? "No plugin install-tree issues detected; configuration warnings remain." : healthyMessage; lines.push("", summary); } const docs = formatDocsLink("/plugin", "docs.openclaw.ai/plugin"); lines.push(""); lines.push(`${theme.muted("Docs:")} ${docs}`); defaultRuntime.log(lines.join("\n")); } function classifyMarketplaceFeedFallback(error) { const text = error?.toLowerCase(); if (!text) return; return [ [/offline mode/u, "offline"], [/checksum mismatch/u, "checksum_mismatch"], [/schema/u, "schema"], [/http\s+304/u, "not_modified"], [/http\s+\d{3}/u, "http_error"], [/timed out|timeout/u, "timeout"] ].find(([pattern]) => pattern.test(text))?.[1] ?? "error"; } function emitMarketplaceFeedTelemetry(params) { const attributes = { command: params.command, entries: params.entryCount ?? params.payload.entries, source: params.payload.source }; if (params.opts.feedProfile?.trim()) attributes.feedProfileProvided = true; if (params.opts.feedUrl?.trim()) attributes.feedUrlOverride = true; if (params.opts.offline === true) attributes.offline = true; if (params.opts.expectedSha256?.trim()) attributes.expectedSha256Provided = true; if (params.payload.feed) { attributes.feedIdPresent = true; attributes.feedSequence = params.payload.feed.sequence; } if (params.payload.metadata) { attributes.httpStatus = params.payload.metadata.status; if (params.payload.metadata.checksum) attributes.payloadChecksumPresent = true; attributes.hasEtag = Boolean(params.payload.metadata.etag); attributes.hasLastModified = Boolean(params.payload.metadata.lastModified); } if (params.payload.snapshot) attributes.snapshotUsed = true; if (params.payload.trust) { attributes.feedTrustVerified = true; attributes.feedTrustMode = params.payload.trust.mode; attributes.feedTrustSignatureCount = params.payload.trust.signatureCount; attributes.feedTrustThreshold = params.payload.trust.threshold; } const fallbackCategory = classifyMarketplaceFeedFallback(params.payload.error); if (fallbackCategory) attributes.fallbackCategory = fallbackCategory; if (params.failedPinnedRefresh === true) attributes.pinnedRefreshFailed = true; emitDiagnosticsTimelineEvent({ type: "mark", name: `plugins.marketplace.feed.${params.command}`, phase: "plugin-marketplace", attributes }, { config: params.config }); } function buildMarketplaceRefreshPayload(result) { const payload = { source: result.source, entries: result.entries.length, ...result.metadata ? { metadata: result.metadata } : {} }; if (result.source === "hosted" || result.source === "hosted-snapshot") { payload.feed = { id: result.feed.id, generatedAt: result.feed.generatedAt, sequence: result.feed.sequence }; if (result.trust) payload.trust = { mode: result.trust.mode, signedBy: result.trust.signedBy, signatureCount: result.trust.signatureCount, threshold: result.trust.threshold, verifiedAt: result.trust.verifiedAt }; } if (result.source === "hosted-snapshot") { payload.snapshot = { savedAt: result.snapshot.savedAt }; payload.error = result.error; } if (result.source === "bundled-fallback") payload.error = result.error; return payload; } function redactMarketplaceFeedUrl(value) { try { const url = new URL(value); url.username = ""; url.password = ""; url.search = ""; url.hash = ""; return url.href; } catch { return value; } } function redactMarketplaceOutputText(value, rawUrls) { let redacted = value; for (const rawUrl of rawUrls) { if (!rawUrl) continue; redacted = redacted.replaceAll(rawUrl, () => redactMarketplaceFeedUrl(rawUrl)); } return redacted; } function sanitizeMarketplaceRefreshPayload(payload, params) { const rawMetadataUrl = payload.metadata?.url; const sanitized = { ...payload, ...payload.metadata ? { metadata: { ...payload.metadata, url: redactMarketplaceFeedUrl(payload.metadata.url) } } : {} }; if (payload.error) sanitized.error = redactMarketplaceOutputText(payload.error, [params?.feedUrl, rawMetadataUrl]); return sanitized; } function formatMarketplaceEntryInstall(entry) { return resolvePluginInstallSources({ npmSpec: entry.install?.npmSpec, clawhubSpec: entry.install?.clawhubSpec })[0]?.spec ?? entry.install?.localPath; } function formatMarketplaceEntryLine(entry) { const id = entry.id ?? entry.name ?? entry.label; const install = formatMarketplaceEntryInstall(entry); const suffix = install ? " " + theme.muted(install) : ""; const label = entry.label !== id ? " " + theme.muted(entry.label) : ""; return theme.command(id) + label + suffix; } function formatMarketplaceRefreshSource(source) { if (source === "hosted") return theme.success("hosted"); if (source === "hosted-snapshot") return theme.warn("hosted snapshot"); return theme.warn("bundled fallback"); } function formatMarketplaceFeedTrust(trust) { return `${trust.mode} by ${trust.signedBy} (${trust.signatureCount}/${trust.threshold}) verified ${trust.verifiedAt}`; } function formatMarketplaceFeedLines(payload, options = {}) { const lines = [`${theme.muted("Source:")} ${formatMarketplaceRefreshSource(payload.source)}`, `${theme.muted("Entries:")} ${payload.entries}`]; if (payload.feed) lines.push(`${theme.muted("Feed:")} ${payload.feed.id} ${theme.muted(`sequence ${payload.feed.sequence}`)}`); if (payload.metadata?.url) lines.push(`${theme.muted("URL:")} ${payload.metadata.url}`); if (options.includeChecksum && payload.metadata?.checksum) lines.push(`${theme.muted("SHA-256:")} ${payload.metadata.checksum}`); if (payload.snapshot?.savedAt) lines.push(`${theme.muted("Snapshot:")} ${payload.snapshot.savedAt}`); if (payload.trust) lines.push(`${theme.muted("Trust:")} ${formatMarketplaceFeedTrust(payload.trust)}`); if (payload.error) lines.push(`${theme.muted("Fallback reason:")} ${payload.error}`); return lines; } function shouldFailPinnedMarketplaceRefresh(params) { return Boolean(params.expectedSha256?.trim()) && params.source !== "hosted"; } function normalizeMarketplaceExpectedSha256(value) { const trimmed = value?.trim(); if (!trimmed) return; if (/^[0-9a-f]{64}$/iu.test(trimmed)) return `sha256:${trimmed.toLowerCase()}`; const prefixed = /^sha256:([0-9a-f]{64})$/iu.exec(trimmed); if (prefixed?.[1]) return `sha256:${prefixed[1].toLowerCase()}`; return trimmed; } function formatPinnedMarketplaceRefreshFailure(payload) { return `Pinned marketplace feed refresh did not accept a fresh hosted payload (source: ${payload.source}).`; } const MARKETPLACE_GATEWAY_RESTART_GUIDANCE = "The running Gateway could not refresh its marketplace catalog. Run \"openclaw gateway restart\" to apply the current catalog state."; /** List entries from the configured OpenClaw marketplace feed. */ async function runPluginMarketplaceEntriesCommand(opts) { const catalog = await import("./official-external-plugin-catalog-C2XmBZVj.js"); const cfg = getRuntimeConfig(); const result = await catalog.loadConfiguredHostedOfficialExternalPluginCatalogEntries({ ...opts.feedProfile ? { feedProfile: opts.feedProfile } : {}, ...opts.feedUrl ? { feedUrl: opts.feedUrl } : {}, ...opts.offline ? { offline: true } : {} }); const summary = sanitizeMarketplaceRefreshPayload(buildMarketplaceRefreshPayload(result), { feedUrl: opts.feedUrl }); const entries = result.entries.map((entry) => { const id = catalog.resolveOfficialExternalPluginId(entry); const install = catalog.resolveOfficialExternalPluginInstall(entry) ?? void 0; const payload = { label: catalog.resolveOfficialExternalPluginLabel(entry) }; if (id) payload.id = id; if (entry.kind) payload.kind = entry.kind; if (entry.name) payload.name = entry.name; if (entry.version) payload.version = entry.version; if (install) payload.install = install; return payload; }); emitMarketplaceFeedTelemetry({ command: "entries", entryCount: entries.length, opts, config: cfg, payload: summary }); if (opts.json) { defaultRuntime.writeJson({ ...summary, entries, entryCount: entries.length }); return; } const lines = formatMarketplaceFeedLines(summary); if (entries.length > 0) { lines.push(""); lines.push(...entries.map(formatMarketplaceEntryLine)); } defaultRuntime.log(lines.join("\n")); } /** Refresh the configured OpenClaw marketplace feed snapshot. */ async function runPluginMarketplaceRefreshCommand(opts) { const { loadConfiguredHostedOfficialExternalPluginCatalogEntries } = await import("./official-external-plugin-catalog-C2XmBZVj.js"); const cfg = getRuntimeConfig(); const expectedSha256 = normalizeMarketplaceExpectedSha256(opts.expectedSha256); const result = await loadConfiguredHostedOfficialExternalPluginCatalogEntries({ ...opts.feedProfile ? { feedProfile: opts.feedProfile } : {}, ...opts.feedUrl ? { feedUrl: opts.feedUrl } : {}, ...expectedSha256 ? { expectedSha256 } : {}, requireSnapshotWrite: true }); const { clearManagedPluginOfficialCatalogCache } = await import("./management-catalog-7Xbf5KN7.js"); clearManagedPluginOfficialCatalogCache(); let gatewayRefreshed = true; if (result.source !== "bundled-fallback") { const { notifyGatewayPluginMetadataChanged } = await import("./plugins-update-gateway-signal-DP4wQQTP.js"); gatewayRefreshed = await notifyGatewayPluginMetadataChanged(cfg); } const payload = sanitizeMarketplaceRefreshPayload(buildMarketplaceRefreshPayload(result), { feedUrl: opts.feedUrl }); const failedPinnedRefresh = shouldFailPinnedMarketplaceRefresh({ expectedSha256, source: payload.source }); emitMarketplaceFeedTelemetry({ command: "refresh", failedPinnedRefresh, opts, config: cfg, payload }); if (opts.json) { defaultRuntime.writeJson(payload); if (!gatewayRefreshed) defaultRuntime.error(MARKETPLACE_GATEWAY_RESTART_GUIDANCE); if (failedPinnedRefresh) { defaultRuntime.error(formatPinnedMarketplaceRefreshFailure(payload)); return defaultRuntime.exit(1); } return; } const lines = formatMarketplaceFeedLines(payload, { includeChecksum: true }); if (!gatewayRefreshed) lines.push("", theme.warn(MARKETPLACE_GATEWAY_RESTART_GUIDANCE)); defaultRuntime.log(lines.join("\n")); if (failedPinnedRefresh) { defaultRuntime.error(formatPinnedMarketplaceRefreshFailure(payload)); return defaultRuntime.exit(1); } } /** List plugins from a configured marketplace manifest. */ async function runPluginMarketplaceListCommand(source, opts) { const { listMarketplacePlugins } = await import("./marketplace-DF0aISAR.js"); const { createPluginInstallLogger, quietPluginJsonLogger } = await loadPluginsCommandHelpers(); const result = await listMarketplacePlugins({ marketplace: source, logger: opts.json ? quietPluginJsonLogger : createPluginInstallLogger() }); if (!result.ok) { const message = result.error; throw new ExpectedCliError({ message, humanOutput: message, machineOutput: message }); } if (opts.json) return defaultRuntime.writeJson({ source: result.sourceLabel, name: result.manifest.name, version: result.manifest.version, plugins: result.manifest.plugins }); 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 { runPluginMarketplaceEntriesCommand, runPluginMarketplaceListCommand, runPluginMarketplaceRefreshCommand, runPluginsDisableCommand, runPluginsDoctorCommand, runPluginsEnableCommand, runPluginsInstallAction, runPluginsRegistryCommand };