Compare commits

...

1 Commits

Author SHA1 Message Date
Vincent Koc
d673578d88 fix(web): resolve explicit global search providers 2026-05-14 13:44:02 +08:00
5 changed files with 52 additions and 10 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- CLI tables: preserve muted/color styling on wrapped continuation lines after multiline cells, keeping `openclaw plugins list` descriptions readable.
- Web: honor explicitly configured global `web_search` providers during provider ownership resolution while keeping sandboxed `web_fetch` limited to bundled providers.
- iOS: restore first-use Contacts, Calendar, and Reminders permission prompts and add Privacy & Access status/actions in Settings. Thanks @BunsDev.
- Canvas: return not found for malformed percent-encoded Canvas/A2UI/document asset paths and keep decoded parent traversal blocked before path normalization.
- Telegram: allow trusted local Bot API media files whose filenames start with dots instead of falling back to remote download.

View File

@@ -77,7 +77,7 @@ describe("web tool runtime context", () => {
const ownerLookup = latestOwnerLookupParams();
expect(ownerLookup.contract).toBe("webSearchProviders");
expect(ownerLookup.value).toBe("perplexity");
expect(ownerLookup.origin).toBe("bundled");
expect(ownerLookup).not.toHaveProperty("origin");
expect(ownerLookup.config).toBe(runtimeConfig);
});
@@ -103,7 +103,7 @@ describe("web tool runtime context", () => {
const ownerLookup = latestOwnerLookupParams();
expect(ownerLookup.contract).toBe("webSearchProviders");
expect(ownerLookup.value).toBe("brave");
expect(ownerLookup.origin).toBe("bundled");
expect(ownerLookup).not.toHaveProperty("origin");
expect(ownerLookup.config).toBe(capturedConfig);
});
@@ -115,12 +115,26 @@ describe("web tool runtime context", () => {
const ownerLookup = latestOwnerLookupParams();
expect(ownerLookup.contract).toBe("webSearchProviders");
expect(ownerLookup.value).toBe("brave");
expect(ownerLookup.origin).toBe("bundled");
expect(ownerLookup).not.toHaveProperty("origin");
expect(ownerLookup.config).toEqual({
tools: { web: { search: { provider: "Brave" } } },
});
});
it("treats resolved global provider owners as explicit selections", async () => {
mocks.resolveManifestContractOwnerPluginId.mockReturnValue("brave");
const { resolveWebSearchToolRuntimeContext } = await import("./web-tool-runtime-context.js");
const resolved = resolveWebSearchToolRuntimeContext({
config: { tools: { web: { search: { provider: "brave" } } } },
});
expect(resolved.preferRuntimeProviders).toBe(false);
expect(mocks.resolveManifestContractOwnerPluginId.mock.calls.at(-1)?.[0]).not.toHaveProperty(
"origin",
);
});
it("keeps runtime providers disabled for bundled fetch owners", async () => {
mocks.resolveManifestContractOwnerPluginId.mockReturnValue("firecrawl");
@@ -132,7 +146,7 @@ describe("web tool runtime context", () => {
const ownerLookup = latestOwnerLookupParams();
expect(ownerLookup.contract).toBe("webFetchProviders");
expect(ownerLookup.value).toBe("firecrawl");
expect(ownerLookup.origin).toBe("bundled");
expect(ownerLookup).not.toHaveProperty("origin");
expect(ownerLookup.config).toEqual({
tools: { web: { fetch: { provider: "firecrawl" } } },
});

View File

@@ -46,7 +46,6 @@ function shouldPreferRuntimeProviders(params: {
return !resolveManifestContractOwnerPluginId({
contract: resolveWebProviderContract(params.kind),
value: params.providerSelectionId,
origin: "bundled",
config: params.config,
});
}

View File

@@ -595,8 +595,8 @@ describe("web search runtime", () => {
const ownerCall = mockCallParam(resolveManifestContractOwnerPluginIdMock);
expect(ownerCall.contract).toBe("webSearchProviders");
expect(ownerCall.origin).toBe("bundled");
expect(ownerCall.value).toBe("duckduckgo");
expect(ownerCall).not.toHaveProperty("origin");
expect(mockCallParam(resolveRuntimeWebSearchProvidersMock).onlyPluginIds).toEqual([
"duckduckgo",
]);
@@ -628,6 +628,38 @@ describe("web search runtime", () => {
expect(mockCallParam(resolveRuntimeWebSearchProvidersMock).onlyPluginIds).toEqual(["google"]);
});
it("scopes configured global web_search providers when runtime providers are not preferred", async () => {
resolveManifestContractOwnerPluginIdMock.mockImplementation(({ value }) =>
value === "custom" ? "custom-search" : undefined,
);
resolvePluginWebSearchProvidersMock.mockReturnValue([createCustomSearchProvider()]);
await expect(
runWebSearch({
config: {
tools: {
web: {
search: {
provider: "custom",
},
},
},
...createCustomSearchConfig("custom-key"),
},
preferRuntimeProviders: false,
args: { query: "configured-custom" },
}),
).resolves.toMatchObject({
provider: "custom",
});
expect(resolvePluginWebSearchProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
onlyPluginIds: ["custom-search"],
}),
);
});
it("keeps runtime provider loading unscoped when configured provider ownership is unknown", async () => {
resolveManifestContractOwnerPluginIdMock.mockReturnValue(undefined);
resolveRuntimeWebSearchProvidersMock.mockReturnValue([

View File

@@ -147,7 +147,6 @@ export function resolveWebSearchProviderId(params: {
resolvePluginWebSearchProviders({
config,
bundledAllowlistCompat: true,
origin: "bundled",
}),
);
const raw =
@@ -231,7 +230,6 @@ function resolveExplicitWebSearchProviderPluginIds(params: {
config: params.config,
contract: "webSearchProviders",
value: providerId,
origin: "bundled",
});
return ownerPluginId ? [ownerPluginId] : undefined;
}
@@ -270,7 +268,6 @@ export function resolveWebSearchDefinition(
: resolvePluginWebSearchProviders({
config,
bundledAllowlistCompat: true,
origin: "bundled",
...loadScope,
}),
);
@@ -334,7 +331,6 @@ function resolveWebSearchCandidates(
: resolvePluginWebSearchProviders({
config,
bundledAllowlistCompat: true,
origin: "bundled",
...loadScope,
}),
).filter(Boolean);