From bf13efd818344ba647bc28fa296db00b901517d7 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 24 Jun 2026 09:07:45 +0800 Subject: [PATCH] fix(web): keep agent search runtime-first --- src/agents/tools/web-search.late-bind.test.ts | 17 +++------ .../tools/web-tool-runtime-context.test.ts | 35 ++++++------------- src/agents/tools/web-tool-runtime-context.ts | 14 +++----- 3 files changed, 20 insertions(+), 46 deletions(-) diff --git a/src/agents/tools/web-search.late-bind.test.ts b/src/agents/tools/web-search.late-bind.test.ts index 8f98dc916ad..73a6fd1889b 100644 --- a/src/agents/tools/web-search.late-bind.test.ts +++ b/src/agents/tools/web-search.late-bind.test.ts @@ -35,20 +35,10 @@ type RunWebSearchParams = { }; }; -type ProviderResolutionParams = { - value?: string; -}; - function firstRunWebSearchParams(): RunWebSearchParams | undefined { return mocks.runWebSearch.mock.calls[0]?.[0] as RunWebSearchParams | undefined; } -function firstProviderResolutionParams(): ProviderResolutionParams | undefined { - return mocks.resolveManifestContractOwnerPluginId.mock.calls[0]?.[0] as - | ProviderResolutionParams - | undefined; -} - describe("web_search late-bound runtime fallback", () => { beforeEach(() => { mocks.runWebSearch.mockReset(); @@ -106,7 +96,7 @@ describe("web_search late-bound runtime fallback", () => { await tool?.execute("call-search", { query: "openclaw" }, undefined); - expect(firstProviderResolutionParams()?.value).toBe("brave"); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); expect(firstRunWebSearchParams()?.preferRuntimeProviders).toBe(true); }); @@ -122,7 +112,7 @@ describe("web_search late-bound runtime fallback", () => { expect(firstRunWebSearchParams()?.preferRuntimeProviders).toBe(true); }); - it("does not prefer runtime providers when the configured provider is a bundled manifest owner", async () => { + it("keeps runtime provider discovery enabled when configured search provider has a manifest owner", async () => { mocks.resolveManifestContractOwnerPluginId.mockReturnValue("openclaw-bundled-brave"); const config = { tools: { web: { search: { provider: "brave" } } }, @@ -134,7 +124,8 @@ describe("web_search late-bound runtime fallback", () => { await tool?.execute("call-search", { query: "openclaw" }, undefined); - expect(firstRunWebSearchParams()?.preferRuntimeProviders).toBe(false); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); + expect(firstRunWebSearchParams()?.preferRuntimeProviders).toBe(true); }); it("prefers active runtime metadata over options.runtimeWebSearch when present", async () => { diff --git a/src/agents/tools/web-tool-runtime-context.test.ts b/src/agents/tools/web-tool-runtime-context.test.ts index 3d3ff476ee1..3c488a574f0 100644 --- a/src/agents/tools/web-tool-runtime-context.test.ts +++ b/src/agents/tools/web-tool-runtime-context.test.ts @@ -78,11 +78,8 @@ describe("web tool runtime context", () => { expect(resolved.config).toBe(runtimeConfig); expect(resolved.runtimeWebSearch?.selectedProvider).toBe("perplexity"); - const ownerLookup = latestOwnerLookupParams(); - expect(ownerLookup.contract).toBe("webSearchProviders"); - expect(ownerLookup.value).toBe("perplexity"); - expect(ownerLookup).not.toHaveProperty("origin"); - expect(ownerLookup.config).toBe(runtimeConfig); + expect(resolved.preferRuntimeProviders).toBe(true); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); }); it("falls back to captured search config and runtime metadata when active globals are missing", async () => { @@ -104,28 +101,20 @@ describe("web tool runtime context", () => { expect(resolved.config).toBe(capturedConfig); expect(resolved.runtimeWebSearch?.selectedProvider).toBe("brave"); - const ownerLookup = latestOwnerLookupParams(); - expect(ownerLookup.contract).toBe("webSearchProviders"); - expect(ownerLookup.value).toBe("brave"); - expect(ownerLookup).not.toHaveProperty("origin"); - expect(ownerLookup.config).toBe(capturedConfig); + expect(resolved.preferRuntimeProviders).toBe(true); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); }); - it("uses configured provider ids when runtime metadata is absent", () => { - resolveWebSearchToolRuntimeContext({ + it("keeps search runtime discovery enabled when runtime metadata is absent", () => { + const resolved = resolveWebSearchToolRuntimeContext({ config: { tools: { web: { search: { provider: "Brave" } } } }, }); - const ownerLookup = latestOwnerLookupParams(); - expect(ownerLookup.contract).toBe("webSearchProviders"); - expect(ownerLookup.value).toBe("brave"); - expect(ownerLookup).not.toHaveProperty("origin"); - expect(ownerLookup.config).toEqual({ - tools: { web: { search: { provider: "Brave" } } }, - }); + expect(resolved.preferRuntimeProviders).toBe(true); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); }); - it("treats resolved global provider owners as explicit selections", async () => { + it("keeps search runtime discovery enabled for manifest-owned configured providers", async () => { mocks.resolveManifestContractOwnerPluginId.mockReturnValue("brave"); const { resolveWebSearchToolRuntimeContext: resolveWebSearchToolRuntimeContextLocal } = await import("./web-tool-runtime-context.js"); @@ -134,10 +123,8 @@ describe("web tool runtime context", () => { config: { tools: { web: { search: { provider: "brave" } } } }, }); - expect(resolved.preferRuntimeProviders).toBe(false); - expect(mocks.resolveManifestContractOwnerPluginId.mock.calls.at(-1)?.[0]).not.toHaveProperty( - "origin", - ); + expect(resolved.preferRuntimeProviders).toBe(true); + expect(mocks.resolveManifestContractOwnerPluginId).not.toHaveBeenCalled(); }); it("keeps runtime providers disabled for bundled fetch owners", async () => { diff --git a/src/agents/tools/web-tool-runtime-context.ts b/src/agents/tools/web-tool-runtime-context.ts index 278baf94ef0..0237782ab57 100644 --- a/src/agents/tools/web-tool-runtime-context.ts +++ b/src/agents/tools/web-tool-runtime-context.ts @@ -16,8 +16,6 @@ type WebProviderKind = "fetch" | "search"; type WebProviderRuntimeMetadata = RuntimeWebFetchMetadata | RuntimeWebSearchMetadata; -type WebProviderContract = "webFetchProviders" | "webSearchProviders"; - type ResolvedWebToolRuntimeContext = { config?: OpenClawConfig; preferRuntimeProviders: boolean; @@ -36,23 +34,21 @@ function resolveRuntimeWebProviderId(metadata: WebProviderRuntimeMetadata | unde return metadata?.selectedProvider ?? metadata?.providerConfigured ?? ""; } -function resolveWebProviderContract(kind: WebProviderKind): WebProviderContract { - return kind === "fetch" ? "webFetchProviders" : "webSearchProviders"; -} - function shouldPreferRuntimeProviders(params: { config?: OpenClawConfig; kind: WebProviderKind; providerSelectionId: string; }): boolean { - if (!params.providerSelectionId) { + // Agent-side web_search must use the live runtime registry; runWebSearch + // applies manifest ownership only as a load-scope hint after that. + if (!params.providerSelectionId || params.kind === "search") { return true; } // Built-in providers are handled by core; plugin-owned selections should route through plugins. return !resolveManifestContractOwnerPluginId({ - contract: resolveWebProviderContract(params.kind), + contract: "webFetchProviders", value: params.providerSelectionId, - ...(params.kind === "fetch" ? { origin: "bundled" as const } : {}), + origin: "bundled", config: params.config, }); }