






















@@ -42,6 +42,28 @@ function requireNpmInstallSource(source: ReturnType<typeof describePluginInstall
4242return source.npm;
4343}
444445+function findCatalogEntry(
46+entries: OfficialChannelCatalogEntry[],
47+predicate: (entry: OfficialChannelCatalogEntry) => boolean,
48+): OfficialChannelCatalogEntry {
49+const entry = entries.find(predicate);
50+if (!entry) {
51+throw new Error("expected official channel catalog entry");
52+}
53+return entry;
54+}
55+56+function summarizeCatalogEntry(entry: OfficialChannelCatalogEntry) {
57+return {
58+name: entry.name,
59+description: entry.description,
60+source: entry.source,
61+plugin: entry.openclaw?.plugin,
62+channel: entry.openclaw?.channel,
63+install: entry.openclaw?.install,
64+};
65+}
66+4567afterEach(() => {
4668cleanupTempDirs(tempDirs);
4769});
@@ -91,66 +113,93 @@ describe("buildOfficialChannelCatalog", () => {
91113},
92114});
9311594-expect(buildOfficialChannelCatalog({ repoRoot }).entries).toEqual(
95-expect.arrayContaining([
96-expect.objectContaining({
97-name: "@wecom/wecom-openclaw-plugin",
98-openclaw: expect.objectContaining({
99-plugin: {
100-id: "wecom-openclaw-plugin",
101-label: "WeCom",
102-},
103-channel: expect.objectContaining({
104-id: "wecom",
105-label: "WeCom",
106-}),
107-install: {
108-npmSpec: "@wecom/wecom-openclaw-plugin@2026.4.23",
109-defaultChoice: "npm",
110-expectedIntegrity:
111-"sha512-bnzfdIEEu1/LFvcdyjaTkyxt27w6c7dqhkPezU62OWaqmcdFsUGR3T55USK/O9pIKsNcnL1Tnu1pqKYCWHFgWQ==",
112-},
113-}),
114-}),
115-expect.objectContaining({
116-name: "openclaw-plugin-yuanbao",
117-openclaw: expect.objectContaining({
118-plugin: {
119-id: "openclaw-plugin-yuanbao",
120-label: "Yuanbao",
121-},
122-channel: expect.objectContaining({
123-id: "yuanbao",
124-label: "Yuanbao",
125-}),
126-install: {
127-npmSpec: "openclaw-plugin-yuanbao@2.13.0",
128-defaultChoice: "npm",
129-expectedIntegrity:
130-"sha512-mx6b2gO8oqZxECG9NLLQofScaIZXjmQXqJxevagVx8IKXLGeLrpTWlvnW1P2NP5dqaSMrkvBsgJqtW+rVM7h4w==",
131-},
132-}),
133-}),
134-expect.objectContaining({
135-name: "@openclaw/whatsapp",
136-description: "OpenClaw WhatsApp channel plugin",
137-source: "official",
138-openclaw: expect.objectContaining({
139-channel: expect.objectContaining({
140-id: "whatsapp",
141-label: "WhatsApp",
142-selectionLabel: "WhatsApp (QR link)",
143-detailLabel: "WhatsApp Web",
144-docsPath: "/channels/whatsapp",
145-}),
146-install: expect.objectContaining({
147-npmSpec: "@openclaw/whatsapp",
148-defaultChoice: "npm",
149-}),
150-}),
151-}),
152-]),
153-);
116+const entries = buildOfficialChannelCatalog({ repoRoot }).entries;
117+118+expect(
119+summarizeCatalogEntry(
120+findCatalogEntry(entries, (entry) => entry.name === "@wecom/wecom-openclaw-plugin"),
121+),
122+).toEqual({
123+name: "@wecom/wecom-openclaw-plugin",
124+description: "OpenClaw WeCom channel plugin by the Tencent WeCom team.",
125+source: "external",
126+plugin: {
127+id: "wecom-openclaw-plugin",
128+label: "WeCom",
129+},
130+channel: {
131+id: "wecom",
132+label: "WeCom",
133+selectionLabel: "WeCom(企业微信)",
134+detailLabel: "WeCom",
135+docsLabel: "wecom",
136+docsPath: "/plugins/community#wecom",
137+blurb: "Enterprise messaging and documents, scheduling, task tools.",
138+order: 45,
139+aliases: ["qywx", "wework", "enterprise-wechat"],
140+},
141+install: {
142+npmSpec: "@wecom/wecom-openclaw-plugin@2026.4.23",
143+defaultChoice: "npm",
144+expectedIntegrity:
145+"sha512-bnzfdIEEu1/LFvcdyjaTkyxt27w6c7dqhkPezU62OWaqmcdFsUGR3T55USK/O9pIKsNcnL1Tnu1pqKYCWHFgWQ==",
146+},
147+});
148+expect(
149+summarizeCatalogEntry(
150+findCatalogEntry(entries, (entry) => entry.name === "openclaw-plugin-yuanbao"),
151+),
152+).toEqual({
153+name: "openclaw-plugin-yuanbao",
154+description: "OpenClaw Yuanbao channel plugin by the Tencent Yuanbao team.",
155+source: "external",
156+plugin: {
157+id: "openclaw-plugin-yuanbao",
158+label: "Yuanbao",
159+},
160+channel: {
161+id: "yuanbao",
162+label: "Yuanbao",
163+selectionLabel: "Yuanbao (元宝)",
164+detailLabel: "Yuanbao",
165+docsLabel: "yuanbao",
166+docsPath: "/plugins/community#yuanbao",
167+blurb: "Tencent Yuanbao AI assistant conversation channel.",
168+order: 85,
169+aliases: ["yuanbao", "yb", "tencent-yuanbao", "元宝"],
170+},
171+install: {
172+npmSpec: "openclaw-plugin-yuanbao@2.13.0",
173+defaultChoice: "npm",
174+expectedIntegrity:
175+"sha512-mx6b2gO8oqZxECG9NLLQofScaIZXjmQXqJxevagVx8IKXLGeLrpTWlvnW1P2NP5dqaSMrkvBsgJqtW+rVM7h4w==",
176+},
177+});
178+expect(
179+summarizeCatalogEntry(
180+findCatalogEntry(entries, (entry) => entry.name === "@openclaw/whatsapp"),
181+),
182+).toEqual({
183+name: "@openclaw/whatsapp",
184+description: "OpenClaw WhatsApp channel plugin",
185+source: "official",
186+plugin: undefined,
187+channel: {
188+id: "whatsapp",
189+label: "WhatsApp",
190+selectionLabel: "WhatsApp (QR link)",
191+detailLabel: "WhatsApp Web",
192+docsLabel: "whatsapp",
193+docsPath: "/channels/whatsapp",
194+blurb: "works with your own number; recommend a separate phone + eSIM.",
195+systemImage: "message",
196+},
197+install: {
198+npmSpec: "@openclaw/whatsapp",
199+defaultChoice: "npm",
200+minHostVersion: ">=2026.4.25",
201+},
202+});
154203});
155204156205it("keeps third-party official external catalog npm sources exactly pinned", () => {
@@ -173,18 +222,17 @@ describe("buildOfficialChannelCatalog", () => {
173222(entry) => entry.openclaw?.channel?.id === "twitch",
174223);
175224176-expect(twitch).toEqual(
177-expect.objectContaining({
178-name: "@openclaw/twitch",
179-openclaw: expect.objectContaining({
180-install: {
181-npmSpec: "@openclaw/twitch",
182-defaultChoice: "npm",
183-minHostVersion: ">=2026.4.10",
184-},
185-}),
186-}),
187-);
225+expect({
226+name: twitch?.name,
227+install: twitch?.openclaw?.install,
228+}).toEqual({
229+name: "@openclaw/twitch",
230+install: {
231+npmSpec: "@openclaw/twitch",
232+defaultChoice: "npm",
233+minHostVersion: ">=2026.4.10",
234+},
235+});
188236const installSource = describePluginInstallSource(requireInstall(twitch));
189237expect(requireNpmInstallSource(installSource).pinState).toBe("floating-without-integrity");
190238expect(installSource.warnings).toEqual(["npm-spec-floating", "npm-spec-missing-integrity"]);
@@ -249,33 +297,40 @@ describe("buildOfficialChannelCatalog", () => {
249297250298const outputPath = path.join(repoRoot, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH);
251299expect(fs.existsSync(outputPath)).toBe(true);
252-expect(JSON.parse(fs.readFileSync(outputPath, "utf8")).entries).toEqual(
253-expect.arrayContaining([
254-expect.objectContaining({
255-name: "@wecom/wecom-openclaw-plugin",
256-}),
257-expect.objectContaining({
258-name: "openclaw-plugin-yuanbao",
259-}),
260-expect.objectContaining({
261-name: "@openclaw/whatsapp",
262-source: "official",
263-openclaw: expect.objectContaining({
264-channel: expect.objectContaining({
265-id: "whatsapp",
266-label: "WhatsApp",
267-selectionLabel: "WhatsApp (QR link)",
268-docsPath: "/channels/whatsapp",
269-}),
270-install: expect.objectContaining({
271-npmSpec: "@openclaw/whatsapp",
272-defaultChoice: "npm",
273-}),
274-}),
275-}),
276-]),
300+const entries = JSON.parse(fs.readFileSync(outputPath, "utf8")).entries;
301+expect(entries.map((entry: { name?: string }) => entry.name)).toContain(
302+"@wecom/wecom-openclaw-plugin",
303+);
304+expect(entries.map((entry: { name?: string }) => entry.name)).toContain(
305+"openclaw-plugin-yuanbao",
277306);
278-const whatsappEntries = JSON.parse(fs.readFileSync(outputPath, "utf8")).entries.filter(
307+const whatsappEntry = findCatalogEntry(
308+entries,
309+(entry: { openclaw?: { channel?: { id?: string } } }) =>
310+entry.openclaw?.channel?.id === "whatsapp",
311+);
312+expect(summarizeCatalogEntry(whatsappEntry)).toEqual({
313+name: "@openclaw/whatsapp",
314+description: "OpenClaw WhatsApp channel plugin",
315+source: "official",
316+plugin: undefined,
317+channel: {
318+id: "whatsapp",
319+label: "WhatsApp",
320+selectionLabel: "WhatsApp (QR link)",
321+detailLabel: "WhatsApp Web",
322+docsLabel: "whatsapp",
323+docsPath: "/channels/whatsapp",
324+blurb: "works with your own number; recommend a separate phone + eSIM.",
325+systemImage: "message",
326+},
327+install: {
328+npmSpec: "@openclaw/whatsapp",
329+defaultChoice: "npm",
330+minHostVersion: ">=2026.4.25",
331+},
332+});
333+const whatsappEntries = entries.filter(
279334(entry: { openclaw?: { channel?: { id?: string } } }) =>
280335entry.openclaw?.channel?.id === "whatsapp",
281336);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。