UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

230 lines (229 loc) • 10.9 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs"; import { r as resolveOpenClawPackageRootSync } from "./openclaw-root-CBoxhfke.mjs"; import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import { d as normalizeStringEntries, y as uniqueStrings } from "./string-normalization-CECOqf64.mjs"; import "./utils-DgZn2dLu.mjs"; import { a as isPrereleaseSemverVersion, o as parseRegistryNpmSpec } from "./npm-registry-spec-Dw2vyAJw.mjs"; import { p as readPluginCacheJsonFile } from "./package-manifest-CEbdS_C4.mjs"; import { t as MANIFEST_KEY } from "./legacy-names-Cq8ruaO5.mjs"; import { s as listOfficialExternalChannelCatalogEntries } from "./official-external-plugin-catalog-Dcv926GC.mjs"; import { t as describePluginInstallSource } from "./install-source-info-fU5T9zEW.mjs"; import { t as listChannelCatalogEntries } from "./channel-catalog-registry-E6IKnWvU.mjs"; import { t as buildManifestChannelMeta } from "./channel-meta-BgTks57p.mjs"; import { n as resolveExternalPluginCatalogPaths, t as parseExternalPluginCatalogEntries } from "./plugin-catalog-source-AYVlnf_X.mjs"; import path from "node:path"; //#region src/channels/plugins/catalog.ts const ORIGIN_PRIORITY = { config: 0, workspace: 1, global: 2, bundled: 3 }; function shouldExcludeCatalogEntry(options, pluginId, origin) { const normalizedPluginId = normalizeOptionalString(pluginId); return options.excludeWorkspace === true && origin === "workspace" || origin !== void 0 && (options.excludeOrigins?.includes(origin) ?? false) || Boolean(normalizedPluginId && options.excludePluginRefs?.some((entry) => entry.pluginId === normalizedPluginId && (entry.origin === void 0 || entry.origin === origin))); } const EXTERNAL_CATALOG_PRIORITY = ORIGIN_PRIORITY.bundled + 1; const FALLBACK_CATALOG_PRIORITY = EXTERNAL_CATALOG_PRIORITY + 1; const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = path.join("dist", "channel-catalog.json"); function loadCatalogEntriesFromPaths(paths) { const entries = []; for (const resolvedPath of paths) { const payload = readPluginCacheJsonFile(resolvedPath); if (payload.ok) entries.push(...parseExternalPluginCatalogEntries(payload.value)); } return entries; } function resolveOfficialCatalogPaths(options) { if (options.officialCatalogPaths && options.officialCatalogPaths.length > 0) return normalizeStringEntries(options.officialCatalogPaths); const candidates = uniqueStrings([resolveOpenClawPackageRootSync({ cwd: process.cwd() }), resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url })].filter((entry) => Boolean(entry))).map((packageRoot) => path.join(packageRoot, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH)); if (process.execPath) { const execDir = path.dirname(process.execPath); candidates.push(path.join(execDir, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH)); candidates.push(path.join(execDir, "channel-catalog.json")); } return uniqueStrings(candidates); } function resolveInstallInfo(params) { const clawhubSpec = normalizeOptionalString(params.install?.clawhubSpec); let npmSpec = normalizeOptionalString(params.install?.npmSpec) ?? (clawhubSpec ? void 0 : normalizeOptionalString(params.packageName)); const packageVersion = normalizeOptionalString(params.packageVersion); const parsedNpmSpec = npmSpec ? parseRegistryNpmSpec(npmSpec) : null; const expectedPackageName = normalizeOptionalString(params.packageName); const parsedPackageName = expectedPackageName ? parseRegistryNpmSpec(expectedPackageName) : null; if (npmSpec && packageVersion && isPrereleaseSemverVersion(packageVersion) && parsedNpmSpec?.selectorKind === "none" && (!parsedPackageName || parsedNpmSpec.name === parsedPackageName.name)) npmSpec = `${parsedNpmSpec.name}@${packageVersion}`; if (!clawhubSpec && !npmSpec) return null; let localPath = normalizeOptionalString(params.install?.localPath); if (!localPath && params.workspaceDir && params.packageDir) localPath = path.relative(params.workspaceDir, params.packageDir) || void 0; const defaultChoice = params.install?.defaultChoice === "local" && localPath ? "local" : npmSpec ? "npm" : "clawhub"; const install = { ...localPath ? { localPath } : {}, defaultChoice, ...params.install?.minHostVersion ? { minHostVersion: params.install.minHostVersion } : {}, ...params.install?.expectedIntegrity ? { expectedIntegrity: params.install.expectedIntegrity } : {}, ...params.install?.allowInvalidConfigRecovery === true ? { allowInvalidConfigRecovery: true } : {} }; if (clawhubSpec) return { clawhubSpec, ...npmSpec ? { npmSpec } : {}, ...install }; if (!npmSpec) return null; return { npmSpec, ...install }; } function buildCatalogEntryFromManifest(params) { const channel = params.channel; const id = channel?.id?.trim(); const label = channel?.label?.trim(); if (!channel || !id || !label) return null; const install = resolveInstallInfo(params); if (!install) return null; const pluginId = normalizeOptionalString(params.pluginId); const systemImage = channel.systemImage?.trim(); return { id, ...pluginId ? { pluginId } : {}, ...params.origin ? { origin: params.origin } : {}, ...params.trustedSourceLinkedOfficialInstall ? { trustedSourceLinkedOfficialInstall: true } : {}, channel, meta: buildManifestChannelMeta({ id, channel, label, selectionLabel: channel.selectionLabel?.trim() || label, docsPath: channel.docsPath?.trim() || `/channels/${id}`, docsLabel: normalizeOptionalString(channel.docsLabel), blurb: channel.blurb?.trim() || "", detailLabel: channel.detailLabel?.trim(), ...systemImage ? { systemImage } : {}, arrayFieldMode: "defined" }), install, installSource: describePluginInstallSource(install, { expectedPackageName: params.packageName }) }; } function buildExternalCatalogEntry(entry, trustedSourceLinkedOfficialInstall = false) { const manifest = entry[MANIFEST_KEY]; return buildCatalogEntryFromManifest({ pluginId: manifest?.plugin?.id, packageName: entry.name, packageVersion: entry.version, trustedSourceLinkedOfficialInstall, channel: manifest?.channel, install: manifest?.install }); } function resolveOfficialCatalogDocsPath(entry, officialEntries, trustedOfficialPackageName) { let official; if (entry.origin === "bundled" || entry.origin === void 0 && entry.trustedSourceLinkedOfficialInstall) official = entry; else if ((entry.origin === "global" || entry.origin === "config") && trustedOfficialPackageName) official = officialEntries.find((candidate) => candidate.id === entry.id && (candidate.pluginId ?? candidate.id) === entry.pluginId && [ candidate.installSource?.npm?.expectedPackageName, candidate.installSource?.npm?.packageName, candidate.installSource?.clawhub?.packageName ].includes(trustedOfficialPackageName)); const value = official?.meta.docsPath.trim(); if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return; const origin = "https://docs.openclaw.ai"; const url = new URL(value, origin); return url.origin === origin && !url.pathname.startsWith("//") ? `${url.pathname}${url.search}${url.hash}` : void 0; } function buildChannelUiCatalog(plugins) { const entries = plugins.map((plugin) => { const detailLabel = plugin.meta.detailLabel ?? plugin.meta.selectionLabel ?? plugin.meta.label; return { id: plugin.id, label: plugin.meta.label, detailLabel, ...plugin.meta.systemImage ? { systemImage: plugin.meta.systemImage } : {} }; }); const order = entries.map((entry) => entry.id); const labels = {}; const detailLabels = {}; const systemImages = {}; const byId = {}; for (const entry of entries) { labels[entry.id] = entry.label; detailLabels[entry.id] = entry.detailLabel; if (entry.systemImage) systemImages[entry.id] = entry.systemImage; byId[entry.id] = entry; } return { entries, order, labels, detailLabels, systemImages, byId }; } /** * Raw catalog primitive. This may include untrusted workspace entries and * workspace shadows. Security-sensitive or execution-facing callers should * prefer `listTrustedChannelPluginCatalogEntries`; use this primitive only when * the caller immediately applies trust filtering or explicitly excludes * workspace entries. * * @internal */ function listRawChannelPluginCatalogEntries(options = {}) { const manifestEntries = listChannelCatalogEntries({ workspaceDir: options.workspaceDir, env: options.env, extraPaths: options.extraPaths, installRecords: options.installRecords, discovery: options.discovery }); const officialFileEntries = loadCatalogEntriesFromPaths(resolveOfficialCatalogPaths(options)); const officialEntries = [...listOfficialExternalChannelCatalogEntries(), ...officialFileEntries].map((entry) => buildExternalCatalogEntry(entry, true)).filter((entry) => entry !== null); const resolved = /* @__PURE__ */ new Map(); const rememberCatalogEntry = (entry, priority, trustedOfficialPackageName) => { const existing = resolved.get(entry.id); if (!existing || priority < existing.priority) { const officialDocsPath = resolveOfficialCatalogDocsPath(entry, officialEntries, trustedOfficialPackageName); resolved.set(entry.id, { entry: officialDocsPath ? { ...entry, officialDocsPath } : entry, priority }); } }; for (const candidate of manifestEntries) { if (shouldExcludeCatalogEntry(options, candidate.pluginId, candidate.origin)) continue; const entry = buildCatalogEntryFromManifest({ pluginId: candidate.pluginId, packageName: candidate.packageName, packageDir: candidate.rootDir, origin: candidate.origin, workspaceDir: candidate.workspaceDir ?? options.workspaceDir, channel: candidate.channel, install: candidate.install }); if (!entry) continue; rememberCatalogEntry(entry, ORIGIN_PRIORITY[candidate.origin] ?? 99, candidate.trustedOfficialInstall ? candidate.packageName : void 0); } for (const entry of officialEntries) rememberCatalogEntry(entry, FALLBACK_CATALOG_PRIORITY); const externalCatalogPaths = resolveExternalPluginCatalogPaths(options).map((rawPath) => resolveUserPath(rawPath, options.env ?? process.env)); for (const candidate of loadCatalogEntriesFromPaths(externalCatalogPaths)) { const entry = buildExternalCatalogEntry(candidate); if (entry) rememberCatalogEntry(entry, EXTERNAL_CATALOG_PRIORITY); } return Array.from(resolved.values()).map(({ entry }) => entry).toSorted((a, b) => { const orderA = a.meta.order ?? 999; const orderB = b.meta.order ?? 999; if (orderA !== orderB) return orderA - orderB; return a.meta.label.localeCompare(b.meta.label); }); } function getChannelPluginCatalogEntry(id, options = {}) { const trimmed = id.trim(); if (!trimmed) return; return listRawChannelPluginCatalogEntries(options).find((entry) => entry.id === trimmed); } //#endregion export { getChannelPluginCatalogEntry as n, listRawChannelPluginCatalogEntries as r, buildChannelUiCatalog as t };