fix(web-search): restore SecretRef runtime compatibility for bundled providers (#68424)

Adds missing compatibility runtime path metadata for bundled SecretRef-capable web-search providers and keeps the manifest registry covered by a regression test.\n\nThanks @afurm!
This commit is contained in:
Andrii Furmanets
2026-04-21 04:34:24 +03:00
committed by GitHub
parent f04185cc70
commit b6a8759b29
9 changed files with 69 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram/status reactions: honor `messages.removeAckAfterReply` when lifecycle status reactions are enabled, clearing or restoring the reaction after success/error using the configured hold timings. (#68067) Thanks @poiskgit.
- Web search/plugins: resolve plugin-scoped SecretRef API keys for bundled Exa, Firecrawl, Gemini, Kimi, Perplexity, Tavily, and Grok web-search providers when they are selected through the shared web-search config. (#68424) Thanks @afurm.
- Telegram/polling: raise the default polling watchdog threshold from 90s to 120s and add configurable `channels.telegram.pollingStallThresholdMs` (also per-account) so long-running Telegram work gets more room before polling is treated as stalled. (#57737) Thanks @Vitalcheffe.
- Telegram/polling: bound the persisted-offset confirmation `getUpdates` probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.
- Agents/Pi runner: retry silent `stopReason=error` turns with no output when no side effects ran, so non-frontier providers that briefly return empty error turns get another chance instead of ending the session early. (#68310) Thanks @Chased1k.

View File

@@ -14,6 +14,9 @@
"contracts": {
"webSearchProviders": ["exa"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -30,6 +30,9 @@
"webSearchProviders": ["firecrawl"],
"tools": ["firecrawl_search", "firecrawl_scrape"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -53,6 +53,9 @@
"videoGenerationProviders": ["google"],
"webSearchProviders": ["gemini"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -52,6 +52,9 @@
"mediaUnderstandingProviders": ["moonshot"],
"webSearchProviders": ["kimi"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -22,6 +22,9 @@
"contracts": {
"webSearchProviders": ["perplexity"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -20,6 +20,9 @@
"webSearchProviders": ["tavily"],
"tools": ["tavily_search", "tavily_extract"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -87,6 +87,9 @@
"videoGenerationProviders": ["xai"],
"tools": ["code_execution", "x_search"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@@ -1,7 +1,9 @@
import { describe, expect, it } from "vitest";
import {
loadPluginManifestRegistry,
resolveManifestContractOwnerPluginId,
resolveManifestContractPluginIds,
resolveManifestContractPluginIdsByCompatibilityRuntimePath,
} from "./manifest-registry.js";
import {
hasBundledWebFetchProviderPublicArtifact,
@@ -9,6 +11,29 @@ import {
resolveBundledExplicitWebSearchProvidersFromPublicArtifacts,
} from "./web-provider-public-artifacts.explicit.js";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function supportsSecretRefWebSearchApiKey(
plugin: ReturnType<typeof loadPluginManifestRegistry>["plugins"][number],
): boolean {
const configProperties = isRecord(plugin.configSchema?.["properties"])
? plugin.configSchema["properties"]
: undefined;
const webSearch = configProperties?.["webSearch"];
if (!isRecord(webSearch)) {
return false;
}
const properties = isRecord(webSearch["properties"]) ? webSearch["properties"] : undefined;
const apiKey = properties?.["apiKey"];
if (!isRecord(apiKey)) {
return false;
}
const typeValue = apiKey["type"];
return Array.isArray(typeValue) && typeValue.includes("object");
}
describe("web provider public artifacts", () => {
it("has a public artifact for every bundled web search provider declared in manifests", () => {
const pluginIds = resolveManifestContractPluginIds({
@@ -44,6 +69,28 @@ describe("web provider public artifacts", () => {
}
});
it("registers compatibility runtime paths for bundled SecretRef-capable web search providers", () => {
const registry = loadPluginManifestRegistry({ cache: false });
const expectedPluginIds = registry.plugins
.filter(
(plugin) =>
plugin.origin === "bundled" &&
(plugin.contracts?.webSearchProviders?.length ?? 0) > 0 &&
supportsSecretRefWebSearchApiKey(plugin),
)
.map((plugin) => plugin.id)
.toSorted((left, right) => left.localeCompare(right));
expect(expectedPluginIds).not.toHaveLength(0);
expect(
resolveManifestContractPluginIdsByCompatibilityRuntimePath({
contract: "webSearchProviders",
path: "tools.web.search.apiKey",
origin: "bundled",
}),
).toEqual(expectedPluginIds);
});
it("has a public artifact for every bundled web fetch provider declared in manifests", () => {
const pluginIds = resolveManifestContractPluginIds({
contract: "webFetchProviders",