mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: accept clawhub plugin api wildcards
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Gateway/models: move local-provider pricing opt-outs, OpenRouter/LiteLLM aliases, and proxy passthrough pricing lookup into plugin manifest metadata so core no longer carries extension-specific pricing tables. Thanks @codex.
|
||||
- CLI/plugins: accept ClawHub plugin API wildcard ranges such as `*` without rejecting compatible plugin installs, while still requiring a valid runtime API version. Fixes #56446; supersedes #56466. Thanks @darconada and @claygeo.
|
||||
- Agents/sessions: acquire the session write lock only after cold bootstrap, plugin, and tool setup so fallback runs are not blocked by stalled pre-model startup work. Thanks @codex.
|
||||
- Browser/plugins: auto-start the bundled browser plugin when root `browser` config is present, including restrictive plugin allowlists, and ignore stale persisted plugin registries whose package paths no longer exist. Thanks @codex.
|
||||
- Gateway/models: skip external OpenRouter and LiteLLM pricing refreshes for local/self-hosted model endpoints so startup does not wait on remote pricing catalogs for local-only Ollama, vLLM, and compatible providers. Thanks @codex.
|
||||
|
||||
@@ -87,6 +87,26 @@ describe("clawhub helpers", () => {
|
||||
expect(satisfiesPluginApiRange("invalid", "^1.2.0")).toBe(false);
|
||||
});
|
||||
|
||||
it.each(["*", "x", "X", "=*", "=x", ">=*", ">=x", "<=*", "^*", "~*"] as const)(
|
||||
"accepts plugin api wildcard range %s for valid runtime versions",
|
||||
(range) => {
|
||||
expect(satisfiesPluginApiRange("2026.3.24", range)).toBe(true);
|
||||
expect(satisfiesPluginApiRange("1.0.0", range)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps wildcard plugin api ranges intersected with concrete comparators", () => {
|
||||
expect(satisfiesPluginApiRange("2026.3.24", "* >=2026.3.22")).toBe(true);
|
||||
expect(satisfiesPluginApiRange("2026.3.21", "* >=2026.3.22")).toBe(false);
|
||||
expect(satisfiesPluginApiRange("2026.3.24", "x <2026.3.24")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects invalid runtime versions and impossible wildcard comparators", () => {
|
||||
expect(satisfiesPluginApiRange("invalid", "*")).toBe(false);
|
||||
expect(satisfiesPluginApiRange("2026.3.24", ">*")).toBe(false);
|
||||
expect(satisfiesPluginApiRange("2026.3.24", "<*")).toBe(false);
|
||||
});
|
||||
|
||||
it("checks min gateway versions with loose host labels", () => {
|
||||
expect(satisfiesGatewayMinimum("2026.3.22", "2026.3.0")).toBe(true);
|
||||
expect(satisfiesGatewayMinimum("OpenClaw 2026.3.22", "2026.3.0")).toBe(true);
|
||||
|
||||
@@ -299,11 +299,24 @@ function upperBoundForCaret(version: string): string | null {
|
||||
return `0.0.${parsed.patch + 1}`;
|
||||
}
|
||||
|
||||
function matchWildcardComparator(token: string): "any" | "none" | null {
|
||||
const match = /^(>=|<=|>|<|=|\^|~)?\s*([*xX])$/.exec(token);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const operator = match[1];
|
||||
return operator === ">" || operator === "<" ? "none" : "any";
|
||||
}
|
||||
|
||||
function satisfiesComparator(version: string, token: string): boolean {
|
||||
const trimmed = token.trim();
|
||||
if (!trimmed) {
|
||||
return true;
|
||||
}
|
||||
const wildcard = matchWildcardComparator(trimmed);
|
||||
if (wildcard) {
|
||||
return wildcard === "any" && parseComparableSemver(version) != null;
|
||||
}
|
||||
if (trimmed.startsWith("^")) {
|
||||
const base = trimmed.slice(1).trim();
|
||||
const upperBound = upperBoundForCaret(base);
|
||||
|
||||
@@ -249,6 +249,64 @@ describe("installPluginFromClawHub", () => {
|
||||
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("installs when ClawHub advertises a wildcard plugin API range", async () => {
|
||||
fetchClawHubPackageVersionMock.mockResolvedValueOnce({
|
||||
version: {
|
||||
version: "2026.3.22",
|
||||
createdAt: 0,
|
||||
changelog: "",
|
||||
sha256hash: "a9eac48c6129bc44b6f93c9a9f48f6c700d191b7279a1e1915f28df6f59bb1af",
|
||||
compatibility: {
|
||||
pluginApiRange: "*",
|
||||
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({
|
||||
version: {
|
||||
version: "2026.3.22",
|
||||
createdAt: 0,
|
||||
changelog: "",
|
||||
sha256hash: "a9eac48c6129bc44b6f93c9a9f48f6c700d191b7279a1e1915f28df6f59bb1af",
|
||||
compatibility: {
|
||||
pluginApiRange: "*",
|
||||
minGatewayVersion: "2026.3.0",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
code: CLAWHUB_INSTALL_ERROR_CODE.INCOMPATIBLE_PLUGIN_API,
|
||||
error: 'Plugin "demo" requires plugin API *, but this OpenClaw runtime exposes invalid.',
|
||||
});
|
||||
expect(downloadClawHubPackageArchiveMock).not.toHaveBeenCalled();
|
||||
expect(installPluginFromArchiveMock).not.toHaveBeenCalled();
|
||||
expect(archiveCleanupMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes dangerous force unsafe install through to archive installs", async () => {
|
||||
await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
|
||||
Reference in New Issue
Block a user