diff --git a/CHANGELOG.md b/CHANGELOG.md index a641646e01c..02cbb5b74a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Plugins/ClawHub: explain unavailable explicit ClawHub ClawPack artifact downloads with a temporary npm install hint while ClawHub artifact routing rolls out. Thanks @vincentkoc. - Onboarding/search: install official external web-search plugins such as Brave before saving provider config, and make doctor repair reconcile selected external search providers whose npm payload is missing. Thanks @vincentkoc. - Plugins/externalization: add official npm-first catalogs for externalized channel, provider, and generic plugins, keep unpublished ACPX/Google Chat/LINE bundled, and make missing-plugin repair honor npm-first metadata while ClawHub pack files roll out. Thanks @vincentkoc. - Plugins/update: detect tracked plugin install records whose package directories disappeared during `openclaw update`, reinstall them before normal plugin updates, and fail the update if any install record still points at missing disk payloads. diff --git a/src/plugins/clawhub.test.ts b/src/plugins/clawhub.test.ts index 86f2374f860..1a265bca60a 100644 --- a/src/plugins/clawhub.test.ts +++ b/src/plugins/clawhub.test.ts @@ -555,6 +555,49 @@ describe("installPluginFromClawHub", () => { expect(archiveCleanupMock).toHaveBeenCalledTimes(1); }); + it("points explicit ClawHub ClawPack download failures at npm during launch rollout", async () => { + fetchClawHubPackageVersionMock.mockResolvedValueOnce({ + version: { + version: "2026.3.22", + createdAt: 0, + changelog: "", + compatibility: { + pluginApiRange: ">=2026.3.22", + minGatewayVersion: "2026.3.0", + }, + artifact: { + kind: "npm-pack", + format: "tgz", + sha256: DEMO_CLAWPACK_SHA256, + }, + }, + }); + downloadClawHubPackageArchiveMock.mockRejectedValueOnce( + new ClawHubRequestError({ + path: "/api/v1/packages/demo/versions/2026.3.22/artifact/download", + status: 404, + body: "Not Found", + }), + ); + + const result = await installPluginFromClawHub({ + spec: "clawhub:demo", + baseUrl: "https://clawhub.ai", + }); + + expect(result).toMatchObject({ + ok: false, + error: + 'ClawHub artifact download for "demo@2026.3.22" is not available yet (ClawHub /api/v1/packages/demo/versions/2026.3.22/artifact/download failed (404): Not Found). Use "npm:demo@2026.3.22" for launch installs while ClawHub artifact routing is being rolled out.', + }); + expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledWith( + expect.objectContaining({ + artifact: "clawpack", + }), + ); + expect(installPluginFromArchiveMock).not.toHaveBeenCalled(); + }); + it("does not persist package-level ClawPack metadata for version records without ClawPack facts", async () => { parseClawHubPluginSpecMock.mockReturnValueOnce({ name: "demo", version: "2026.3.21" }); fetchClawHubPackageDetailMock.mockResolvedValueOnce({ diff --git a/src/plugins/clawhub.ts b/src/plugins/clawhub.ts index 4952e92c3bc..0eb2eeb4881 100644 --- a/src/plugins/clawhub.ts +++ b/src/plugins/clawhub.ts @@ -330,6 +330,18 @@ function mapClawHubRequestError( return buildClawHubInstallFailure(formatErrorMessage(error)); } +function formatClawHubClawPackDownloadError(params: { + error: unknown; + packageName: string; + version: string; +}): string { + const message = formatErrorMessage(params.error); + if (!(params.error instanceof ClawHubRequestError)) { + return message; + } + return `ClawHub artifact download for "${params.packageName}@${params.version}" is not available yet (${message}). Use "npm:${params.packageName}@${params.version}" for launch installs while ClawHub artifact routing is being rolled out.`; +} + function resolveRequestedVersion(params: { detail: ClawHubPackageDetail; requestedVersion?: string; @@ -1037,7 +1049,17 @@ export async function installPluginFromClawHub( timeoutMs: params.timeoutMs, }); } catch (error) { - return buildClawHubInstallFailure(formatErrorMessage(error)); + // Fix-me(clawhub): remove this npm hint once ClawHub ClawPack artifact + // routing is live for official package installs. + return buildClawHubInstallFailure( + expectedClawPackSha256 + ? formatClawHubClawPackDownloadError({ + error, + packageName: canonicalPackageName, + version: versionState.version, + }) + : formatErrorMessage(error), + ); } try { if (expectedClawPackSha256) {