diff --git a/src/agents/agent-paths.ts b/src/agents/agent-paths.ts index cfb874d3112..15861d145a1 100644 --- a/src/agents/agent-paths.ts +++ b/src/agents/agent-paths.ts @@ -3,14 +3,13 @@ import { resolveStateDir } from "../config/paths.js"; import { DEFAULT_AGENT_ID } from "../routing/session-key.js"; import { resolveUserPath } from "../utils.js"; -export function resolveOpenClawAgentDir(): string { - const override = - process.env.OPENCLAW_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim(); +export function resolveOpenClawAgentDir(env: NodeJS.ProcessEnv = process.env): string { + const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim(); if (override) { - return resolveUserPath(override); + return resolveUserPath(override, env); } - const defaultAgentDir = path.join(resolveStateDir(), "agents", DEFAULT_AGENT_ID, "agent"); - return resolveUserPath(defaultAgentDir); + const defaultAgentDir = path.join(resolveStateDir(env), "agents", DEFAULT_AGENT_ID, "agent"); + return resolveUserPath(defaultAgentDir, env); } export function ensureOpenClawAgentEnv(): string { diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index 5d190ce1eae..5425b033dca 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -327,12 +327,16 @@ export function resolveAgentIdByWorkspacePath( return resolveAgentIdsByWorkspacePath(cfg, workspacePath)[0]; } -export function resolveAgentDir(cfg: OpenClawConfig, agentId: string) { +export function resolveAgentDir( + cfg: OpenClawConfig, + agentId: string, + env: NodeJS.ProcessEnv = process.env, +) { const id = normalizeAgentId(agentId); const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim(); if (configured) { - return resolveUserPath(configured); + return resolveUserPath(configured, env); } - const root = resolveStateDir(process.env); + const root = resolveStateDir(env); return path.join(root, "agents", id, "agent"); } diff --git a/src/secrets/apply.test.ts b/src/secrets/apply.test.ts index 55d14c7e6d0..d71c98ac389 100644 --- a/src/secrets/apply.test.ts +++ b/src/secrets/apply.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { runSecretsApply } from "./apply.js"; import type { SecretsApplyPlan } from "./plan.js"; +import { clearSecretsRuntimeSnapshot } from "./runtime.js"; const OPENAI_API_KEY_ENV_REF = { source: "env", @@ -173,11 +174,13 @@ describe("secrets apply", () => { let fixture: ApplyFixture; beforeEach(async () => { + clearSecretsRuntimeSnapshot(); fixture = await createApplyFixture(); await seedDefaultApplyFixture(fixture); }); afterEach(async () => { + clearSecretsRuntimeSnapshot(); await fs.rm(fixture.rootDir, { recursive: true, force: true }); }); diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index 57e3e955066..c67f6af6573 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import * as webSearchProviders from "../plugins/web-search-providers.js"; import * as secretResolve from "./resolve.js"; import { createResolverContext } from "./runtime-shared.js"; import { resolveRuntimeWebTools } from "./runtime-web-tools.js"; @@ -88,6 +89,32 @@ describe("runtime web tools resolution", () => { vi.restoreAllMocks(); }); + it("skips loading web search providers when search config is absent", async () => { + const providerSpy = vi.spyOn(webSearchProviders, "resolvePluginWebSearchProviders"); + + const { metadata } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + fetch: { + firecrawl: { + apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY_REF" }, + }, + }, + }, + }, + }), + env: { + FIRECRAWL_API_KEY: "firecrawl-runtime-key", // pragma: allowlist secret + }, + }); + + expect(providerSpy).not.toHaveBeenCalled(); + expect(metadata.search.providerSource).toBe("none"); + expect(metadata.fetch.firecrawl.active).toBe(true); + expect(metadata.fetch.firecrawl.apiKeySource).toBe("env"); + }); + it.each([ { provider: "brave" as const, diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index 71b346cc462..fd32ecedf93 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -315,11 +315,13 @@ export async function resolveRuntimeWebTools(params: { const tools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined; const web = isRecord(tools?.web) ? tools.web : undefined; const search = isRecord(web?.search) ? web.search : undefined; - const providers = resolvePluginWebSearchProviders({ - config: params.sourceConfig, - env: params.context.env, - bundledAllowlistCompat: true, - }); + const providers = search + ? resolvePluginWebSearchProviders({ + config: params.sourceConfig, + env: params.context.env, + bundledAllowlistCompat: true, + }) + : []; const searchMetadata: RuntimeWebSearchMetadata = { providerSource: "none", diff --git a/src/secrets/runtime.ts b/src/secrets/runtime.ts index 903fe5a6d24..ed85cde5a8d 100644 --- a/src/secrets/runtime.ts +++ b/src/secrets/runtime.ts @@ -79,11 +79,14 @@ function clearActiveSecretsRuntimeState(): void { clearRuntimeAuthProfileStoreSnapshots(); } -function collectCandidateAgentDirs(config: OpenClawConfig): string[] { +function collectCandidateAgentDirs( + config: OpenClawConfig, + env: NodeJS.ProcessEnv = process.env, +): string[] { const dirs = new Set(); - dirs.add(resolveUserPath(resolveOpenClawAgentDir())); + dirs.add(resolveUserPath(resolveOpenClawAgentDir(env), env)); for (const agentId of listAgentIds(config)) { - dirs.add(resolveUserPath(resolveAgentDir(config, agentId))); + dirs.add(resolveUserPath(resolveAgentDir(config, agentId, env), env)); } return [...dirs]; } @@ -92,7 +95,7 @@ function resolveRefreshAgentDirs( config: OpenClawConfig, context: SecretsRuntimeRefreshContext, ): string[] { - const configDerived = collectCandidateAgentDirs(config); + const configDerived = collectCandidateAgentDirs(config, context.env); if (!context.explicitAgentDirs || context.explicitAgentDirs.length === 0) { return configDerived; } @@ -119,8 +122,12 @@ export async function prepareSecretsRuntimeSnapshot(params: { const loadAuthStore = params.loadAuthStore ?? loadAuthProfileStoreForSecretsRuntime; const candidateDirs = params.agentDirs?.length - ? [...new Set(params.agentDirs.map((entry) => resolveUserPath(entry)))] - : collectCandidateAgentDirs(resolvedConfig); + ? [ + ...new Set( + params.agentDirs.map((entry) => resolveUserPath(entry, params.env ?? process.env)), + ), + ] + : collectCandidateAgentDirs(resolvedConfig, params.env ?? process.env); const authStores: Array<{ agentDir: string; store: AuthProfileStore }> = []; for (const agentDir of candidateDirs) {