diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4e7fe4aba..93ec2e2d284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Plugins/externalization: keep ACPX, Google Chat, and LINE publishable plugin dist trees out of the core npm package file list. +- Status/channels: show configured channels in `openclaw status` and config-only `openclaw channels status` output even when the Gateway is unreachable, avoiding empty Channels tables on WSL and other no-Gateway paths. Thanks @vincentkoc. - Plugins/ClawHub: explain unavailable explicit ClawHub ClawPack artifact downloads with a temporary npm install hint while ClawHub artifact routing rolls out. Thanks @vincentkoc. - Media: accept home-relative `MEDIA:~/...` attachment paths while preserving existing file-read policy, traversal checks, and media type validation. Fixes #73796. Thanks @fabkury. - Onboarding/search: install official external web-search plugins such as Brave before saving provider config, and make doctor repair reconcile selected external search providers whose npm payload is missing. Thanks @vincentkoc. diff --git a/src/commands/channels.config-only-status-output.test.ts b/src/commands/channels.config-only-status-output.test.ts index 44a8d85eba1..f0b5f350560 100644 --- a/src/commands/channels.config-only-status-output.test.ts +++ b/src/commands/channels.config-only-status-output.test.ts @@ -4,6 +4,7 @@ import { makeDirectPlugin } from "../test-utils/channel-plugin-test-fixtures.js" import { formatConfigChannelsStatusLines } from "./channels/status-config-format.js"; const activeChannelPlugins = vi.hoisted(() => [] as ChannelPlugin[]); +const listReadOnlyChannelPluginsForConfig = vi.hoisted(() => vi.fn(() => activeChannelPlugins)); vi.mock("../channels/plugins/index.js", () => ({ listChannelPlugins: () => activeChannelPlugins, @@ -12,7 +13,7 @@ vi.mock("../channels/plugins/index.js", () => ({ })); vi.mock("../channels/plugins/read-only.js", () => ({ - listReadOnlyChannelPluginsForConfig: () => activeChannelPlugins, + listReadOnlyChannelPluginsForConfig, })); vi.mock("../channels/plugins/status.js", () => ({ @@ -191,6 +192,20 @@ function expectResolvedTokenStatusSummary( } describe("config-only channels status output", () => { + it("uses setup fallback plugins so configured external channels can be shown", async () => { + registerSingleTestPlugin("token-only", makeUnavailableTokenPlugin()); + listReadOnlyChannelPluginsForConfig.mockClear(); + + await formatLocalStatusSummary({ channels: { "token-only": { enabled: true } } }); + + expect(listReadOnlyChannelPluginsForConfig).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + includeSetupFallbackPlugins: true, + }), + ); + }); + it("shows configured-but-unavailable credentials distinctly from not configured", async () => { registerSingleTestPlugin("token-only", makeUnavailableTokenPlugin()); diff --git a/src/commands/channels/status-config-format.ts b/src/commands/channels/status-config-format.ts index 0d8133e274d..826dbe29ba8 100644 --- a/src/commands/channels/status-config-format.ts +++ b/src/commands/channels/status-config-format.ts @@ -60,7 +60,7 @@ export async function formatConfigChannelsStatusLines( const sourceConfig = opts?.sourceConfig ?? cfg; const plugins = listReadOnlyChannelPluginsForConfig(cfg, { activationSourceConfig: sourceConfig, - includeSetupFallbackPlugins: false, + includeSetupFallbackPlugins: true, }); for (const plugin of plugins) { const accountIds = plugin.config.listAccountIds(cfg); diff --git a/src/commands/status.scan.test.ts b/src/commands/status.scan.test.ts index 407ad25ed65..6751e69c6a1 100644 --- a/src/commands/status.scan.test.ts +++ b/src/commands/status.scan.test.ts @@ -90,13 +90,13 @@ describe("scanStatus", () => { expect(mocks.buildChannelsTable).toHaveBeenCalledWith( expect.objectContaining({ marker: "resolved" }), expect.objectContaining({ - includeSetupFallbackPlugins: false, + includeSetupFallbackPlugins: true, sourceConfig: expect.objectContaining({ marker: "source" }), }), ); }); - it("keeps default text status off live channel status and setup runtime fallback", async () => { + it("keeps default text status off live channel status while keeping configured channel setup fallback", async () => { configureScanStatus({ hasConfiguredChannels: true }); mocks.probeGateway.mockResolvedValue({ ok: true, @@ -117,7 +117,7 @@ describe("scanStatus", () => { ); expect(mocks.buildChannelsTable).toHaveBeenCalledWith( expect.any(Object), - expect.objectContaining({ includeSetupFallbackPlugins: false }), + expect.objectContaining({ includeSetupFallbackPlugins: true }), ); }); diff --git a/src/commands/status.scan.ts b/src/commands/status.scan.ts index abf1bee2829..46321ee05ee 100644 --- a/src/commands/status.scan.ts +++ b/src/commands/status.scan.ts @@ -53,7 +53,7 @@ export async function scanStatus( opts, showSecrets: process.env.OPENCLAW_SHOW_SECRETS?.trim() !== "0", includeLiveChannelStatus: includeLiveChannelChecks, - includeChannelSetupRuntimeFallback: includeLiveChannelChecks, + includeChannelSetupRuntimeFallback: true, progress, labels: { loadingConfig: "Loading config…",