mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-07 07:58:36 +00:00
fix(runtime): resolve web search SecretRefs from snapshots (#72563)
This commit is contained in:
@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/install: stage bundled plugin runtime dependencies before Gateway startup and drain update restarts while preserving per-plugin isolation when pre-stage scan or install fails. Thanks @codex.
|
||||
- CLI/startup: read generated startup metadata from the bundled `dist` layout before falling back to live help rendering, so root/browser help and channel-option bootstrap stay on the fast path. Thanks @vincentkoc.
|
||||
- CLI/help: treat positional `help` invocations like `openclaw channels help` as help paths for startup gating, avoiding model/auth warmup while preserving positional arguments such as `openclaw docs help`. Thanks @gumadeiras.
|
||||
- Web search: route plugin-scoped web_search SecretRefs through the active runtime config snapshot so provider execution receives resolved credentials across app/runtime paths, including `plugins.entries.brave.config.webSearch.apiKey`. Fixes #68690. Thanks @VACInc.
|
||||
- Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.
|
||||
- Cron: treat isolated run-level agent failures as job errors even when no reply payload is produced, synthesizing a safe error payload so model/provider failures increment error counters and trigger failure notifications instead of clearing as successful. Fixes #43604; carries forward #43631. Thanks @SPFAdvisors.
|
||||
- Cron: preserve exact `NO_REPLY` tool results from isolated jobs with empty final assistant turns as quiet successes instead of surfacing incomplete-turn errors. Fixes #68452; carries forward #68453. Thanks @anyech.
|
||||
|
||||
@@ -185,7 +185,8 @@ error prompting you to configure one).
|
||||
<Note>
|
||||
All provider key fields support SecretRef objects. Plugin-scoped SecretRefs
|
||||
under `plugins.entries.<plugin>.config.webSearch.apiKey` are resolved for the
|
||||
bundled Exa, Firecrawl, Gemini, Grok, Kimi, Perplexity, and Tavily providers
|
||||
bundled API-backed web search providers, including Brave, Exa, Firecrawl,
|
||||
Gemini, Grok, Kimi, MiniMax, Perplexity, and Tavily,
|
||||
whether the provider is picked explicitly via `tools.web.search.provider` or
|
||||
selected through auto-detect. In auto-detect mode, OpenClaw resolves only the
|
||||
selected provider key -- non-selected SecretRefs stay inactive, so you can
|
||||
|
||||
@@ -155,6 +155,60 @@ describe("web search runtime", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the active resolved runtime config for matching source config callers", async () => {
|
||||
const provider = createCustomSearchProvider({
|
||||
createTool: ({ config }) => ({
|
||||
description: "custom",
|
||||
parameters: {},
|
||||
execute: async (args) => ({
|
||||
...args,
|
||||
apiKey: getCustomSearchApiKey(config),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
resolveRuntimeWebSearchProvidersMock.mockReturnValue([provider]);
|
||||
resolvePluginWebSearchProvidersMock.mockReturnValue([provider]);
|
||||
|
||||
const sourceConfig = createCustomSearchConfig({
|
||||
source: "exec",
|
||||
provider: "mockexec",
|
||||
id: "custom-search/api-key",
|
||||
});
|
||||
const resolvedConfig = createCustomSearchConfig("resolved-custom-key");
|
||||
|
||||
activateSecretsRuntimeSnapshot({
|
||||
sourceConfig,
|
||||
config: resolvedConfig,
|
||||
authStores: [],
|
||||
warnings: [],
|
||||
webTools: {
|
||||
search: {
|
||||
providerSource: "auto-detect",
|
||||
selectedProvider: "custom",
|
||||
diagnostics: [],
|
||||
},
|
||||
fetch: {
|
||||
providerSource: "none",
|
||||
diagnostics: [],
|
||||
},
|
||||
diagnostics: [],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
runWebSearch({
|
||||
config: structuredClone(sourceConfig),
|
||||
args: { query: "runtime-source" },
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
provider: "custom",
|
||||
result: {
|
||||
query: "runtime-source",
|
||||
apiKey: "resolved-custom-key",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("treats non-env SecretRefs as configured credentials for provider auto-detect", async () => {
|
||||
const provider = createCustomSearchProvider();
|
||||
resolveRuntimeWebSearchProvidersMock.mockReturnValue([provider]);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
getRuntimeConfigSnapshot,
|
||||
getRuntimeConfigSourceSnapshot,
|
||||
selectApplicableRuntimeConfig,
|
||||
} from "../config/runtime-snapshot.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import type {
|
||||
@@ -41,6 +46,14 @@ function resolveSearchConfig(cfg?: OpenClawConfig): WebSearchConfig {
|
||||
return resolveWebProviderConfig(cfg, "search") as NonNullable<WebSearchConfig> | undefined;
|
||||
}
|
||||
|
||||
function resolveWebSearchRuntimeConfig(config?: OpenClawConfig): OpenClawConfig | undefined {
|
||||
return selectApplicableRuntimeConfig({
|
||||
inputConfig: config,
|
||||
runtimeConfig: getRuntimeConfigSnapshot(),
|
||||
runtimeSourceConfig: getRuntimeConfigSourceSnapshot(),
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveWebSearchEnabled(params: {
|
||||
search?: WebSearchConfig;
|
||||
sandboxed?: boolean;
|
||||
@@ -91,14 +104,16 @@ export function isWebSearchProviderConfigured(params: {
|
||||
>;
|
||||
config?: OpenClawConfig;
|
||||
}): boolean {
|
||||
return hasEntryCredential(params.provider, params.config, resolveSearchConfig(params.config));
|
||||
const config = resolveWebSearchRuntimeConfig(params.config);
|
||||
return hasEntryCredential(params.provider, config, resolveSearchConfig(config));
|
||||
}
|
||||
|
||||
export function listWebSearchProviders(params?: {
|
||||
config?: OpenClawConfig;
|
||||
}): PluginWebSearchProviderEntry[] {
|
||||
const config = resolveWebSearchRuntimeConfig(params?.config);
|
||||
return resolveRuntimeWebSearchProviders({
|
||||
config: params?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
});
|
||||
}
|
||||
@@ -106,8 +121,9 @@ export function listWebSearchProviders(params?: {
|
||||
export function listConfiguredWebSearchProviders(params?: {
|
||||
config?: OpenClawConfig;
|
||||
}): PluginWebSearchProviderEntry[] {
|
||||
const config = resolveWebSearchRuntimeConfig(params?.config);
|
||||
return resolvePluginWebSearchProviders({
|
||||
config: params?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
});
|
||||
}
|
||||
@@ -117,18 +133,18 @@ export function resolveWebSearchProviderId(params: {
|
||||
config?: OpenClawConfig;
|
||||
providers?: PluginWebSearchProviderEntry[];
|
||||
}): string {
|
||||
const config = resolveWebSearchRuntimeConfig(params.config);
|
||||
const search = params.search ?? resolveSearchConfig(config);
|
||||
const providers = sortWebSearchProvidersForAutoDetect(
|
||||
params.providers ??
|
||||
resolvePluginWebSearchProviders({
|
||||
config: params.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
origin: "bundled",
|
||||
}),
|
||||
);
|
||||
const raw =
|
||||
params.search && "provider" in params.search
|
||||
? normalizeLowercaseStringOrEmpty(params.search.provider)
|
||||
: "";
|
||||
search && "provider" in search ? normalizeLowercaseStringOrEmpty(search.provider) : "";
|
||||
|
||||
if (raw) {
|
||||
const explicit = providers.find((provider) => provider.id === raw);
|
||||
@@ -144,7 +160,7 @@ export function resolveWebSearchProviderId(params: {
|
||||
keylessFallbackProviderId ||= provider.id;
|
||||
continue;
|
||||
}
|
||||
if (!hasEntryCredential(provider, params.config, params.search)) {
|
||||
if (!hasEntryCredential(provider, config, search)) {
|
||||
continue;
|
||||
}
|
||||
logVerbose(
|
||||
@@ -166,22 +182,23 @@ export function resolveWebSearchProviderId(params: {
|
||||
export function resolveWebSearchDefinition(
|
||||
options?: ResolveWebSearchDefinitionParams,
|
||||
): { provider: PluginWebSearchProviderEntry; definition: WebSearchProviderToolDefinition } | null {
|
||||
const search = resolveSearchConfig(options?.config);
|
||||
const config = resolveWebSearchRuntimeConfig(options?.config);
|
||||
const search = resolveSearchConfig(config);
|
||||
const runtimeWebSearch = options?.runtimeWebSearch ?? getActiveRuntimeWebToolsMetadata()?.search;
|
||||
const providers = sortWebSearchProvidersForAutoDetect(
|
||||
options?.preferRuntimeProviders
|
||||
? resolveRuntimeWebSearchProviders({
|
||||
config: options?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
})
|
||||
: resolvePluginWebSearchProviders({
|
||||
config: options?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
origin: "bundled",
|
||||
}),
|
||||
);
|
||||
return resolveWebProviderDefinition({
|
||||
config: options?.config,
|
||||
config,
|
||||
toolConfig: search as Record<string, unknown> | undefined,
|
||||
runtimeMetadata: runtimeWebSearch,
|
||||
sandboxed: options?.sandboxed,
|
||||
@@ -216,7 +233,8 @@ export function resolveWebSearchDefinition(
|
||||
function resolveWebSearchCandidates(
|
||||
options?: ResolveWebSearchDefinitionParams,
|
||||
): PluginWebSearchProviderEntry[] {
|
||||
const search = resolveSearchConfig(options?.config);
|
||||
const config = resolveWebSearchRuntimeConfig(options?.config);
|
||||
const search = resolveSearchConfig(config);
|
||||
const runtimeWebSearch = options?.runtimeWebSearch ?? getActiveRuntimeWebToolsMetadata()?.search;
|
||||
if (!resolveWebSearchEnabled({ search, sandboxed: options?.sandboxed })) {
|
||||
return [];
|
||||
@@ -225,11 +243,11 @@ function resolveWebSearchCandidates(
|
||||
const providers = sortWebSearchProvidersForAutoDetect(
|
||||
options?.preferRuntimeProviders
|
||||
? resolveRuntimeWebSearchProviders({
|
||||
config: options?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
})
|
||||
: resolvePluginWebSearchProviders({
|
||||
config: options?.config,
|
||||
config,
|
||||
bundledAllowlistCompat: true,
|
||||
origin: "bundled",
|
||||
}),
|
||||
@@ -242,7 +260,7 @@ function resolveWebSearchCandidates(
|
||||
options?.providerId,
|
||||
runtimeWebSearch?.selectedProvider,
|
||||
runtimeWebSearch?.providerConfigured,
|
||||
resolveWebSearchProviderId({ config: options?.config, search, providers }),
|
||||
resolveWebSearchProviderId({ config, search, providers }),
|
||||
].filter(
|
||||
(value, index, array): value is string => Boolean(value) && array.indexOf(value) === index,
|
||||
);
|
||||
@@ -294,10 +312,12 @@ function hasExplicitWebSearchSelection(params: {
|
||||
}
|
||||
|
||||
export async function runWebSearch(params: RunWebSearchParams): Promise<RunWebSearchResult> {
|
||||
const search = resolveSearchConfig(params.config);
|
||||
const config = resolveWebSearchRuntimeConfig(params.config);
|
||||
const search = resolveSearchConfig(config);
|
||||
const runtimeWebSearch = params.runtimeWebSearch ?? getActiveRuntimeWebToolsMetadata()?.search;
|
||||
const candidates = resolveWebSearchCandidates({
|
||||
...params,
|
||||
config,
|
||||
runtimeWebSearch,
|
||||
preferRuntimeProviders: params.preferRuntimeProviders ?? true,
|
||||
});
|
||||
@@ -316,7 +336,7 @@ export async function runWebSearch(params: RunWebSearchParams): Promise<RunWebSe
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const definition = candidate.createTool({
|
||||
config: params.config,
|
||||
config,
|
||||
searchConfig: search as Record<string, unknown> | undefined,
|
||||
runtimeMetadata: runtimeWebSearch,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user