fix(channels): preserve filters in offline status diagnostics (#117570)

Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
Peter Steinberger
2026-08-01 12:58:23 -07:00
committed by GitHub
parent 04ac671de6
commit 81e392b7ff
2 changed files with 77 additions and 2 deletions
@@ -11,6 +11,10 @@ vi.mock("../channels/plugins/index.js", () => ({
listChannelPlugins: () => activeChannelPlugins,
getLoadedChannelPlugin: (id: string) => activeChannelPlugins.find((plugin) => plugin.id === id),
getChannelPlugin: (id: string) => activeChannelPlugins.find((plugin) => plugin.id === id),
normalizeChannelId: (value: string) => {
const normalized = value.trim().toLowerCase();
return normalized === "wa" || normalized === "whatsapp" ? "whatsapp" : null;
},
}));
vi.mock("../channels/plugins/read-only.js", () => ({
@@ -25,12 +29,18 @@ async function formatLocalStatusSummary(
cfg: unknown,
options?: {
sourceConfig?: unknown;
channel?: string;
},
) {
const lines = await formatConfigChannelsStatusLines(
cfg as never,
{ mode: "local" },
options?.sourceConfig ? { sourceConfig: options.sourceConfig as never } : undefined,
options
? {
...(options.sourceConfig ? { sourceConfig: options.sourceConfig as never } : {}),
...(options.channel !== undefined ? { channel: options.channel } : {}),
}
: undefined,
);
return lines.join("\n");
}
@@ -193,6 +203,68 @@ function requireReadOnlyPluginListCall(): unknown[] {
}
describe("config-only channels status output", () => {
it.each([
{
label: "unregistered external channels",
channel: "token-only",
included: ["TokenOnly"],
excluded: ["WhatsApp"],
},
{
label: "case-insensitive external channels",
channel: "TOKEN-ONLY",
included: ["TokenOnly"],
excluded: ["WhatsApp"],
},
{
label: "bundled channel aliases",
channel: "wa",
included: ["WhatsApp"],
excluded: ["TokenOnly"],
},
{
label: "unknown channels",
channel: "missing-channel",
included: [],
excluded: ["TokenOnly", "WhatsApp"],
},
{
label: "no channel filter",
channel: undefined,
included: ["TokenOnly", "WhatsApp"],
excluded: [],
},
{
label: "blank channel filters",
channel: " ",
included: ["TokenOnly", "WhatsApp"],
excluded: [],
},
])(
"preserves exact config-only status filtering for $label",
async ({ channel, included, excluded }) => {
activeChannelPlugins.splice(
0,
activeChannelPlugins.length,
makeUnavailableTokenPlugin(),
makeIndeterminateLinkPlugin(),
);
const summary = await formatLocalStatusSummary(
{ channels: { "token-only": {}, whatsapp: {} } },
{ channel },
);
expect(summary).toContain("Gateway not reachable; showing config-only status.");
for (const label of included) {
expect(summary).toContain(label);
}
for (const label of excluded) {
expect(summary).not.toContain(label);
}
},
);
it("uses setup fallback plugins so configured external channels can be shown", async () => {
registerSingleTestPlugin("token-only", makeUnavailableTokenPlugin());
listReadOnlyChannelPluginsForConfig.mockClear();
@@ -1,4 +1,5 @@
// Config-only channel status formatter used when the gateway is unreachable.
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
import { theme } from "../../../packages/terminal-core/src/theme.js";
import {
@@ -68,7 +69,9 @@ export async function formatConfigChannelsStatusLines(
});
const sourceConfig = opts?.sourceConfig ?? cfg;
const requestedChannel = opts?.channel ? normalizeChannelId(opts.channel) : null;
const requestedChannel = opts?.channel
? (normalizeChannelId(opts.channel) ?? normalizeOptionalLowercaseString(opts.channel))
: null;
const plugins = listReadOnlyChannelPluginsForConfig(cfg, {
activationSourceConfig: sourceConfig,
includeSetupFallbackPlugins: true,