From d976f8828b60195eebed7958ffccfa65a61593ea Mon Sep 17 00:00:00 2001 From: Simone Date: Sun, 2 Aug 2026 02:23:40 +0200 Subject: [PATCH] fix(plugins): ignore malformed build version metadata (#117639) --- src/plugins/loader-shared.test.ts | 48 ++++++++++++++++++++++++++++++- src/plugins/loader-shared.ts | 9 ++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/plugins/loader-shared.test.ts b/src/plugins/loader-shared.test.ts index c3214c7deb81..e0a35a25fa85 100644 --- a/src/plugins/loader-shared.test.ts +++ b/src/plugins/loader-shared.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from "vitest"; -import { validatePluginConfig } from "./loader-shared.js"; +import type { PluginCandidate } from "./discovery.js"; +import { createManifestPluginRecord, validatePluginConfig } from "./loader-shared.js"; +import type { PluginManifestRecord } from "./manifest-registry.js"; const emptyObjectSchema = { type: "object", @@ -11,6 +13,50 @@ function withSchemaKeyword(key: "if" | "then" | "else", value: unknown) { return { [key]: value }; } +const manifestRecord = { + id: "example", + channels: [], + providers: [], + cliBackends: [], + skills: [], + hooks: [], + origin: "global", + rootDir: "/plugins/example", + source: "/plugins/example/index.js", + manifestPath: "/plugins/example/openclaw.plugin.json", +} satisfies PluginManifestRecord; + +function createRecordWithBuildVersion(openclawVersion: unknown) { + const candidate = { + idHint: "example", + source: manifestRecord.source, + rootDir: manifestRecord.rootDir, + origin: manifestRecord.origin, + packageManifest: { + build: { openclawVersion }, + } as unknown as NonNullable, + } satisfies PluginCandidate; + + return createManifestPluginRecord({ + candidate, + manifestRecord, + enabled: true, + activationState: { + enabled: true, + activated: true, + explicitlyEnabled: true, + source: "explicit", + }, + }); +} + +describe("createManifestPluginRecord", () => { + it("ignores malformed package build version metadata", () => { + expect(createRecordWithBuildVersion(" 2026.7.2 ").builtWithOpenClawVersion).toBe("2026.7.2"); + expect(createRecordWithBuildVersion(42).builtWithOpenClawVersion).toBeUndefined(); + }); +}); + describe("validatePluginConfig empty schema classification", () => { it("validates pattern properties instead of requiring empty config", () => { const schema = { diff --git a/src/plugins/loader-shared.ts b/src/plugins/loader-shared.ts index fefce332f611..5945d4a7912a 100644 --- a/src/plugins/loader-shared.ts +++ b/src/plugins/loader-shared.ts @@ -1,7 +1,10 @@ import fs from "node:fs"; import path from "node:path"; import { err as resultError, ok, type Result } from "@openclaw/normalization-core/result"; -import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { activateContextEngineRegistrations } from "../context-engine/registry.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; @@ -285,7 +288,9 @@ export function createManifestPluginRecord(params: { description: manifestRecord.description, packageVersion: manifestRecord.packageVersion, version: manifestRecord.version, - builtWithOpenClawVersion: candidate.packageManifest?.build?.openclawVersion?.trim(), + builtWithOpenClawVersion: normalizeOptionalString( + candidate.packageManifest?.build?.openclawVersion, + ), packageName: manifestRecord.packageName, format: manifestRecord.format, bundleFormat: manifestRecord.bundleFormat,