From 961575ddf7e4c6078e8896479f634a767f3ccd5f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 02:59:49 -0700 Subject: [PATCH] fix(catalog): preserve channel ClawHub specs --- CHANGELOG.md | 1 + scripts/write-official-channel-catalog.mjs | 10 +++++-- test/official-channel-catalog.test.ts | 34 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 845c87642a6..2d038c78616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai - Plugins/externalization: pin beta-only official launch packages for ACPX, Google Chat, and LINE to explicit npm beta specs so catalog-driven installs do not trip the prerelease safety guard while npm `latest` still points at beta. Thanks @vincentkoc. - CLI/doctor: keep missing-plugin repair from overriding official catalog metadata with runtime fallbacks, so ACPX repairs preserve the beta npm spec during the externalization rollout. Thanks @vincentkoc. +- Plugins/catalog: preserve ClawHub install specs when generating the packaged channel catalog so future storepack-first channel plugins keep their remote source instead of becoming npm-only. Thanks @vincentkoc. - Control UI/Talk: fix Talk (OpenAI Realtime WebRTC) CORS failure by stripping server-side-only attribution headers (`originator`, `version`, `User-Agent`) from browser offer headers; `api.openai.com/v1/realtime/calls` only allows `authorization` and `content-type` in its CORS preflight, so forwarding these headers caused the browser SDP exchange to fail. Fixes #76435. Thanks @hclsys. - CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola. - Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc. diff --git a/scripts/write-official-channel-catalog.mjs b/scripts/write-official-channel-catalog.mjs index ab40d233b9f..48ea37fbe91 100644 --- a/scripts/write-official-channel-catalog.mjs +++ b/scripts/write-official-channel-catalog.mjs @@ -9,16 +9,20 @@ export const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = "dist/channel-catalog.json function toCatalogInstall(value, packageName) { const install = isRecord(value) ? value : {}; + const clawhubSpec = trimString(install.clawhubSpec); const npmSpec = trimString(install.npmSpec) || packageName; - if (!npmSpec) { + if (!clawhubSpec && !npmSpec) { return null; } const defaultChoice = trimString(install.defaultChoice); const minHostVersion = trimString(install.minHostVersion); const expectedIntegrity = trimString(install.expectedIntegrity); return { - npmSpec, - ...(defaultChoice === "npm" || defaultChoice === "local" ? { defaultChoice } : {}), + ...(clawhubSpec ? { clawhubSpec } : {}), + ...(npmSpec ? { npmSpec } : {}), + ...(defaultChoice === "clawhub" || defaultChoice === "npm" || defaultChoice === "local" + ? { defaultChoice } + : {}), ...(minHostVersion ? { minHostVersion } : {}), ...(expectedIntegrity ? { expectedIntegrity } : {}), ...(install.allowInvalidConfigRecovery === true ? { allowInvalidConfigRecovery: true } : {}), diff --git a/test/official-channel-catalog.test.ts b/test/official-channel-catalog.test.ts index 945d3bacdd5..1ea91a050a4 100644 --- a/test/official-channel-catalog.test.ts +++ b/test/official-channel-catalog.test.ts @@ -168,6 +168,40 @@ describe("buildOfficialChannelCatalog", () => { expect(installSource.warnings).toEqual(["npm-spec-floating", "npm-spec-missing-integrity"]); }); + it("preserves ClawHub specs when generating publishable channel catalog entries", () => { + const repoRoot = makeRepoRoot("openclaw-official-channel-catalog-clawhub-"); + writeJson(path.join(repoRoot, "extensions", "storepack-chat", "package.json"), { + name: "@openclaw/storepack-chat", + openclaw: { + channel: { + id: "storepack-chat", + label: "Storepack Chat", + selectionLabel: "Storepack Chat", + docsPath: "/channels/storepack-chat", + blurb: "storepack-first channel", + }, + install: { + clawhubSpec: "clawhub:@openclaw/storepack-chat", + npmSpec: "@openclaw/storepack-chat", + defaultChoice: "clawhub", + }, + release: { + publishToNpm: true, + }, + }, + }); + + const entry = buildOfficialChannelCatalog({ repoRoot }).entries.find( + (candidate) => candidate.openclaw?.channel?.id === "storepack-chat", + ); + + expect(entry?.openclaw?.install).toEqual({ + clawhubSpec: "clawhub:@openclaw/storepack-chat", + npmSpec: "@openclaw/storepack-chat", + defaultChoice: "clawhub", + }); + }); + it("writes the official catalog under dist", () => { const repoRoot = makeRepoRoot("openclaw-official-channel-catalog-write-"); writeJson(path.join(repoRoot, "extensions", "whatsapp", "package.json"), {