Web: use config snapshot in finalize

This commit is contained in:
Gustavo Madeira Santana
2026-03-20 12:38:32 -07:00
parent 8cab2c6c9e
commit e1c7d72833
3 changed files with 79 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import type {
WebSearchProviderToolDefinition,
} from "../plugins/types.js";
import { resolveBundledPluginWebSearchProviders } from "../plugins/web-search-providers.js";
import { resolvePluginWebSearchProviders } from "../plugins/web-search-providers.runtime.js";
import { resolveRuntimeWebSearchProviders } from "../plugins/web-search-providers.runtime.js";
import type { RuntimeWebSearchMetadata } from "../secrets/runtime-web-tools.types.js";
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
@@ -88,6 +89,15 @@ export function listWebSearchProviders(params?: {
});
}
export function listConfiguredWebSearchProviders(params?: {
config?: OpenClawConfig;
}): PluginWebSearchProviderEntry[] {
return resolvePluginWebSearchProviders({
config: params?.config,
bundledAllowlistCompat: true,
});
}
export function resolveWebSearchProviderId(params: {
search?: WebSearchConfig;
config?: OpenClawConfig;

View File

@@ -45,7 +45,7 @@ const hasExistingKey = vi.hoisted(() =>
const hasKeyInEnv = vi.hoisted(() =>
vi.fn<(entry: Pick<PluginWebSearchProviderEntry, "envVars">) => boolean>(() => false),
);
const listWebSearchProviders = vi.hoisted(() =>
const listConfiguredWebSearchProviders = vi.hoisted(() =>
vi.fn<(params?: { config?: OpenClawConfig }) => PluginWebSearchProviderEntry[]>(() => []),
);
@@ -92,7 +92,7 @@ vi.mock("../commands/onboard-search.js", () => ({
}));
vi.mock("../web-search/runtime.js", () => ({
listWebSearchProviders,
listConfiguredWebSearchProviders,
}));
vi.mock("../daemon/service.js", () => ({
@@ -186,8 +186,8 @@ describe("finalizeSetupWizard", () => {
hasExistingKey.mockReturnValue(false);
hasKeyInEnv.mockReset();
hasKeyInEnv.mockReturnValue(false);
listWebSearchProviders.mockReset();
listWebSearchProviders.mockReturnValue([]);
listConfiguredWebSearchProviders.mockReset();
listConfiguredWebSearchProviders.mockReturnValue([]);
});
it("resolves gateway password SecretRef for probe and TUI", async () => {
@@ -413,7 +413,7 @@ describe("finalizeSetupWizard", () => {
});
it("only reports legacy auto-detect for runtime-visible providers", async () => {
listWebSearchProviders.mockReturnValue([
listConfiguredWebSearchProviders.mockReturnValue([
{
id: "perplexity",
label: "Perplexity Search",
@@ -460,4 +460,64 @@ describe("finalizeSetupWizard", () => {
"Web search",
);
});
it("uses configured provider resolution instead of the active runtime registry", async () => {
listConfiguredWebSearchProviders.mockReturnValue([
{
id: "firecrawl",
label: "Firecrawl Search",
hint: "Structured results",
envVars: ["FIRECRAWL_API_KEY"],
placeholder: "fc-...",
signupUrl: "https://www.firecrawl.dev/",
credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey",
},
]);
hasExistingKey.mockImplementation((_config, provider) => provider === "firecrawl");
const prompter = buildWizardPrompter({
select: vi.fn(async () => "later") as never,
confirm: vi.fn(async () => false),
});
await finalizeSetupWizard({
flow: "advanced",
opts: {
acceptRisk: true,
authChoice: "skip",
installDaemon: false,
skipHealth: true,
skipUi: true,
},
baseConfig: {},
nextConfig: {
tools: {
web: {
search: {
provider: "firecrawl",
enabled: true,
},
},
},
},
workspaceDir: "/tmp",
settings: {
port: 18789,
bind: "loopback",
authMode: "token",
gatewayToken: undefined,
tailscaleMode: "off",
tailscaleResetOnExit: false,
},
prompter,
runtime: createRuntime(),
});
expect(prompter.note).toHaveBeenCalledWith(
expect.stringContaining(
"Web search is enabled, so your agent can look things up online when needed.",
),
"Web search",
);
});
});

View File

@@ -30,7 +30,7 @@ import type { RuntimeEnv } from "../runtime.js";
import { restoreTerminalState } from "../terminal/restore.js";
import { runTui } from "../tui/tui.js";
import { resolveUserPath } from "../utils.js";
import { listWebSearchProviders } from "../web-search/runtime.js";
import { listConfiguredWebSearchProviders } from "../web-search/runtime.js";
import type { WizardPrompter } from "./prompts.js";
import { setupWizardShellCompletion } from "./setup.completion.js";
import { resolveSetupSecretInputString } from "./setup.secret-input.js";
@@ -484,11 +484,11 @@ export async function finalizeSetupWizard(
const webSearchProvider = nextConfig.tools?.web?.search?.provider;
const webSearchEnabled = nextConfig.tools?.web?.search?.enabled;
const runtimeSearchProviders = listWebSearchProviders({ config: nextConfig });
const configuredSearchProviders = listConfiguredWebSearchProviders({ config: nextConfig });
if (webSearchProvider) {
const { resolveExistingKey, hasExistingKey, hasKeyInEnv } =
await import("../commands/onboard-search.js");
const entry = runtimeSearchProviders.find((e) => e.id === webSearchProvider);
const entry = configuredSearchProviders.find((e) => e.id === webSearchProvider);
const label = entry?.label ?? webSearchProvider;
const storedKey = entry ? resolveExistingKey(nextConfig, webSearchProvider) : undefined;
const keyConfigured = entry ? hasExistingKey(nextConfig, webSearchProvider) : false;
@@ -550,7 +550,7 @@ export async function finalizeSetupWizard(
// Legacy configs may have a working key (e.g. apiKey or BRAVE_API_KEY) without
// an explicit provider. Runtime auto-detects these, so avoid saying "skipped".
const { hasExistingKey, hasKeyInEnv } = await import("../commands/onboard-search.js");
const legacyDetected = runtimeSearchProviders.find(
const legacyDetected = configuredSearchProviders.find(
(e) => hasExistingKey(nextConfig, e.id) || hasKeyInEnv(e),
);
if (legacyDetected) {