UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

333 lines (332 loc) 12.5 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js"; import { d as pathExists } from "./fs-safe-B6pvPGnf.js"; import "./utils-P__uGsPB.js"; import { s as resolvePackageExtensionEntries } from "./manifest-ByRdkf9X.js"; import { l as normalizePluginsConfig, u as resolveEffectiveEnableState } from "./config-state-BkU1frVq.js"; import { a as detectBundleManifestFormat, o as loadBundleManifest } from "./bundle-manifest-C0Vv9-Hj.js"; import { r as validatePackageExtensionEntriesForInstall } from "./package-entry-resolution-DZYcOePc.js"; import { l as resolveTrustedSourceLinkedOfficialNpmSpec, s as resolveTrustedSourceLinkedOfficialClawHubSpec } from "./official-external-install-records-DClkub0e.js"; import { r as auditOpenClawPeerDependencyLink, s as resolveOpenClawHostDependency } from "./plugin-peer-link-60qm_mPq.js"; import { existsSync } from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; //#region src/plugins/payload-verification.ts const TRACKED_SOURCES = /* @__PURE__ */ new Set([ "npm", "clawhub", "git", "marketplace" ]); function isPayloadMissing(env, rawInstallPath) { const installPath = normalizeOptionalString(rawInstallPath); if (!installPath) return true; const resolved = resolveUserPath(installPath, env); const bundleFormat = detectBundleManifestFormat(resolved); return !existsSync(path.join(resolved, "package.json")) && (!bundleFormat || !loadBundleManifest({ rootDir: resolved, bundleFormat }).ok); } /** Finds tracked install records whose package payload is absent on disk. */ async function collectMissingPluginInstallPayloads(params) { const env = params.env ?? process.env; const normalizedPluginConfig = params.skipDisabledPlugins && params.config ? normalizePluginsConfig(params.config.plugins) : void 0; const missing = []; for (const [pluginId, record] of Object.entries(params.records).toSorted(([left], [right]) => left.localeCompare(right))) { if (!TRACKED_SOURCES.has(record.source)) continue; const officialNpmSpec = params.syncOfficialPluginInstalls ? resolveTrustedSourceLinkedOfficialNpmSpec({ pluginId, record }) : void 0; const officialClawHubSpec = params.syncOfficialPluginInstalls ? resolveTrustedSourceLinkedOfficialClawHubSpec({ pluginId, record }) : void 0; if (normalizedPluginConfig && params.config) { if (!resolveEffectiveEnableState({ id: pluginId, origin: "global", config: normalizedPluginConfig, rootConfig: params.config }).enabled && !officialNpmSpec && !officialClawHubSpec) continue; } const rawInstallPath = normalizeOptionalString(record.installPath); if (!rawInstallPath) { missing.push({ pluginId, reason: "missing-install-path" }); continue; } const installPath = resolveUserPath(rawInstallPath, env); if (!await pathExists(installPath)) { missing.push({ pluginId, installPath, reason: "missing-package-dir" }); continue; } const bundlePayload = resolveBundleInstallRecordPayload({ record, installPath }); if (bundlePayload.isBundlePayload) { if (await hasNativePackageInstallPayload(installPath)) continue; if (validateBundleInstallRecordPayload({ pluginId, installPath, record, bundleFormat: bundlePayload.bundleFormat })) missing.push({ pluginId, installPath, reason: "missing-package-json" }); continue; } if (isPayloadMissing(env, record.installPath)) missing.push({ pluginId, installPath, reason: "missing-package-json" }); } return missing; } /** * Verify that each tracked plugin install record on disk is structurally * loadable: code packages contain a parseable `package.json` and declared * package entry files, while bundle packages satisfy their bundle manifest * contract. * * IMPORTANT: this is intentionally a *static* check. We do NOT execute the * plugin's code, so post-update side effects (network calls, filesystem * writes, registry registration) cannot fire while the gateway is still * stopped. The goal is to catch obvious payload corruption — missing files, * unparseable manifests — before we hand control back to the restart path. */ async function runPluginPayloadSmokeCheck(params) { const checked = []; const failures = []; for (const [pluginId, record] of Object.entries(params.records).toSorted(([a], [b]) => a.localeCompare(b))) { if (!record || typeof record !== "object" || !TRACKED_SOURCES.has(record.source)) continue; const rawInstallPath = typeof record.installPath === "string" ? record.installPath.trim() : ""; if (!rawInstallPath) { checked.push(pluginId); failures.push({ pluginId, reason: "missing-install-path", detail: "Install path is missing from the plugin install record." }); continue; } const installPath = resolveUserPath(rawInstallPath, params.env); checked.push(pluginId); if (!(await safeStat(installPath))?.isDirectory()) { failures.push({ pluginId, installPath, reason: "missing-package-dir", detail: `Install dir is missing: ${installPath}` }); continue; } const bundlePayload = resolveBundleInstallRecordPayload({ record, installPath }); const packagePayload = await readPackagePayloadManifest(installPath); if (packagePayload.status === "present") { if (!bundlePayload.isBundlePayload || hasNativePackageMetadata(packagePayload.manifest)) { failures.push(...await validatePackagePayload({ pluginId, installPath, manifest: packagePayload.manifest, installSource: record.source, installSourceIsAuthoritative: params.installSourceProvenance !== "manifest-only" })); continue; } } else if (!bundlePayload.isBundlePayload) { failures.push(formatPackagePayloadReadFailure({ pluginId, installPath, packagePayload })); continue; } const bundleFailure = validateBundleInstallRecordPayload({ pluginId, installPath, record, bundleFormat: bundlePayload.bundleFormat }); if (bundleFailure) failures.push(bundleFailure); } return { checked, failures }; } /** Verifies the exact manifest records selected for this process. */ async function runPluginPayloadSmokeCheckForManifestRecords(params) { return await runPluginPayloadSmokeCheck({ records: Object.fromEntries(params.plugins.map((plugin) => [plugin.id, { source: plugin.format === "bundle" ? "marketplace" : "npm", installPath: plugin.rootDir, ...plugin.format === "bundle" ? { clawhubFamily: "bundle-plugin" } : {} }])), env: params.env, installSourceProvenance: "manifest-only" }); } async function readPackagePayloadManifest(installPath) { const packageJsonPath = path.join(installPath, "package.json"); if (!(await safeStat(packageJsonPath))?.isFile()) return { status: "missing" }; let packageJson; try { packageJson = await fs$1.readFile(packageJsonPath, "utf8"); } catch (err) { return { status: "unreadable", error: err instanceof Error ? err.message : String(err) }; } try { return { status: "present", manifest: JSON.parse(packageJson) }; } catch (err) { return { status: "invalid", error: err instanceof Error ? err.message : String(err) }; } } function formatPackagePayloadReadFailure(params) { if (params.packagePayload.status === "unreadable") { const packageJsonPath = path.join(params.installPath, "package.json"); return { pluginId: params.pluginId, installPath: params.installPath, reason: "unreadable-package-json", detail: `Could not read package.json at ${packageJsonPath}: ${params.packagePayload.error}` }; } if (params.packagePayload.status === "invalid") return { pluginId: params.pluginId, installPath: params.installPath, reason: "invalid-package-json", detail: `Could not parse package.json: ${params.packagePayload.error}` }; return { pluginId: params.pluginId, installPath: params.installPath, reason: "missing-package-json", detail: `package.json is missing under ${params.installPath}` }; } function hasNativePackageMetadata(manifest) { return resolvePackageExtensionEntries(manifest).status !== "missing"; } async function hasNativePackageInstallPayload(installPath) { const packagePayload = await readPackagePayloadManifest(installPath); return packagePayload.status === "present" && hasNativePackageMetadata(packagePayload.manifest); } async function validatePackagePayload(params) { const failures = []; const hostDependency = resolveOpenClawHostDependency(params.manifest); if (hostDependency && (hostDependency.declaration === "peerDependencies" || params.installSourceIsAuthoritative && params.installSource === "npm")) { const peerIssue = await auditOpenClawPeerDependencyLink({ packageDir: params.installPath, packageName: params.manifest.name ?? params.pluginId }); if (peerIssue) failures.push({ pluginId: params.pluginId, installPath: params.installPath, reason: "missing-openclaw-peer-link", detail: `Plugin declares ${hostDependency.declaration === "peerDependencies" ? "peerDependency" : "dependency"} "openclaw" but ${hostDependency.declaration === "peerDependencies" ? "peer" : "host"} link audit failed: ${peerIssue.reason}.` }); } const extensionResolution = resolvePackageExtensionEntries(params.manifest); if (extensionResolution.status === "invalid" || extensionResolution.status === "empty") { failures.push({ pluginId: params.pluginId, installPath: params.installPath, reason: "missing-extension-entry", detail: `Plugin extension entry validation failed: ${extensionResolution.status === "invalid" ? extensionResolution.error : "package.json openclaw.extensions is empty"}` }); return failures; } else if (extensionResolution.status === "ok") { const extensionValidation = await validatePackageExtensionEntriesForInstall({ packageDir: params.installPath, extensions: extensionResolution.entries, manifest: params.manifest }); if (!extensionValidation.ok) failures.push({ pluginId: params.pluginId, installPath: params.installPath, reason: "missing-extension-entry", detail: `Plugin extension entry validation failed: ${extensionValidation.error}` }); return failures; } if (typeof params.manifest.main !== "string" || !params.manifest.main.trim()) return failures; const mainRel = params.manifest.main.trim(); const mainPath = path.join(params.installPath, mainRel); if (!(await safeStat(mainPath))?.isFile()) failures.push({ pluginId: params.pluginId, installPath: params.installPath, reason: "missing-main-entry", detail: `Plugin main entry "${mainRel}" not found at ${mainPath}` }); return failures; } function isBundleInstallRecord(record) { return record.format === "bundle" || record.clawhubFamily === "bundle-plugin"; } function resolveBundleInstallRecordPayload(params) { const hasBundleRecordMetadata = isBundleInstallRecord(params.record); if (!hasBundleRecordMetadata && params.record.source !== "marketplace") return { isBundlePayload: false, bundleFormat: null }; const bundleFormat = detectBundleManifestFormat(params.installPath); return { isBundlePayload: hasBundleRecordMetadata || bundleFormat !== null, bundleFormat }; } function validateBundleInstallRecordPayload(params) { const hasBundleRecordMetadata = isBundleInstallRecord(params.record); const bundleFormat = params.bundleFormat === void 0 ? detectBundleManifestFormat(params.installPath) : params.bundleFormat; if (!hasBundleRecordMetadata && !bundleFormat) return null; if (!bundleFormat) return { pluginId: params.pluginId, installPath: params.installPath, reason: "missing-bundle-manifest", detail: `No supported bundle manifest or bundle marker found under ${params.installPath}` }; const bundleManifest = loadBundleManifest({ rootDir: params.installPath, bundleFormat }); if (bundleManifest.ok) return null; return { pluginId: params.pluginId, installPath: params.installPath, reason: bundleManifest.error.startsWith("plugin manifest not found") ? "missing-bundle-manifest" : "invalid-bundle-manifest", detail: `Bundle manifest validation failed: ${bundleManifest.error}` }; } async function safeStat(target) { try { return await fs$1.stat(target); } catch { return null; } } //#endregion export { runPluginPayloadSmokeCheckForManifestRecords as i, isPayloadMissing as n, runPluginPayloadSmokeCheck as r, collectMissingPluginInstallPayloads as t };