Skip to content

Commit e7f379c

Browse files
committed
fix(agents): keep web_search runtime providers visible
1 parent 68b5610 commit e7f379c

3 files changed

Lines changed: 78 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
4545
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola.
4646
- CLI/logs: announce `--follow` recovery with a `[logs] gateway reconnected` notice once a poll succeeds after a transient outage, and emit JSON `notice` records in `--json` mode for both the retry warning and the reconnect transition, so live monitoring scripts can react to the recovery. Carries forward #75059. (#75372) Thanks @romneyda.
4747
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
48+
- Agents/web_search: keep installed runtime provider discovery enabled when web-search metadata is missing, so externally installed official providers such as Brave remain visible to agent and cron turns instead of falling back to bundled-only lookup. Fixes #76626. Thanks @amknight.
4849
- Tests/plugins: expose the Discord npm onboarding Docker lane as a package script and assert planned Docker lanes point at real scripts, so external-channel onboarding coverage can actually run. Thanks @vincentkoc.
4950
- Plugins/ClawHub: explain unreleased ClawHub plugin artifacts as a rollout-state fallback to `npm:` installs instead of leaking raw archive metadata fields. Thanks @vincentkoc.
5051
- Tests/onboarding: assert packaged channel onboarding leaves `openclaw channels status --json` and plain `openclaw status` showing the configured channel, covering the empty Channels table regression path. Thanks @vincentkoc.

src/agents/tools/web-search.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function createWebSearchTool(options?: {
9898
? (getActiveSecretsRuntimeSnapshot()?.config ?? options?.config)
9999
: options?.config;
100100
const preferRuntimeProviders =
101-
Boolean(runtimeProviderId) &&
101+
!runtimeProviderId ||
102102
!resolveManifestContractOwnerPluginId({
103103
contract: "webSearchProviders",
104104
value: runtimeProviderId,

src/agents/tools/web-tools.enabled-defaults.test.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,37 @@ import {
88
import { createWebFetchTool, createWebSearchTool } from "./web-tools.js";
99

1010
const runWebSearchCalls = vi.hoisted(
11-
() => [] as Array<{ config?: unknown; runtimeWebSearch?: unknown }>,
11+
() =>
12+
[] as Array<{
13+
config?: unknown;
14+
preferRuntimeProviders?: boolean;
15+
runtimeWebSearch?: unknown;
16+
}>,
1217
);
1318
const activeSecretsRuntimeSnapshot = vi.hoisted(() => ({
1419
current: null as null | { config: unknown },
1520
}));
1621

22+
function readConfiguredSearchProvider(config: unknown): string | undefined {
23+
if (!config || typeof config !== "object") {
24+
return undefined;
25+
}
26+
const tools = (config as { tools?: unknown }).tools;
27+
if (!tools || typeof tools !== "object") {
28+
return undefined;
29+
}
30+
const web = (tools as { web?: unknown }).web;
31+
if (!web || typeof web !== "object") {
32+
return undefined;
33+
}
34+
const search = (web as { search?: unknown }).search;
35+
if (!search || typeof search !== "object") {
36+
return undefined;
37+
}
38+
const provider = (search as { provider?: unknown }).provider;
39+
return typeof provider === "string" ? provider : undefined;
40+
}
41+
1742
vi.mock("../../secrets/runtime.js", () => ({
1843
getActiveSecretsRuntimeSnapshot: () => activeSecretsRuntimeSnapshot.current,
1944
}));
@@ -30,7 +55,8 @@ vi.mock("../../web-search/runtime.js", async () => {
3055
options?.runtimeWebSearch?.selectedProvider ??
3156
options?.runtimeWebSearch?.providerConfigured ??
3257
getActiveRuntimeWebToolsMetadata()?.search?.selectedProvider ??
33-
getActiveRuntimeWebToolsMetadata()?.search?.providerConfigured;
58+
getActiveRuntimeWebToolsMetadata()?.search?.providerConfigured ??
59+
readConfiguredSearchProvider(options?.config);
3460
const registration = getActivePluginRegistry()?.webSearchProviders.find(
3561
(entry) => entry.provider.id === providerId,
3662
);
@@ -54,10 +80,12 @@ vi.mock("../../web-search/runtime.js", async () => {
5480
runWebSearch: async (options: {
5581
config?: unknown;
5682
args: Record<string, unknown>;
83+
preferRuntimeProviders?: boolean;
5784
runtimeWebSearch?: unknown;
5885
}) => {
5986
runWebSearchCalls.push({
6087
config: options.config,
88+
preferRuntimeProviders: options.preferRuntimeProviders,
6189
runtimeWebSearch: options.runtimeWebSearch,
6290
});
6391
const resolved = resolveRuntimeDefinition(options as never);
@@ -142,6 +170,52 @@ describe("web tools defaults", () => {
142170
expect(result?.details).toMatchObject({ ok: true });
143171
});
144172

173+
it("keeps runtime provider discovery enabled when runtime web_search metadata is missing", async () => {
174+
const registry = createEmptyPluginRegistry();
175+
registry.webSearchProviders.push({
176+
pluginId: "custom-search",
177+
pluginName: "Custom Search",
178+
source: "test",
179+
provider: {
180+
id: "custom",
181+
label: "Custom Search",
182+
hint: "Custom runtime provider",
183+
envVars: ["CUSTOM_SEARCH_API_KEY"],
184+
placeholder: "custom-...",
185+
signupUrl: "https://example.com/signup",
186+
autoDetectOrder: 1,
187+
credentialPath: "plugins.entries.custom-search.config.webSearch.apiKey",
188+
getCredentialValue: () => "configured",
189+
setCredentialValue: () => {},
190+
createTool: () => ({
191+
description: "custom runtime tool",
192+
parameters: {},
193+
execute: async () => ({ provider: "custom" }),
194+
}),
195+
},
196+
});
197+
setActivePluginRegistry(registry);
198+
199+
const tool = createWebSearchTool({
200+
config: {
201+
tools: {
202+
web: {
203+
search: {
204+
provider: "custom",
205+
},
206+
},
207+
},
208+
},
209+
sandboxed: true,
210+
});
211+
212+
const result = await tool?.execute?.("call-runtime-provider-without-metadata", {});
213+
214+
expect(result?.details).toMatchObject({ provider: "custom" });
215+
expect(runWebSearchCalls).toHaveLength(1);
216+
expect(runWebSearchCalls[0]?.preferRuntimeProviders).toBe(true);
217+
});
218+
145219
it("late-binds managed web_search execution to the current runtime snapshot", async () => {
146220
const registry = createEmptyPluginRegistry();
147221
registry.webSearchProviders.push(

0 commit comments

Comments
 (0)