diff --git a/src/commands/configure.wizard.test.ts b/src/commands/configure.wizard.test.ts index c1e42d7fcd4..ee6fd74c38d 100644 --- a/src/commands/configure.wizard.test.ts +++ b/src/commands/configure.wizard.test.ts @@ -237,7 +237,7 @@ describe("runConfigureWizard", () => { mocks.clackOutro.mockResolvedValue(undefined); mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(true); mocks.clackSelect.mockImplementation(async (params: { message: string }) => { - if (params.message === "Choose web search provider") { + if (params.message === "Choose active web search provider") { return "tavily"; } if (params.message.startsWith("Search depth")) { @@ -317,7 +317,7 @@ describe("runConfigureWizard", () => { mocks.clackOutro.mockResolvedValue(undefined); mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(false); mocks.clackSelect.mockImplementation(async (params: { message: string }) => { - if (params.message === "Choose web search provider") { + if (params.message === "Choose active web search provider") { return "brave"; } return "__continue"; @@ -411,7 +411,7 @@ describe("runConfigureWizard", () => { mocks.clackOutro.mockResolvedValue(undefined); mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(true); mocks.clackSelect.mockImplementation(async (params: { message: string }) => { - if (params.message === "Choose web search provider") { + if (params.message === "Choose active web search provider") { return "tavily"; } if (params.message.startsWith("Search depth")) { @@ -571,7 +571,7 @@ describe("runConfigureWizard", () => { mocks.clackOutro.mockResolvedValue(undefined); mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(true); mocks.clackSelect.mockImplementation(async (params: { message: string }) => { - if (params.message === "Choose web search provider") { + if (params.message === "Choose active web search provider") { return "tavily"; } if (params.message.startsWith("Search depth")) { @@ -784,7 +784,7 @@ describe("runConfigureWizard", () => { mocks.clackConfirm.mockResolvedValueOnce(true).mockResolvedValueOnce(true); mocks.clackSelect.mockImplementation( async (params: { message: string; options?: Array<{ value: string; hint?: string }> }) => { - if (params.message === "Choose web search provider") { + if (params.message === "Choose active web search provider") { expect(params.options?.[0]).toMatchObject({ value: "tavily", hint: "Plugin search ยท External plugin", diff --git a/src/commands/onboard-search.test.ts b/src/commands/onboard-search.test.ts index 2f56ab54605..3168846960c 100644 --- a/src/commands/onboard-search.test.ts +++ b/src/commands/onboard-search.test.ts @@ -166,7 +166,7 @@ describe("setupSearch", () => { await setupSearch(cfg, runtime, prompter); const providerSelectCall = (prompter.select as ReturnType).mock.calls.find( - (call) => call[0]?.message === "Choose web search provider", + (call) => call[0]?.message === "Choose active web search provider", ); expect(providerSelectCall?.[0]).toEqual( expect.objectContaining({ @@ -225,7 +225,7 @@ describe("setupSearch", () => { await setupSearch(cfg, runtime, prompter); const providerSelectCall = (prompter.select as ReturnType).mock.calls.find( - (call) => call[0]?.message === "Choose web search provider", + (call) => call[0]?.message === "Choose active web search provider", ); expect(providerSelectCall?.[0]).toEqual( expect.objectContaining({ @@ -294,7 +294,7 @@ describe("setupSearch", () => { await setupSearch(cfg, runtime, prompter); const providerSelectCall = (prompter.select as ReturnType).mock.calls.find( - (call) => call[0]?.message === "Choose web search provider", + (call) => call[0]?.message === "Choose active web search provider", ); const matchingOptions = providerSelectCall?.[0]?.options?.filter( @@ -359,7 +359,7 @@ describe("setupSearch", () => { expect(prompter.select).toHaveBeenCalledWith( expect.objectContaining({ - message: "Choose web search provider", + message: "Choose active web search provider", options: expect.arrayContaining([ expect.objectContaining({ value: "__install_plugin__", @@ -462,7 +462,7 @@ describe("setupSearch", () => { await setupSearch(cfg, runtime, prompter); const options = (prompter.select as ReturnType).mock.calls.find( - (call) => call[0]?.message === "Choose web search provider", + (call) => call[0]?.message === "Choose active web search provider", )?.[0]?.options; expect(options[0]).toMatchObject({ value: "tavily", @@ -528,7 +528,7 @@ describe("setupSearch", () => { await setupSearch(cfg, runtime, prompter); const configurePickerCall = (prompter.select as ReturnType).mock.calls.find( - (call) => call[0]?.message === "Choose web search provider", + (call) => call[0]?.message === "Choose active web search provider", ); expect(configurePickerCall?.[0]).toEqual( expect.objectContaining({ diff --git a/src/commands/onboard-search.ts b/src/commands/onboard-search.ts index 6c4839ce0a1..823669d5811 100644 --- a/src/commands/onboard-search.ts +++ b/src/commands/onboard-search.ts @@ -30,6 +30,7 @@ import { } from "./onboarding/plugin-install.js"; import { buildProviderSelectionOptions, + promptProviderManagementIntent, type ProviderManagementIntent, } from "./provider-management.js"; import { @@ -42,6 +43,8 @@ export type SearchProvider = string; const SEARCH_PROVIDER_INSTALL_SENTINEL = "__install_plugin__" as const; const SEARCH_PROVIDER_KEEP_CURRENT_SENTINEL = "__keep_current__" as const; const SEARCH_PROVIDER_SKIP_SENTINEL = "__skip__" as const; +const SEARCH_PROVIDER_CONFIGURE_SENTINEL = "__configure_provider__" as const; +const SEARCH_PROVIDER_SWITCH_ACTIVE_SENTINEL = "__switch_active_provider__" as const; type PluginSearchProviderEntry = { kind: "plugin"; @@ -1231,14 +1234,43 @@ export async function promptSearchProviderFlow(params: { includeSkipOption: params.includeSkipOption, skipHint: params.skipHint, }); + const action = await promptProviderManagementIntent({ + prompter: params.prompter, + message: "Web search setup", + includeSkipOption: params.includeSkipOption, + configuredCount: pickerModel.configuredCount, + configureValue: SEARCH_PROVIDER_CONFIGURE_SENTINEL, + switchValue: SEARCH_PROVIDER_SWITCH_ACTIVE_SENTINEL, + skipValue: SEARCH_PROVIDER_SKIP_SENTINEL, + configureLabel: "Configure or install a provider", + configureHint: + "Update keys, plugin settings, or install a provider without changing the active provider", + switchLabel: "Switch active provider", + switchHint: "Change which provider web_search uses right now", + skipHint: params.skipHint ?? "Configure later with openclaw configure --section web", + }); + if (action === SEARCH_PROVIDER_SKIP_SENTINEL) { + return params.config; + } + const intent: SearchProviderFlowIntent = + action === SEARCH_PROVIDER_CONFIGURE_SENTINEL ? "configure-provider" : "switch-active"; const choice = await params.prompter.select({ - message: "Choose web search provider", + message: + intent === "switch-active" + ? "Choose active web search provider" + : "Choose provider to configure", options: buildProviderSelectionOptions({ - intent: "configure-provider", + intent, options: pickerModel.options, activeValue: pickerModel.activeProvider, + hiddenValues: intent === "configure-provider" ? [SEARCH_PROVIDER_KEEP_CURRENT_SENTINEL] : [], }), - initialValue: pickerModel.initialValue, + initialValue: + intent === "switch-active" + ? pickerModel.initialValue + : (pickerModel.options.find( + (option) => option.value !== SEARCH_PROVIDER_KEEP_CURRENT_SENTINEL, + )?.value ?? pickerModel.initialValue), }); if ( @@ -1247,17 +1279,6 @@ export async function promptSearchProviderFlow(params: { ) { return params.config; } - - const selectedEntry = providerEntries.find((entry) => entry.value === choice); - const intent: SearchProviderFlowIntent = - choice === SEARCH_PROVIDER_INSTALL_SENTINEL - ? "configure-provider" - : choice === pickerModel.activeProvider - ? "configure-provider" - : selectedEntry?.configured - ? "switch-active" - : "configure-provider"; - return applySearchProviderChoice({ config: params.config, choice,