fix(plugins): treat CalVer correction versions as compatible with plugin API ranges (#77450)

* fix(plugins): accept CalVer correction plugin API hosts

Fixes #77293

* docs(changelog): credit plugin api calver fix pr

---------

Co-authored-by: pingu <pingu@penchan.co>
This commit is contained in:
Penchan
2026-05-05 05:46:29 +08:00
committed by GitHub
parent a4f2bf273a
commit 1c52447f0b
4 changed files with 48 additions and 1 deletions

View File

@@ -64,6 +64,7 @@ Docs: https://docs.openclaw.ai
- Doctor/config: restore legacy group chat config migrations for `routing.allowFrom`, `routing.groupChat.*`, and `channels.telegram.requireMention` so upgrades keep WhatsApp, Telegram, and iMessage group mention gates and history settings instead of leaving configs invalid or silently blocked. Thanks @scoootscooob.
- CLI/update: make package-update follow-up processes write completion results and exit explicitly, so Windows packaged upgrades do not hang after the new package finishes post-core plugin work. Thanks @vincentkoc.
- Release validation: skip Slack live QA unless Slack credentials are explicitly configured, so release gates can keep proving non-Slack surfaces while Slack is still local and credential-gated. Thanks @vincentkoc.
- Plugins/update: treat OpenClaw CalVer correction versions like `2026.5.3-1` as satisfying base plugin API ranges, so correction builds can install plugins that require the base runtime API. Fixes #77293. (#77450) Thanks @p3nchan.
- fix(gateway): clamp unbound websocket auth scopes [AI]. (#77413) Thanks @pgondhi987.
- Gate zalouser startup name matching [AI]. (#77411) Thanks @pgondhi987.
- Active Memory: send a bounded latest-message search query to the recall worker so channel/runtime metadata does not become the memory search string. Fixes #65309. Thanks @joeykrug, @westley3601, @pimenov, and @tasi333.

View File

@@ -100,6 +100,12 @@ describe("clawhub helpers", () => {
expect(satisfiesPluginApiRange("invalid", "^1.2.0")).toBe(false);
});
it("treats OpenClaw CalVer correction versions as stable plugin API hosts", () => {
expect(satisfiesPluginApiRange("2026.5.3-1", ">=2026.5.3")).toBe(true);
expect(satisfiesPluginApiRange("2026.5.3-2", ">=2026.5.3")).toBe(true);
expect(satisfiesPluginApiRange("2026.5.3-beta.1", ">=2026.5.3")).toBe(false);
});
it("accepts legacy bare major.minor plugin api ranges as lower bounds", () => {
expect(satisfiesPluginApiRange("2026.5.2", "2026.4")).toBe(true);
expect(satisfiesPluginApiRange("2026.4.0", "2026.4")).toBe(true);

View File

@@ -542,6 +542,13 @@ function satisfiesSemverRange(version: string, range: string): boolean {
return tokens.every((token) => satisfiesComparator(version, token));
}
const OPENCLAW_CALVER_STABLE_CORRECTION_PATTERN = /^[vV]?(\d{4}\.\d{1,2}\.\d{1,2})-\d+$/;
function normalizeCalVerCorrectionForPluginApi(pluginApiVersion: string): string {
const match = OPENCLAW_CALVER_STABLE_CORRECTION_PATTERN.exec(pluginApiVersion.trim());
return match?.[1] ?? pluginApiVersion;
}
function buildUrl(params: Pick<ClawHubRequestParams, "baseUrl" | "path" | "search">): URL {
const url = new URL(params.path, `${normalizeBaseUrl(params.baseUrl)}/`);
for (const [key, value] of Object.entries(params.search ?? {})) {
@@ -1046,7 +1053,10 @@ export function satisfiesPluginApiRange(
if (!pluginApiRange) {
return true;
}
return satisfiesSemverRange(pluginApiVersion, pluginApiRange);
return satisfiesSemverRange(
normalizeCalVerCorrectionForPluginApi(pluginApiVersion),
pluginApiRange,
);
}
export function satisfiesGatewayMinimum(

View File

@@ -852,6 +852,36 @@ describe("installPluginFromClawHub", () => {
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
});
it("installs when a CalVer correction runtime satisfies the base plugin API range", async () => {
resolveCompatibilityHostVersionMock.mockReturnValueOnce("2026.5.3-1");
fetchClawHubPackageVersionMock.mockResolvedValueOnce({
version: {
version: "2026.5.3",
createdAt: 0,
changelog: "",
sha256hash: "a9eac48c6129bc44b6f93c9a9f48f6c700d191b7279a1e1915f28df6f59bb1af",
compatibility: {
pluginApiRange: ">=2026.5.3",
minGatewayVersion: "2026.3.0",
},
},
});
const result = await installPluginFromClawHub({
spec: "clawhub:demo",
baseUrl: "https://clawhub.ai",
});
expectSuccessfulClawHubInstall(result);
expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledTimes(1);
expect(installPluginFromArchiveMock).toHaveBeenCalledWith(
expect.objectContaining({
archivePath: "/tmp/clawhub-demo/archive.zip",
}),
);
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
});
it("does not let a wildcard plugin API range hide an invalid runtime version", async () => {
resolveCompatibilityHostVersionMock.mockReturnValueOnce("invalid");
fetchClawHubPackageVersionMock.mockResolvedValueOnce({