



















@@ -3,9 +3,18 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
33const mocks = vi.hoisted(() => ({
44listChannelPlugins: vi.fn(),
55resolveOutboundChannelPlugin: vi.fn(),
6+missingOfficialExternalChannels: new Set<string>(),
67}));
788-const deliverableChannelIds = vi.hoisted(() => ["alpha", "beta", "gamma", "delta", "muted"]);
9+const deliverableChannelIds = vi.hoisted(() => [
10+"alpha",
11+"beta",
12+"gamma",
13+"delta",
14+"feishu",
15+"muted",
16+"whatsapp",
17+]);
9181019vi.mock("../../channels/plugins/index.js", () => ({
1120getLoadedChannelPlugin: vi.fn(),
@@ -23,6 +32,21 @@ vi.mock("./channel-resolution.js", () => ({
2332resolveOutboundChannelPlugin: mocks.resolveOutboundChannelPlugin,
2433}));
253435+vi.mock("../../plugins/official-external-plugin-repair-hints.js", () => ({
36+resolveMissingOfficialExternalChannelPluginRepairHint: ({ channelId }: { channelId: string }) =>
37+mocks.missingOfficialExternalChannels.has(channelId)
38+ ? {
39+pluginId: channelId,
40+ channelId,
41+label: channelId === "whatsapp" ? "WhatsApp" : "Feishu",
42+installSpec: `@openclaw/${channelId}`,
43+installCommand: `openclaw plugins install @openclaw/${channelId}`,
44+doctorFixCommand: "openclaw doctor --fix",
45+repairHint: `Install the official external plugin with: openclaw plugins install @openclaw/${channelId}, or run: openclaw doctor --fix.`,
46+}
47+ : null,
48+}));
49+2650type ChannelSelectionModule = typeof import("./channel-selection.js");
2751type RuntimeModule = typeof import("../../runtime.js");
2852@@ -141,6 +165,11 @@ describe("resolveMessageChannelSelection", () => {
141165beforeEach(() => {
142166mocks.listChannelPlugins.mockReset();
143167mocks.listChannelPlugins.mockReturnValue([]);
168+mocks.resolveOutboundChannelPlugin.mockReset();
169+mocks.resolveOutboundChannelPlugin.mockImplementation(({ channel }: { channel: string }) => ({
170+id: channel,
171+}));
172+mocks.missingOfficialExternalChannels.clear();
144173});
145174146175it.each([
@@ -228,10 +257,43 @@ describe("resolveMessageChannelSelection", () => {
228257params: { cfg: {} as never, channel: "alpha" },
229258expectedMessage: "Channel is unavailable: alpha",
230259},
260+{
261+setup: () => {
262+mocks.resolveOutboundChannelPlugin.mockReturnValue(undefined);
263+mocks.missingOfficialExternalChannels.add("feishu");
264+},
265+params: {
266+cfg: { channels: { feishu: { appId: "cli_xxx" } } } as never,
267+channel: "feishu",
268+},
269+expectedMessage:
270+"Channel is unavailable: feishu. Install the official external plugin with: openclaw plugins install @openclaw/feishu, or run: openclaw doctor --fix.",
271+},
231272{
232273params: { cfg: {} as never },
233274expectedMessage: "Channel is required (no configured channels detected).",
234275},
276+{
277+setup: () => {
278+mocks.resolveOutboundChannelPlugin.mockReturnValue(undefined);
279+mocks.missingOfficialExternalChannels.add("whatsapp");
280+},
281+params: { cfg: { channels: { whatsapp: { enabled: true } } } as never },
282+expectedMessage:
283+"Channel is required (no available channels detected). Configured official external channel WhatsApp is missing its plugin. Install the official external plugin with: openclaw plugins install @openclaw/whatsapp, or run: openclaw doctor --fix.",
284+},
285+{
286+setup: () => {
287+mocks.listChannelPlugins.mockReturnValue([
288+makePlugin({
289+id: "whatsapp",
290+isConfigured: async () => false,
291+}),
292+]);
293+},
294+params: { cfg: { channels: { whatsapp: { enabled: true } } } as never },
295+expectedMessage: "Channel is required (no configured channels detected).",
296+},
235297{
236298setup: () => {
237299mocks.listChannelPlugins.mockReturnValue([
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。