From 89960cfcc9bccf914af080ae99d89fe50b98d8a9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 10 May 2026 14:20:55 +0800 Subject: [PATCH] fix(channels): keep guided add quiet before selection --- CHANGELOG.md | 1 + src/commands/channels.add.test.ts | 61 +++++++++++++++++++++++++++++++ src/commands/channels/add.ts | 2 + 3 files changed, 64 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0060e2e24e..23ccf79534d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai - CLI/config: explain strict JSON parse failures with a valid example and the plain-string escape hatch. - CLI/secrets: turn offline Gateway reload failures into actionable recovery text. - CLI/channels: explain missing or ambiguous channel selections with next commands. +- CLI/channels: defer guided channel status collection until a channel is selected, keeping `openclaw channels add` first screen quieter. - Browser/Docker: detect Playwright-managed Chromium from `PLAYWRIGHT_BROWSERS_PATH` and the default Playwright cache on Linux, so Docker installs that persist `/home/node/.cache/ms-playwright` no longer need `browser.executablePath`. - Ollama: keep DeepSeek V4 cloud models thinking-capable even when Ollama Cloud `/api/show` omits the `thinking` capability, so `/think high` no longer rejects `ollama/deepseek-v4-*:cloud`. - ACPX/Claude ACP: keep foreground prompts waiting for their own result when autonomous task-notification results arrive during the same session, and retarget the patch for Claude Agent ACP `0.33.1`. diff --git a/src/commands/channels.add.test.ts b/src/commands/channels.add.test.ts index 47b06988afe..e242e842ab0 100644 --- a/src/commands/channels.add.test.ts +++ b/src/commands/channels.add.test.ts @@ -42,6 +42,21 @@ const pluginInstallRecordCommitMocks = vi.hoisted(() => ({ commitConfigWithPendingPluginInstalls: vi.fn(), })); +const channelWizardMocks = vi.hoisted(() => { + const prompter = { + intro: vi.fn(async () => undefined), + outro: vi.fn(async () => undefined), + confirm: vi.fn(async () => false), + note: vi.fn(async () => undefined), + select: vi.fn(), + text: vi.fn(), + }; + return { + prompter, + setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg), + }; +}); + const bundledMocks = vi.hoisted(() => ({ getBundledChannelPlugin: vi.fn(() => undefined), getBundledChannelSetupPlugin: vi.fn(() => undefined), @@ -73,6 +88,19 @@ vi.mock("../cli/plugins-registry-refresh.js", () => registryRefreshMocks); vi.mock("../cli/plugins-install-record-commit.js", () => pluginInstallRecordCommitMocks); +vi.mock("../wizard/clack-prompter.js", () => ({ + createClackPrompter: () => channelWizardMocks.prompter, +})); + +vi.mock("./onboard-channels.js", async () => { + const actual = + await vi.importActual("./onboard-channels.js"); + return { + ...actual, + setupChannels: (...args: unknown[]) => channelWizardMocks.setupChannels(...args), + }; +}); + const runtime = createTestRuntime(); function listConfiguredAccountIds( @@ -309,9 +337,42 @@ describe("channelsAddCommand", () => { createTestRegistry(), ); registryRefreshMocks.refreshPluginRegistryAfterConfigMutation.mockClear(); + channelWizardMocks.prompter.intro.mockClear(); + channelWizardMocks.prompter.outro.mockClear(); + channelWizardMocks.prompter.confirm.mockClear(); + channelWizardMocks.prompter.note.mockClear(); + channelWizardMocks.prompter.select.mockClear(); + channelWizardMocks.prompter.text.mockClear(); + channelWizardMocks.setupChannels.mockClear(); + channelWizardMocks.setupChannels.mockImplementation(async (cfg: OpenClawConfig) => cfg); setMinimalChannelsAddRegistryForTests(); }); + it("keeps guided channel setup lazy until the user selects a channel", async () => { + const config: OpenClawConfig = { channels: {} }; + configMocks.readConfigFileSnapshot.mockResolvedValue({ + ...baseConfigSnapshot, + sourceConfig: config, + config, + }); + + await channelsAddCommand({}, runtime, { hasFlags: false }); + + expect(channelWizardMocks.prompter.intro).toHaveBeenCalledWith("Channel setup"); + expect(channelWizardMocks.setupChannels).toHaveBeenCalledWith( + config, + runtime, + channelWizardMocks.prompter, + expect.objectContaining({ + deferStatusUntilSelection: true, + skipStatusNote: true, + promptAccountIds: true, + }), + ); + expect(configMocks.writeConfigFile).not.toHaveBeenCalled(); + expect(channelWizardMocks.prompter.outro).toHaveBeenCalledWith("No channel changes made."); + }); + it("runs channel lifecycle hooks only when account config changes", async () => { configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot, diff --git a/src/commands/channels/add.ts b/src/commands/channels/add.ts index 9e87c280f77..7ee61f817c2 100644 --- a/src/commands/channels/add.ts +++ b/src/commands/channels/add.ts @@ -154,6 +154,8 @@ export async function channelsAddCommand( postWriteHooks.collect(hook); }, promptAccountIds: true, + deferStatusUntilSelection: true, + skipStatusNote: true, onSelection: (value) => { selection = value; },