From b520e40cf6d4dbf32594b98a93be4eef5b5e3940 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 16:24:07 -0700 Subject: [PATCH] fix(plugins): merge external catalog channel hints --- CHANGELOG.md | 1 + src/plugins/manifest-registry.test.ts | 42 +++++++++++++++++++++++++++ src/plugins/manifest-registry.ts | 32 +++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9cb1c92e27..f2d0baf4557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Plugins/catalog: merge official external catalog descriptors into partial package channel config metadata, so lagging WeCom/Yuanbao manifests keep their own schema while still exposing host-supplied labels and setup text. Thanks @vincentkoc. - Plugins/catalog: supplement lagging official external WeCom and Yuanbao npm manifests with channel config descriptors and declared tool contracts from the OpenClaw catalog, so trusted package sweeps no longer fail because external package metadata trails the host contract. Thanks @vincentkoc. - Plugins/install: let trusted official `@openclaw/*` catalog installs recover when npm `latest` points at a prerelease by falling back to the newest stable version, or by allowing prerelease-only launch packages with a warning instead of making beta/development plugin sweeps fail at install time. Thanks @vincentkoc. - Google Meet: grant Chrome media permissions against the actual Meet tab, start the local realtime audio bridge only after Meet joins, expose realtime transcripts in status/logs, and force explicit audio responses with current OpenAI realtime output-audio events so BlackHole capture does not keep the OpenClaw participant muted or silent. diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index 9abcc19dec5..ef0519da8ff 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -1133,6 +1133,48 @@ describe("loadPluginManifestRegistry", () => { ).toBe(false); }); + it("fills missing official external catalog descriptors for partial npm channel configs", () => { + const dir = makeTempDir(); + writeManifest(dir, { + id: "wecom-openclaw-plugin", + channels: ["wecom"], + configSchema: { type: "object" }, + channelConfigs: { + wecom: { + schema: { + type: "object", + additionalProperties: false, + properties: { + corpId: { type: "string" }, + }, + }, + }, + }, + }); + + const registry = loadRegistry([ + createPluginCandidate({ + idHint: "wecom-openclaw-plugin", + rootDir: dir, + origin: "global", + packageName: "@wecom/wecom-openclaw-plugin", + }), + ]); + + expect(registry.plugins[0]?.channelConfigs?.wecom).toEqual( + expect.objectContaining({ + label: "WeCom", + description: "Enterprise WeChat conversation channel.", + schema: expect.objectContaining({ + additionalProperties: false, + properties: { + corpId: { type: "string" }, + }, + }), + }), + ); + }); + it("drops prototype-polluting channel config keys from plugin manifests", () => { const dir = makeTempDir(); writeTextFile( diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index d0a6676a66a..1fb72bd82f5 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -322,7 +322,37 @@ function mergeCatalogChannelConfigs(params: { } for (const [key, value] of Object.entries(params.manifestChannelConfigs ?? {})) { if (!isBlockedObjectKey(key)) { - merged[key] = value; + const catalogValue = merged[key]; + merged[key] = catalogValue + ? { + ...catalogValue, + ...value, + schema: value.schema ?? catalogValue.schema, + ...(catalogValue.uiHints || value.uiHints + ? { + uiHints: { + ...catalogValue.uiHints, + ...value.uiHints, + }, + } + : {}), + ...((value.runtime ?? catalogValue.runtime) + ? { runtime: value.runtime ?? catalogValue.runtime } + : {}), + ...((value.label ?? catalogValue.label) + ? { label: value.label ?? catalogValue.label } + : {}), + ...((value.description ?? catalogValue.description) + ? { description: value.description ?? catalogValue.description } + : {}), + ...((value.preferOver ?? catalogValue.preferOver) + ? { preferOver: value.preferOver ?? catalogValue.preferOver } + : {}), + ...((value.commands ?? catalogValue.commands) + ? { commands: value.commands ?? catalogValue.commands } + : {}), + } + : value; } } return Object.keys(merged).length > 0 ? merged : undefined;