diff --git a/CHANGELOG.md b/CHANGELOG.md index 073d900d13a..25ca40c4958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Docs: https://docs.openclaw.ai - Providers/Fireworks: expose Kimi models as thinking-off-only and keep K2.5/K2.6 requests on `thinking: disabled`, so manual model switches do not send Fireworks-rejected `reasoning*` parameters. Refs #74289. Thanks @frankekn. - WhatsApp responsiveness: stop only verified stale local TUI clients when they degrade the Gateway event loop and delay replies. Thanks @vincentkoc. - Hooks/session-memory: add collision suffixes to fallback memory filenames so repeated `/new` or `/reset` captures in the same minute do not overwrite the earlier session archive. Thanks @vincentkoc. +- Agents/config: remove the ambiguous legacy `main` agent dir helper from runtime paths; model, auth, gateway, bundled plugin, and test helpers now resolve default/session agent dirs through `agents.list`/agent-scope helpers while plugin SDK keeps a deprecated compatibility export. - Video generation: wait up to 20 minutes for slow fal/MiniMax queue-backed jobs, stop forwarding unsupported Google Veo generated-audio options, and normalize MiniMax `720P` requests to its supported `768P` resolution with the usual override warning/details instead of failing fallback. - Video generation: accept provider-specific aspect-ratio and resolution hints at the tool boundary, normalize `720P` to MiniMax's supported `768P`, and stop sending Google `generateAudio` on Gemini video requests so provider fallback can recover from model-specific parameter differences. Thanks @vincentkoc. - OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc. diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index b2e94d9a312..0e0b7e6ea89 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -43c6f668cd8301f485c64e6a663dc1b19d38c146ce2572943e2dc961973e0c6f plugin-sdk-api-baseline.json -1d877d94bebb634d90d929fe0581ba4bccf4d12d8342d179ae9bf1053e68c013 plugin-sdk-api-baseline.jsonl +2164ea491c61e643f0a9c68f7b9bd2e41ab338eb93bbdf301da2fae548722581 plugin-sdk-api-baseline.json +c07c3719218a12482e2a76e6b9654da2ddddf75d8d70145cdaef3da2b2eaccef plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 26dd0c5ab1d..bf267c7dc48 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -117,7 +117,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `plugin-sdk/provider-auth-result` | Standard OAuth auth-result builder | | `plugin-sdk/provider-auth-login` | Shared interactive login helpers for provider plugins | | `plugin-sdk/provider-env-vars` | Provider auth env-var lookup helpers | - | `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials` | + | `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, deprecated `resolveOpenClawAgentDir` compatibility export | | `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and model-id normalization helpers such as `normalizeNativeXaiModelId` | | `plugin-sdk/provider-catalog-runtime` | Provider catalog augmentation runtime hook and plugin-provider registry seams for contract tests | | `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` | @@ -253,7 +253,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `plugin-sdk/string-coerce-runtime` | Narrow primitive record/string coercion and normalization helpers without markdown/logging imports | | `plugin-sdk/host-runtime` | Hostname and SCP host normalization helpers | | `plugin-sdk/retry-runtime` | Retry config and retry runner helpers | - | `plugin-sdk/agent-runtime` | Agent dir/identity/workspace helpers | + | `plugin-sdk/agent-runtime` | Agent dir/identity/workspace helpers, including `resolveAgentDir`, `resolveDefaultAgentDir`, and deprecated `resolveOpenClawAgentDir` compatibility export | | `plugin-sdk/directory-runtime` | Config-backed directory query/dedup | | `plugin-sdk/keyed-async-queue` | `KeyedAsyncQueue` | diff --git a/extensions/codex/src/app-server/auth-bridge.ts b/extensions/codex/src/app-server/auth-bridge.ts index 2d67b60b0c0..641e7ff04e0 100644 --- a/extensions/codex/src/app-server/auth-bridge.ts +++ b/extensions/codex/src/app-server/auth-bridge.ts @@ -6,7 +6,7 @@ import { resolveAuthProfileOrder, resolveProviderIdForAuth, resolveApiKeyForProfile, - resolveOpenClawAgentDir, + resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, saveAuthProfileStore, type AuthProfileCredential, @@ -82,7 +82,7 @@ export function resolveCodexAppServerAuthProfileIdForAgent(params: { agentDir?: string; config?: AuthProfileOrderConfig; }): string | undefined { - const agentDir = params.agentDir?.trim() || resolveOpenClawAgentDir(); + const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {}); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }); return resolveCodexAppServerAuthProfileId({ authProfileId: params.authProfileId, diff --git a/extensions/codex/src/app-server/models.test.ts b/extensions/codex/src/app-server/models.test.ts index 1c141d964ae..cd279788f70 100644 --- a/extensions/codex/src/app-server/models.test.ts +++ b/extensions/codex/src/app-server/models.test.ts @@ -27,8 +27,8 @@ vi.mock("./managed-binary.js", () => ({ resolveManagedCodexAppServerStartOptions: mocks.managedBinary.startOptions, })); -vi.mock("openclaw/plugin-sdk/provider-auth", () => ({ - resolveOpenClawAgentDir: mocks.providerAuth.agentDir, +vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({ + resolveDefaultAgentDir: mocks.providerAuth.agentDir, })); let listCodexAppServerModels: typeof import("./models.js").listCodexAppServerModels; diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index c87f92b58d4..b8c5f1b611d 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -16,9 +16,9 @@ import { isSubagentSessionKey, normalizeAgentRuntimeTools, resolveAttemptSpawnWorkspaceDir, + resolveAgentDir, resolveAgentHarnessBeforePromptBuildResult, resolveModelAuthMode, - resolveOpenClawAgentDir, resolveSandboxContext, resolveSessionAgentIds, resolveUserPath, @@ -385,7 +385,7 @@ export async function runCodexAppServerAttempt( config: params.config, agentId: params.agentId, }); - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId); const startupBinding = await readCodexAppServerBinding(params.sessionFile); const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? @@ -1477,7 +1477,7 @@ async function buildDynamicTools(input: DynamicToolBuildParams) { return []; } const modelHasVision = params.model.input?.includes("image") ?? false; - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId); const { createOpenClawCodingTools } = await import("openclaw/plugin-sdk/agent-harness"); const allTools = createOpenClawCodingTools({ agentId: input.sessionAgentId, diff --git a/extensions/codex/src/app-server/session-binding.ts b/extensions/codex/src/app-server/session-binding.ts index 7c3022d0d0b..d6d21d0f32c 100644 --- a/extensions/codex/src/app-server/session-binding.ts +++ b/extensions/codex/src/app-server/session-binding.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import { embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime"; import { ensureAuthProfileStore, - resolveOpenClawAgentDir, + resolveDefaultAgentDir, resolveProviderIdForAuth, type AuthProfileStore, } from "openclaw/plugin-sdk/agent-runtime"; @@ -194,12 +194,16 @@ function resolveCodexAppServerAuthProfileCredential( if (!authProfileId) { return undefined; } - const store = lookup.authProfileStore ?? loadCodexAppServerAuthProfileStore(lookup.agentDir); + const store = + lookup.authProfileStore ?? loadCodexAppServerAuthProfileStore(lookup.agentDir, lookup.config); return store.profiles[authProfileId]; } -function loadCodexAppServerAuthProfileStore(agentDir: string | undefined): AuthProfileStore { - return ensureAuthProfileStore(agentDir?.trim() || resolveOpenClawAgentDir(), { +function loadCodexAppServerAuthProfileStore( + agentDir: string | undefined, + config?: ProviderAuthAliasConfig, +): AuthProfileStore { + return ensureAuthProfileStore(agentDir?.trim() || resolveDefaultAgentDir(config ?? {}), { allowKeychainPrompt: false, }); } diff --git a/extensions/codex/src/app-server/shared-client.test.ts b/extensions/codex/src/app-server/shared-client.test.ts index 8e035c44d71..7e028411e00 100644 --- a/extensions/codex/src/app-server/shared-client.test.ts +++ b/extensions/codex/src/app-server/shared-client.test.ts @@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({ ), resolveManagedCodexAppServerStartOptions: vi.fn(async (startOptions) => startOptions), embeddedAgentLog: { debug: vi.fn(), warn: vi.fn() }, - resolveOpenClawAgentDir: vi.fn(() => "/tmp/openclaw-agent"), + resolveDefaultAgentDir: vi.fn(() => "/tmp/openclaw-agent"), })); vi.mock("./auth-bridge.js", () => ({ @@ -29,8 +29,8 @@ vi.mock("openclaw/plugin-sdk/agent-harness-runtime", () => ({ OPENCLAW_VERSION: "test", })); -vi.mock("openclaw/plugin-sdk/provider-auth", () => ({ - resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir, +vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({ + resolveDefaultAgentDir: mocks.resolveDefaultAgentDir, })); let listCodexAppServerModels: typeof import("./models.js").listCodexAppServerModels; @@ -81,7 +81,7 @@ describe("shared Codex app-server client", () => { ); mocks.embeddedAgentLog.debug.mockClear(); mocks.embeddedAgentLog.warn.mockClear(); - mocks.resolveOpenClawAgentDir.mockClear(); + mocks.resolveDefaultAgentDir.mockClear(); }); it("closes the shared app-server when the version gate fails", async () => { diff --git a/extensions/codex/src/app-server/shared-client.ts b/extensions/codex/src/app-server/shared-client.ts index 1f5f4c23bb5..289857b0a26 100644 --- a/extensions/codex/src/app-server/shared-client.ts +++ b/extensions/codex/src/app-server/shared-client.ts @@ -1,4 +1,4 @@ -import { resolveOpenClawAgentDir } from "openclaw/plugin-sdk/provider-auth"; +import { resolveDefaultAgentDir } from "openclaw/plugin-sdk/agent-runtime"; import { applyCodexAppServerAuthProfile, bridgeCodexAppServerStartOptions, @@ -37,7 +37,7 @@ export async function getSharedCodexAppServerClient(options?: { config?: Parameters[0]["config"]; }): Promise { const state = getSharedCodexAppServerClientState(); - const agentDir = options?.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {}); const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({ authProfileId: options?.authProfileId, agentDir, @@ -104,7 +104,7 @@ export async function createIsolatedCodexAppServerClient(options?: { agentDir?: string; config?: Parameters[0]["config"]; }): Promise { - const agentDir = options?.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {}); const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({ authProfileId: options?.authProfileId, agentDir, diff --git a/extensions/codex/src/conversation-binding.test.ts b/extensions/codex/src/conversation-binding.test.ts index 5ded64df0dd..1d0b4b2839e 100644 --- a/extensions/codex/src/conversation-binding.test.ts +++ b/extensions/codex/src/conversation-binding.test.ts @@ -12,7 +12,7 @@ const agentRuntimeMocks = vi.hoisted(() => ({ loadAuthProfileStoreForSecretsRuntime: vi.fn(), resolveApiKeyForProfile: vi.fn(), resolveAuthProfileOrder: vi.fn(), - resolveOpenClawAgentDir: vi.fn(() => "/agent"), + resolveDefaultAgentDir: vi.fn(() => "/agent"), resolvePersistedAuthProfileOwnerAgentDir: vi.fn(), resolveProviderIdForAuth: vi.fn((provider: string) => provider), saveAuthProfileStore: vi.fn(), @@ -40,7 +40,7 @@ describe("codex conversation binding", () => { agentRuntimeMocks.loadAuthProfileStoreForSecretsRuntime.mockReset(); agentRuntimeMocks.resolveApiKeyForProfile.mockReset(); agentRuntimeMocks.resolveAuthProfileOrder.mockReset(); - agentRuntimeMocks.resolveOpenClawAgentDir.mockClear(); + agentRuntimeMocks.resolveDefaultAgentDir.mockClear(); agentRuntimeMocks.resolvePersistedAuthProfileOwnerAgentDir.mockReset(); agentRuntimeMocks.resolveProviderIdForAuth.mockClear(); agentRuntimeMocks.saveAuthProfileStore.mockReset(); @@ -53,7 +53,7 @@ describe("codex conversation binding", () => { profiles: {}, }); agentRuntimeMocks.resolveAuthProfileOrder.mockReturnValue([]); - agentRuntimeMocks.resolveOpenClawAgentDir.mockReturnValue("/agent"); + agentRuntimeMocks.resolveDefaultAgentDir.mockReturnValue("/agent"); agentRuntimeMocks.resolveProviderIdForAuth.mockImplementation((provider: string) => provider); }); diff --git a/extensions/comfy/comfy.live.test.ts b/extensions/comfy/comfy.live.test.ts index b7af743c5c8..5c944074b11 100644 --- a/extensions/comfy/comfy.live.test.ts +++ b/extensions/comfy/comfy.live.test.ts @@ -1,4 +1,4 @@ -import { resolveOpenClawAgentDir } from "openclaw/plugin-sdk/agent-runtime"; +import { resolveDefaultAgentDir } from "openclaw/plugin-sdk/agent-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api"; import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot"; @@ -42,7 +42,7 @@ describeLive("comfy live", () => { beforeAll(async () => { cfg = withPluginsEnabled(getRuntimeConfig()); - agentDir = resolveOpenClawAgentDir(); + agentDir = resolveDefaultAgentDir(cfg as never); plugin.register( createTestPluginApi({ config: cfg as never, diff --git a/extensions/music-generation-providers.live.test.ts b/extensions/music-generation-providers.live.test.ts index 99814f85f7e..5c0689016fb 100644 --- a/extensions/music-generation-providers.live.test.ts +++ b/extensions/music-generation-providers.live.test.ts @@ -1,6 +1,6 @@ import { resolveApiKeyForProvider, - resolveOpenClawAgentDir, + resolveDefaultAgentDir, } from "openclaw/plugin-sdk/agent-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { @@ -159,7 +159,7 @@ describeLive("music generation provider live", () => { async () => { const cfg = withPluginsEnabled(getRuntimeConfig()); const configuredModels = resolveConfiguredLiveMusicModels(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg as never); const attempted: string[] = []; const skipped: string[] = []; const failures: string[] = []; diff --git a/extensions/video-generation-providers.live.test.ts b/extensions/video-generation-providers.live.test.ts index e26f044ecfb..ed7bb86d6fc 100644 --- a/extensions/video-generation-providers.live.test.ts +++ b/extensions/video-generation-providers.live.test.ts @@ -1,6 +1,6 @@ import { resolveApiKeyForProvider, - resolveOpenClawAgentDir, + resolveDefaultAgentDir, } from "openclaw/plugin-sdk/agent-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { @@ -361,7 +361,7 @@ function resolveLiveSmokeDurationSeconds(params: { async function runLiveVideoProviderCase(testCase: LiveProviderCase): Promise { const cfg = withPluginsEnabled(getRuntimeConfig()); const configuredModels = resolveConfiguredLiveVideoModels(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg as never); const attempted: string[] = []; const skipped: string[] = []; const failures: string[] = []; diff --git a/scripts/anthropic-prompt-probe.ts b/scripts/anthropic-prompt-probe.ts index 4e3f383caec..20c13633806 100644 --- a/scripts/anthropic-prompt-probe.ts +++ b/scripts/anthropic-prompt-probe.ts @@ -12,7 +12,7 @@ import http from "node:http"; import os from "node:os"; import path from "node:path"; import process from "node:process"; -import { resolveOpenClawAgentDir } from "../src/agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../src/agents/agent-scope.js"; import { ensureAuthProfileStore, type AuthProfileCredential } from "../src/agents/auth-profiles.js"; import { normalizeProviderId } from "../src/agents/model-selection.js"; import { validateAnthropicSetupToken } from "../src/commands/auth-token.js"; @@ -185,7 +185,7 @@ function resolveSetupTokenSource(): TokenSource { }; } - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir({}); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false, }); diff --git a/src/agents/agent-scope-config.ts b/src/agents/agent-scope-config.ts index 5691180d621..ba5063c14c4 100644 --- a/src/agents/agent-scope-config.ts +++ b/src/agents/agent-scope-config.ts @@ -189,3 +189,10 @@ export function resolveAgentDir( const root = resolveStateDir(env); return path.join(root, "agents", id, "agent"); } + +export function resolveDefaultAgentDir( + cfg: OpenClawConfig, + env: NodeJS.ProcessEnv = process.env, +): string { + return resolveAgentDir(cfg, resolveDefaultAgentId(cfg), env); +} diff --git a/src/agents/agent-scope.test.ts b/src/agents/agent-scope.test.ts index d46ceecefe9..f66768ea085 100644 --- a/src/agents/agent-scope.test.ts +++ b/src/agents/agent-scope.test.ts @@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { hasConfiguredModelFallbacks, resolveAgentConfig, + resolveDefaultAgentDir, resolveAgentDir, resolveAgentEffectiveModelPrimary, resolveAgentExplicitModelPrimary, @@ -595,6 +596,20 @@ describe("resolveAgentConfig", () => { expect(agentDir).toBe(path.join(path.resolve(home), ".openclaw", "agents", "main", "agent")); }); + it("resolves default agentDir from the configured default agent", () => { + const stateDir = path.join(path.sep, "tmp", "test-state"); + vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "main" }, { id: "ops", default: true }], + }, + }; + + const agentDir = resolveDefaultAgentDir(cfg); + + expect(agentDir).toBe(path.join(stateDir, "agents", "ops", "agent")); + }); + it("non-default agent uses agents.defaults.workspace as base (#59789)", () => { const cfg: OpenClawConfig = { agents: { diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index 57c7ddebfbf..e992816745c 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -23,6 +23,7 @@ import { resolveAgentConfig, resolveAgentContextLimits, resolveAgentDir, + resolveDefaultAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, type ResolvedAgentConfig, @@ -34,6 +35,7 @@ export { resolveAgentConfig, resolveAgentContextLimits, resolveAgentDir, + resolveDefaultAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, type ResolvedAgentConfig, diff --git a/src/agents/anthropic.setup-token.live.test.ts b/src/agents/anthropic.setup-token.live.test.ts index 227b776fcfe..fd2848cbca8 100644 --- a/src/agents/anthropic.setup-token.live.test.ts +++ b/src/agents/anthropic.setup-token.live.test.ts @@ -9,7 +9,7 @@ import { validateAnthropicSetupToken, } from "../commands/auth-token.js"; import { getRuntimeConfig } from "../config/config.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { type AuthProfileCredential, ensureAuthProfileStore, @@ -95,7 +95,7 @@ async function resolveTokenSource(): Promise { }; } - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(getRuntimeConfig()); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false, }); diff --git a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts index 7b796e2c3a8..673f4a0cf3a 100644 --- a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts +++ b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts @@ -69,7 +69,7 @@ describe("resolveApiKeyForProfile fallback to main agent", () => { await fs.mkdir(mainAgentDir, { recursive: true }); await fs.mkdir(secondaryAgentDir, { recursive: true }); - // Set environment variables so resolveOpenClawAgentDir() returns mainAgentDir + // Set environment variables so the default agent dir resolves under tmpDir. process.env.OPENCLAW_STATE_DIR = tmpDir; process.env.OPENCLAW_AGENT_DIR = mainAgentDir; process.env.PI_CODING_AGENT_DIR = mainAgentDir; diff --git a/src/agents/auth-profiles/path-resolve.ts b/src/agents/auth-profiles/path-resolve.ts index 1b4ebd00b51..5db7d5dfb29 100644 --- a/src/agents/auth-profiles/path-resolve.ts +++ b/src/agents/auth-profiles/path-resolve.ts @@ -2,7 +2,7 @@ import { createHash } from "node:crypto"; import path from "node:path"; import { resolveStateDir } from "../../config/paths.js"; import { resolveUserPath } from "../../utils.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; +import { resolveDefaultAgentDir } from "../agent-scope-config.js"; import { AUTH_PROFILE_FILENAME, AUTH_STATE_FILENAME, @@ -10,17 +10,17 @@ import { } from "./path-constants.js"; export function resolveAuthStorePath(agentDir?: string): string { - const resolved = resolveUserPath(agentDir ?? resolveOpenClawAgentDir()); + const resolved = resolveUserPath(agentDir ?? resolveDefaultAgentDir({})); return path.join(resolved, AUTH_PROFILE_FILENAME); } export function resolveLegacyAuthStorePath(agentDir?: string): string { - const resolved = resolveUserPath(agentDir ?? resolveOpenClawAgentDir()); + const resolved = resolveUserPath(agentDir ?? resolveDefaultAgentDir({})); return path.join(resolved, LEGACY_AUTH_FILENAME); } export function resolveAuthStatePath(agentDir?: string): string { - const resolved = resolveUserPath(agentDir ?? resolveOpenClawAgentDir()); + const resolved = resolveUserPath(agentDir ?? resolveDefaultAgentDir({})); return path.join(resolved, AUTH_STATE_FILENAME); } diff --git a/src/agents/auth-profiles/paths-direct-import.test.ts b/src/agents/auth-profiles/paths-direct-import.test.ts index 6fc0cfde449..fc5de381cae 100644 --- a/src/agents/auth-profiles/paths-direct-import.test.ts +++ b/src/agents/auth-profiles/paths-direct-import.test.ts @@ -41,10 +41,9 @@ describe("path-resolve helpers (direct-import coverage attribution)", () => { expect(path.basename(resolved)).toMatch(/auth-profiles/); }); - it("resolveAuthStorePath falls back to resolveOpenClawAgentDir when agentDir is omitted", () => { - // Omitting agentDir exercises the `agentDir ?? resolveOpenClawAgentDir()` - // nullish branch. With OPENCLAW_STATE_DIR set to our tempdir, the - // resolved path must live under it. + it("resolveAuthStorePath falls back to the default agent dir when agentDir is omitted", () => { + // Omitting agentDir exercises the default agent-dir branch. With + // OPENCLAW_STATE_DIR set to our tempdir, the resolved path must live under it. const resolved = resolveAuthStorePath(); expect(resolved.startsWith(stateDir)).toBe(true); expect(path.basename(resolved)).toMatch(/auth-profiles/); diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 5f09f201f21..226899880a9 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -12,8 +12,7 @@ import type { import { buildAgentHookContextChannelFields } from "../../plugins/hook-agent-context.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { annotateInterSessionPromptText } from "../../sessions/input-provenance.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; -import { resolveSessionAgentIds } from "../agent-scope.js"; +import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js"; import { externalCliDiscoveryForProviderAuth } from "../auth-profiles/external-cli-discovery.js"; import { loadAuthProfileStoreForRuntime } from "../auth-profiles/store.js"; import type { AuthProfileCredential } from "../auth-profiles/types.js"; @@ -119,7 +118,12 @@ export async function prepareCliRunContext( `CLI backend ${backendResolved.id} cannot run with tools disabled because it exposes native tools`, ); } - const agentDir = resolveOpenClawAgentDir(); + const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({ + sessionKey: params.sessionKey, + config: params.config, + agentId: params.agentId, + }); + const agentDir = resolveAgentDir(params.config ?? {}, sessionAgentId); const requestedAuthProfileId = params.authProfileId?.trim() || undefined; const effectiveAuthProfileId = requestedAuthProfileId ?? backendResolved.defaultAuthProfileId?.trim() ?? undefined; @@ -175,11 +179,6 @@ export async function prepareCliRunContext( seenSignatures: params.bootstrapPromptWarningSignaturesSeen, previousSignature: params.bootstrapPromptWarningSignature, }); - const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({ - sessionKey: params.sessionKey, - config: params.config, - agentId: params.agentId, - }); const bundleMcpEnabled = backendResolved.bundleMcp && params.disableTools !== true; let mcpLoopbackRuntime = bundleMcpEnabled ? prepareDeps.getActiveMcpLoopbackRuntime() : undefined; if (bundleMcpEnabled && !mcpLoopbackRuntime) { diff --git a/src/agents/context.lookup.test.ts b/src/agents/context.lookup.test.ts index 01116cde3fd..87792534270 100644 --- a/src/agents/context.lookup.test.ts +++ b/src/agents/context.lookup.test.ts @@ -29,10 +29,6 @@ vi.mock("./models-config.runtime.js", () => ({ ensureOpenClawModelsJson: contextTestState.ensureOpenClawModelsJson, })); -vi.mock("./agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => "/tmp/openclaw-agent", -})); - vi.mock("./pi-model-discovery-runtime.js", () => ({ discoverAuthStorage: contextTestState.discoverAuthStorage, discoverModels: contextTestState.discoverModels, @@ -301,7 +297,7 @@ describe("lookupContextTokens", () => { expect(contextTestState.discoverModels).toHaveBeenCalledWith( expect.anything(), - "/tmp/openclaw-agent", + expect.stringMatching(/\/\.openclaw\/agents\/main\/agent$/), { normalizeModels: false }, ); expect(lookupContextTokens("anthropic/claude-opus-4.7-20260219")).toBe(1_048_576); diff --git a/src/agents/context.ts b/src/agents/context.ts index e0dbf644b29..fe956d151ca 100644 --- a/src/agents/context.ts +++ b/src/agents/context.ts @@ -8,7 +8,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { computeBackoff, type BackoffPolicy } from "../infra/backoff.js"; import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { lookupCachedContextTokens, MODEL_CONTEXT_TOKEN_CACHE } from "./context-cache.js"; import { CONTEXT_WINDOW_RUNTIME_STATE } from "./context-runtime-state.js"; import { normalizeProviderId } from "./model-selection.js"; @@ -240,7 +240,7 @@ function ensureContextWindowCacheLoaded(): Promise { try { const { discoverAuthStorage, discoverModels } = await import("./pi-model-discovery-runtime.js"); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir, { normalizeModels: false, diff --git a/src/agents/live-cache-test-support.ts b/src/agents/live-cache-test-support.ts index 3f5b8c59d5d..7efe9427406 100644 --- a/src/agents/live-cache-test-support.ts +++ b/src/agents/live-cache-test-support.ts @@ -1,7 +1,7 @@ import { completeSimple, type Api, type AssistantMessage, type Model } from "@mariozechner/pi-ai"; import { getRuntimeConfig } from "../config/config.js"; import { isTruthyEnvValue } from "../infra/env.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { collectProviderApiKeys } from "./live-auth-keys.js"; import { isLiveTestEnabled } from "./live-test-helpers.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; @@ -163,7 +163,7 @@ export async function resolveLiveDirectModel(params: { }): Promise { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const models = discoverModels(authStorage, agentDir).getAll(); diff --git a/src/agents/model-catalog.test.ts b/src/agents/model-catalog.test.ts index 3d9c200bc6e..e88decf2847 100644 --- a/src/agents/model-catalog.test.ts +++ b/src/agents/model-catalog.test.ts @@ -92,8 +92,8 @@ describe("loadModelCatalog", () => { vi.doMock("./models-config.js", () => ({ ensureOpenClawModelsJson: ensureOpenClawModelsJsonMock, })); - vi.doMock("./agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => "/tmp/openclaw", + vi.doMock("./agent-scope.js", () => ({ + resolveDefaultAgentDir: () => "/tmp/openclaw", })); vi.doMock("../plugins/provider-runtime.runtime.js", () => ({ augmentModelCatalogWithProviderPlugins: vi.fn().mockResolvedValue([]), @@ -143,7 +143,7 @@ describe("loadModelCatalog", () => { afterAll(() => { vi.doUnmock("node:fs/promises"); vi.doUnmock("./models-config.js"); - vi.doUnmock("./agent-paths.js"); + vi.doUnmock("./agent-scope.js"); vi.doUnmock("../plugins/provider-runtime.runtime.js"); vi.doUnmock("../plugins/current-plugin-metadata-snapshot.js"); vi.doUnmock("../plugins/plugin-metadata-snapshot.js"); diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index a3268309981..7f24bbb9314 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -13,7 +13,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "../shared/string-coerce.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { modelSupportsInput as modelCatalogEntrySupportsInput } from "./model-catalog-lookup.js"; import type { ModelCatalogEntry, ModelInputType } from "./model-catalog.types.js"; import { buildConfiguredModelCatalog } from "./model-selection-shared.js"; @@ -224,7 +224,7 @@ async function loadReadOnlyPersistedModelCatalog(params?: { config?: OpenClawConfig; }): Promise { const cfg = params?.config ?? getRuntimeConfig(); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const raw = await readFile(join(agentDir, "models.json"), "utf8"); const parsed = JSON.parse(raw) as Record; const models: ModelCatalogEntry[] = []; @@ -333,7 +333,7 @@ export async function loadModelCatalog(params?: { // will keep failing until restart). const piSdk = await importPiSdk(); logStage("pi-sdk-imported"); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const { buildShouldSuppressBuiltInModel } = await loadModelSuppression(); logStage("catalog-deps-ready"); const authStorage = piSdk.discoverAuthStorage( diff --git a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts index b5ab75083be..d77a7321040 100644 --- a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts +++ b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { CUSTOM_PROXY_MODELS_CONFIG, installModelsConfigTestHooks, @@ -107,7 +107,7 @@ async function runEnvProviderCase(params: { try { await ensureOpenClawModelsJson({}); - const modelPath = path.join(resolveOpenClawAgentDir(), "models.json"); + const modelPath = path.join(resolveDefaultAgentDir({}), "models.json"); const raw = await fs.readFile(modelPath, "utf8"); const parsed = JSON.parse(raw) as { providers: Record }; const provider = parsed.providers[params.providerKey]; @@ -177,7 +177,7 @@ describe("models-config", () => { await withTempHome(async () => { await ensureOpenClawModelsJson(CUSTOM_PROXY_MODELS_CONFIG); - const modelPath = path.join(resolveOpenClawAgentDir(), "models.json"); + const modelPath = path.join(resolveDefaultAgentDir({}), "models.json"); const raw = await fs.readFile(modelPath, "utf8"); const parsed = JSON.parse(raw) as { providers: Record< diff --git a/src/agents/models-config.test-utils.ts b/src/agents/models-config.test-utils.ts index 3d32f1b5fc4..e70512f0fb7 100644 --- a/src/agents/models-config.test-utils.ts +++ b/src/agents/models-config.test-utils.ts @@ -1,8 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; -export async function readGeneratedModelsJson(agentDir = resolveOpenClawAgentDir()): Promise { +export async function readGeneratedModelsJson( + agentDir = resolveDefaultAgentDir({}), +): Promise { const modelPath = path.join(agentDir, "models.json"); const raw = await fs.readFile(modelPath, "utf8"); return JSON.parse(raw) as T; diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index cfba621280a..7cb061c1f5e 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -10,8 +10,11 @@ import { createConfigRuntimeEnv } from "../config/env-vars.js"; import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js"; import { resolveInstalledManifestRegistryIndexFingerprint } from "../plugins/manifest-registry-installed.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; -import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js"; +import { + resolveAgentWorkspaceDir, + resolveDefaultAgentDir, + resolveDefaultAgentId, +} from "./agent-scope.js"; import { MODELS_JSON_STATE } from "./models-config-state.js"; import { planOpenClawModelsJson } from "./models-config.plan.js"; @@ -180,7 +183,7 @@ export async function ensureOpenClawModelsJson( config: cfg, ...(workspaceDir ? { workspaceDir } : {}), }); - const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir(); + const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg); const targetPath = path.join(agentDir, "models.json"); const fingerprint = await buildModelsJsonFingerprint({ config: cfg, diff --git a/src/agents/models-config.write-serialization.test.ts b/src/agents/models-config.write-serialization.test.ts index 942fa1e001f..4323bab02b4 100644 --- a/src/agents/models-config.write-serialization.test.ts +++ b/src/agents/models-config.write-serialization.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { CUSTOM_PROXY_MODELS_CONFIG, installModelsConfigTestHooks, @@ -114,6 +114,24 @@ describe("models-config write serialization", () => { }); }); + it("writes implicit models.json into the configured default agent dir", async () => { + await withModelsTempHome(async (home) => { + const cfg = { + agents: { + list: [{ id: "main" }, { id: "ops", default: true }], + }, + }; + + const result = await ensureOpenClawModelsJson(cfg); + + expect(result.agentDir).toBe(path.join(home, ".openclaw", "agents", "ops", "agent")); + await expect(fs.access(path.join(result.agentDir, "models.json"))).resolves.toBeUndefined(); + await expect( + fs.access(path.join(home, ".openclaw", "agents", "main", "agent", "models.json")), + ).rejects.toThrow(); + }); + }); + it("does not reuse scoped startup discovery cache for a different provider scope", async () => { await withModelsTempHome(async (home) => { planOpenClawModelsJsonMock.mockImplementation(async () => ({ action: "skip" })); @@ -151,7 +169,7 @@ describe("models-config write serialization", () => { await ensureOpenClawModelsJson(CUSTOM_PROXY_MODELS_CONFIG); await ensureOpenClawModelsJson(CUSTOM_PROXY_MODELS_CONFIG); - const modelPath = path.join(resolveOpenClawAgentDir(), "models.json"); + const modelPath = path.join(resolveDefaultAgentDir({}), "models.json"); await fs.writeFile(modelPath, `${JSON.stringify({ external: true })}\n`, "utf8"); const externalMtime = new Date(Date.now() + 2000); await fs.utimes(modelPath, externalMtime, externalMtime); diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index c4474063192..c5d3236b6c5 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; import { getRuntimeConfig } from "../config/config.js"; import { parseLiveCsvFilter } from "../media-generation/live-test-helpers.js"; import { runTasksWithConcurrency } from "../utils/run-with-concurrency.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { externalCliDiscoveryForProviders } from "./auth-profiles/external-cli-discovery.js"; import { collectAnthropicApiKeys, @@ -733,7 +733,7 @@ describeLive("live models (profile keys)", () => { const providers = parseProviderFilter(process.env.OPENCLAW_LIVE_PROVIDERS); const providerList = providers ? [...providers] : null; - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir, { config: cfg, env: process.env, diff --git a/src/agents/openai-reasoning-compat.live.test.ts b/src/agents/openai-reasoning-compat.live.test.ts index 4992ac91d3d..36a0d765786 100644 --- a/src/agents/openai-reasoning-compat.live.test.ts +++ b/src/agents/openai-reasoning-compat.live.test.ts @@ -4,7 +4,7 @@ import { SessionManager } from "@mariozechner/pi-coding-agent"; import { Type } from "typebox"; import { describe, expect, it } from "vitest"; import { getRuntimeConfig } from "../config/config.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "./live-test-helpers.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; @@ -127,7 +127,7 @@ describeLive("openai reasoning compat live", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const model = modelRegistry.find(provider, modelId) as Model | null; @@ -181,7 +181,7 @@ describeLive("openai reasoning compat live", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const model = modelRegistry.find(provider, modelId) as Model | null; diff --git a/src/agents/pi-embedded-runner/compact.hooks.harness.ts b/src/agents/pi-embedded-runner/compact.hooks.harness.ts index db554179f94..d41ef51b834 100644 --- a/src/agents/pi-embedded-runner/compact.hooks.harness.ts +++ b/src/agents/pi-embedded-runner/compact.hooks.harness.ts @@ -499,13 +499,11 @@ export async function loadCompactHooksHarness(): Promise<{ resolveSkillsPromptForRun: vi.fn(() => undefined), })); - vi.doMock("../agent-paths.js", () => ({ - resolveOpenClawAgentDir: vi.fn(() => "/tmp"), - })); - vi.doMock("../agent-scope.js", () => ({ listAgentEntries: vi.fn(() => []), resolveAgentConfig: vi.fn(() => undefined), + resolveAgentDir: vi.fn((_cfg: unknown, agentId: string) => `/tmp/agents/${agentId}/agent`), + resolveDefaultAgentDir: vi.fn(() => "/tmp/agents/main/agent"), resolveDefaultAgentId: vi.fn(() => "main"), resolveRunModelFallbacksOverride: vi.fn(() => undefined), resolveSessionAgentId: resolveSessionAgentIdMock, diff --git a/src/agents/pi-embedded-runner/compact.queued.ts b/src/agents/pi-embedded-runner/compact.queued.ts index 64b5c25bc34..01bafcc3826 100644 --- a/src/agents/pi-embedded-runner/compact.queued.ts +++ b/src/agents/pi-embedded-runner/compact.queued.ts @@ -14,8 +14,7 @@ import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js"; import { enqueueCommandInLane } from "../../process/command-queue.js"; import { resolveUserPath } from "../../utils.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; -import { resolveSessionAgentIds } from "../agent-scope.js"; +import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js"; import { resolveContextWindowInfo } from "../context-window-guard.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js"; import { maybeCompactAgentHarnessSession } from "../harness/selection.js"; @@ -51,7 +50,11 @@ export async function compactEmbeddedPiSession( allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, }); ensureContextEnginesInitialized(); - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentIds = resolveSessionAgentIds({ + sessionKey: params.sessionKey, + config: params.config, + }); + const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, agentIds.sessionAgentId); const resolvedWorkspaceDir = resolveUserPath(params.workspaceDir); const contextEngine = await resolveContextEngine(params.config, { agentDir, diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index d36f9678057..063889f5f7b 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -34,8 +34,11 @@ import { buildTtsSystemPromptHint } from "../../tts/tts.js"; import { resolveUserPath } from "../../utils.js"; import { normalizeMessageChannel } from "../../utils/message-channel.js"; import { isReasoningTagProvider } from "../../utils/provider-utils.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; -import { resolveRunModelFallbacksOverride, resolveSessionAgentIds } from "../agent-scope.js"; +import { + resolveAgentDir, + resolveRunModelFallbacksOverride, + resolveSessionAgentIds, +} from "../agent-scope.js"; import { makeBootstrapWarn, resolveBootstrapContextForRun, @@ -481,7 +484,12 @@ async function compactEmbeddedPiSessionDirectOnce( : undefined, }; }; - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const earlyAgentIds = resolveSessionAgentIds({ + sessionKey: params.sessionKey, + config: params.config, + }); + const agentDir = + params.agentDir ?? resolveAgentDir(params.config ?? {}, earlyAgentIds.sessionAgentId); await ensureOpenClawModelsJson(params.config, agentDir, { workspaceDir: resolvedWorkspace, }); diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 3a37b1ced92..dde23af3f62 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -17,7 +17,7 @@ import { normalizeProviderResolvedModelWithPlugin, shouldPreferProviderRuntimeResolvedModel, } from "../../plugins/provider-runtime.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; +import { resolveDefaultAgentDir } from "../agent-scope.js"; import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { buildModelAliasLines } from "../model-alias-lines.js"; import { modelKey, normalizeStaticProviderModelId } from "../model-ref-shared.js"; @@ -989,7 +989,7 @@ export function resolveModel( provider, model: normalizeStaticProviderModelId(normalizeProviderId(provider), modelId), }; - const resolvedAgentDir = agentDir ?? resolveOpenClawAgentDir(); + const resolvedAgentDir = agentDir ?? resolveDefaultAgentDir(cfg ?? {}); const authStorage = options?.authStorage ?? discoverAuthStorage(resolvedAgentDir); const modelRegistry = options?.modelRegistry ?? discoverModels(authStorage, resolvedAgentDir); const runtimeHooks = resolveRuntimeHooks(options); @@ -1041,7 +1041,7 @@ export async function resolveModelAsync( provider, model: normalizeStaticProviderModelId(normalizeProviderId(provider), modelId), }; - const resolvedAgentDir = agentDir ?? resolveOpenClawAgentDir(); + const resolvedAgentDir = agentDir ?? resolveDefaultAgentDir(cfg ?? {}); const emptyDiscoveryStores = options?.skipPiDiscovery && (!options.authStorage || !options.modelRegistry) ? createEmptyPiDiscoveryStores() diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts index 363cc57f991..b8ceb71414d 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts @@ -535,10 +535,6 @@ export async function loadRunOverflowCompactionHarness(): Promise<{ isMarkdownCapableMessageChannel: vi.fn(() => true), })); - vi.doMock("../agent-paths.js", () => ({ - resolveOpenClawAgentDir: vi.fn(() => "/tmp/agent-dir"), - })); - vi.doMock("../defaults.js", () => ({ DEFAULT_CONTEXT_TOKENS: 200000, DEFAULT_MODEL: "test-model", diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts index d5993462158..659b679968a 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts @@ -205,7 +205,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => { expect(mockedEnsureAuthProfileStore).not.toHaveBeenCalled(); expect(mockedEnsureAuthProfileStoreWithoutExternalProfiles).toHaveBeenCalledWith( - "/tmp/agent-dir", + expect.stringMatching(/[/\\]\.openclaw[/\\]agents[/\\]main[/\\]agent$/), { allowKeychainPrompt: false }, ); }); diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index f81ee725bb4..be627aa2d1f 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -18,10 +18,10 @@ import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { sanitizeForLog } from "../../terminal/ansi.js"; import { resolveUserPath } from "../../utils.js"; import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js"; -import { resolveOpenClawAgentDir } from "../agent-paths.js"; import { hasConfiguredModelFallbacks, resolveAgentExecutionContract, + resolveAgentDir, resolveSessionAgentIds, resolveAgentWorkspaceDir, } from "../agent-scope.js"; @@ -429,7 +429,8 @@ export async function runEmbeddedPiAgent( let provider = (params.provider ?? DEFAULT_PROVIDER).trim() || DEFAULT_PROVIDER; let modelId = (params.model ?? DEFAULT_MODEL).trim() || DEFAULT_MODEL; - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = + params.agentDir ?? resolveAgentDir(params.config ?? {}, workspaceResolution.agentId); const normalizedSessionKey = params.sessionKey?.trim(); const fallbackConfigured = hasConfiguredModelFallbacks({ cfg: params.config, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 49835399da1..30084da735e 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -50,8 +50,7 @@ import { buildTtsSystemPromptHint } from "../../../tts/tts.js"; import { resolveUserPath } from "../../../utils.js"; import { normalizeMessageChannel } from "../../../utils/message-channel.js"; import { isReasoningTagProvider } from "../../../utils/provider-utils.js"; -import { resolveOpenClawAgentDir } from "../../agent-paths.js"; -import { resolveSessionAgentIds } from "../../agent-scope.js"; +import { resolveAgentDir, resolveSessionAgentIds } from "../../agent-scope.js"; import { createAnthropicPayloadLogger } from "../../anthropic-payload-log.js"; import { analyzeBootstrapBudget, @@ -749,7 +748,7 @@ export async function runEmbeddedAttempt( ); } const activeContextEngine = isRawModelRun ? undefined : params.contextEngine; - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId); const diagnosticTrace = freezeDiagnosticTraceContext( createDiagnosticTraceContextFromActiveScope(), ); diff --git a/src/agents/tool-replay-repair.live.test.ts b/src/agents/tool-replay-repair.live.test.ts index a728d332a5b..efb891c1d2b 100644 --- a/src/agents/tool-replay-repair.live.test.ts +++ b/src/agents/tool-replay-repair.live.test.ts @@ -4,7 +4,7 @@ import { SessionManager } from "@mariozechner/pi-coding-agent"; import { Type } from "typebox"; import { describe, expect, it } from "vitest"; import { getRuntimeConfig } from "../config/config.js"; -import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { resolveDefaultAgentDir } from "./agent-scope.js"; import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "./live-test-helpers.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; @@ -189,7 +189,7 @@ describeLive("tool replay repair live", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const model = modelRegistry.find(target.provider, target.modelId) as Model | null; @@ -304,7 +304,7 @@ describeLive("tool replay repair live", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const model = modelRegistry.find(target.provider, target.modelId) as Model | null; diff --git a/src/commands/auth-choice.apply.plugin-provider.test.ts b/src/commands/auth-choice.apply.plugin-provider.test.ts index 7e1f450f199..d78e4aae76d 100644 --- a/src/commands/auth-choice.apply.plugin-provider.test.ts +++ b/src/commands/auth-choice.apply.plugin-provider.test.ts @@ -58,11 +58,6 @@ vi.mock("../agents/workspace.js", () => ({ resolveDefaultAgentWorkspaceDir, })); -const resolveOpenClawAgentDir = vi.hoisted(() => vi.fn(() => "/tmp/agent")); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir, -})); - const applyAuthProfileConfig = vi.hoisted(() => vi.fn((config) => config)); vi.mock("../plugins/provider-auth-helpers.js", () => ({ applyAuthProfileConfig, diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index 07023227848..dfe2a3b5550 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -14,7 +14,6 @@ import { createAuthTestLifecycle, createExitThrowingRuntime, createWizardPrompter, - requireOpenClawAgentDir, setupAuthTestEnv, } from "./test-wizard-helpers.js"; @@ -72,13 +71,10 @@ vi.mock("../plugins/provider-zai-endpoint.js", () => ({ detectZaiEndpoint, })); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => process.env.OPENCLAW_AGENT_DIR ?? "/tmp/openclaw-agent", -})); - vi.mock("../agents/agent-scope.js", () => ({ resolveDefaultAgentId: () => "main", - resolveAgentDir: (_config: unknown, agentId: string) => `/tmp/openclaw-agents/${agentId}`, + resolveAgentDir: (_config: unknown, agentId: string) => + `${process.env.OPENCLAW_STATE_DIR ?? "/tmp/openclaw-state"}/agents/${agentId}/agent`, resolveAgentWorkspaceDir: (_config: unknown, agentId: string) => `/tmp/openclaw-workspaces/${agentId}`, })); @@ -606,7 +602,7 @@ describe("applyAuthChoice", () => { }; } async function readAuthProfiles() { - return readTestAuthProfileStore(requireOpenClawAgentDir()); + return readTestAuthProfileStore(resolveAgentDir({} as OpenClawConfig, "main")); } async function readAuthProfilesForAgentDir(agentDir: string) { return readTestAuthProfileStore(agentDir); diff --git a/src/commands/doctor-auth-flat-profiles.ts b/src/commands/doctor-auth-flat-profiles.ts index 06620850b0f..8fcf9ed4bb1 100644 --- a/src/commands/doctor-auth-flat-profiles.ts +++ b/src/commands/doctor-auth-flat-profiles.ts @@ -1,7 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; -import { resolveAgentDir, listAgentIds } from "../agents/agent-scope.js"; +import { resolveAgentDir, resolveDefaultAgentDir, listAgentIds } from "../agents/agent-scope.js"; import { AUTH_STORE_VERSION } from "../agents/auth-profiles/constants.js"; import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js"; import { @@ -176,7 +175,7 @@ function listExistingAgentDirsFromState(): string[] { function listAuthProfileRepairCandidates(cfg: OpenClawConfig): AuthProfileRepairCandidate[] { const candidates = new Map(); - addCandidate(candidates, resolveOpenClawAgentDir()); + addCandidate(candidates, resolveDefaultAgentDir(cfg)); for (const agentId of listAgentIds(cfg)) { addCandidate(candidates, resolveAgentDir(cfg, agentId)); } diff --git a/src/commands/models.list.e2e.test.ts b/src/commands/models.list.e2e.test.ts index 4fcbe60b040..16c432686d0 100644 --- a/src/commands/models.list.e2e.test.ts +++ b/src/commands/models.list.e2e.test.ts @@ -14,7 +14,6 @@ const readConfigFileSnapshotForWrite = vi.fn().mockResolvedValue({ writeOptions: {}, }); const setRuntimeConfigSnapshot = vi.fn(); -const resolveOpenClawAgentDir = vi.fn().mockReturnValue("/tmp/openclaw-agent"); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const listProfilesForProvider = vi.fn().mockReturnValue([]); const resolveEnvApiKey = vi.fn().mockReturnValue(undefined); @@ -55,10 +54,6 @@ vi.mock("./models/load-config.js", () => ({ }), })); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir, -})); - vi.mock("../agents/auth-profiles/profile-list.js", () => ({ listProfilesForProvider, })); @@ -512,8 +507,9 @@ describe("models list/status", () => { loadProviderCatalogModelsForList.mockResolvedValueOnce([MOONSHOT_MODEL]); const runtime = makeRuntime(); - await withEnvAsync({ KIMI_API_KEY: undefined, MOONSHOT_API_KEY: undefined }, () => - modelsListCommand({ all: true, provider: "moonshot", json: true }, runtime), + await withEnvAsync( + { KIMI_API_KEY: undefined, KIMICODE_API_KEY: undefined, MOONSHOT_API_KEY: undefined }, + () => modelsListCommand({ all: true, provider: "moonshot", json: true }, runtime), ); const payload = parseJsonLog(runtime); diff --git a/src/commands/models/list.list-command.forward-compat.test.ts b/src/commands/models/list.list-command.forward-compat.test.ts index a6111207a59..90859db81e9 100644 --- a/src/commands/models/list.list-command.forward-compat.test.ts +++ b/src/commands/models/list.list-command.forward-compat.test.ts @@ -45,7 +45,7 @@ const mocks = vi.hoisted(() => { loadModelsConfigWithSource: vi.fn(), ensureOpenClawModelsJson: vi.fn(), ensureAuthProfileStore: vi.fn(), - resolveOpenClawAgentDir: vi.fn(), + resolveDefaultAgentDir: vi.fn(), loadModelRegistry: vi.fn(), loadModelCatalog: vi.fn(), loadProviderCatalogModelsForList: vi.fn(), @@ -69,7 +69,7 @@ function resetMocks() { }); mocks.ensureOpenClawModelsJson.mockResolvedValue({ wrote: false }); mocks.ensureAuthProfileStore.mockReturnValue({ version: 1, profiles: {}, order: {} }); - mocks.resolveOpenClawAgentDir.mockReturnValue("/tmp/openclaw-agent"); + mocks.resolveDefaultAgentDir.mockReturnValue("/tmp/openclaw-agent"); mocks.loadModelRegistry.mockResolvedValue({ models: [], availableKeys: new Set(), @@ -201,8 +201,10 @@ function installModelsListCommandForwardCompatMocks() { loadAuthProfileStoreWithoutExternalProfiles: mocks.ensureAuthProfileStore, })); - vi.doMock("../../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir, + vi.doMock("../../agents/agent-scope.js", () => ({ + resolveAgentWorkspaceDir: vi.fn(() => "/tmp/openclaw-workspace"), + resolveDefaultAgentDir: mocks.resolveDefaultAgentDir, + resolveDefaultAgentId: vi.fn(() => "main"), })); vi.doMock("../../agents/model-catalog.js", () => ({ diff --git a/src/commands/models/list.list-command.ts b/src/commands/models/list.list-command.ts index 320b165d9f8..5e8802dbe58 100644 --- a/src/commands/models/list.list-command.ts +++ b/src/commands/models/list.list-command.ts @@ -71,12 +71,10 @@ export async function modelsListCommand( } const [ { loadAuthProfileStoreWithoutExternalProfiles }, - { resolveOpenClawAgentDir }, - { resolveAgentWorkspaceDir, resolveDefaultAgentId }, + { resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId }, { resolveDefaultAgentWorkspaceDir }, ] = await Promise.all([ import("../../agents/auth-profiles/store.js"), - import("../../agents/agent-paths.js"), import("../../agents/agent-scope.js"), import("../../agents/workspace.js"), ]); @@ -84,8 +82,8 @@ export async function modelsListCommand( commandName: "models list", runtime, }); - const authStore = loadAuthProfileStoreWithoutExternalProfiles(); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); + const authStore = loadAuthProfileStoreWithoutExternalProfiles(agentDir); const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)) ?? resolveDefaultAgentWorkspaceDir(); const authIndex = createModelListAuthIndex({ cfg, authStore, workspaceDir }); diff --git a/src/commands/models/list.probe.ts b/src/commands/models/list.probe.ts index 761b294fa24..381db9b799e 100644 --- a/src/commands/models/list.probe.ts +++ b/src/commands/models/list.probe.ts @@ -1,7 +1,10 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; -import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js"; +import { + resolveAgentDir, + resolveAgentWorkspaceDir, + resolveDefaultAgentId, +} from "../../agents/agent-scope.js"; import { type AuthProfileCredential, type AuthProfileEligibilityReasonCode, @@ -522,7 +525,7 @@ async function runTargetsWithConcurrency(params: { const concurrency = Math.max(1, Math.min(targets.length || 1, params.concurrency)); const agentId = params.agentId ?? resolveDefaultAgentId(cfg); - const agentDir = params.agentDir ?? resolveOpenClawAgentDir(); + const agentDir = params.agentDir ?? resolveAgentDir(cfg, agentId); const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(cfg, agentId) ?? diff --git a/src/commands/models/list.registry-load.ts b/src/commands/models/list.registry-load.ts index 8e9d50a2f5c..997962ab81f 100644 --- a/src/commands/models/list.registry-load.ts +++ b/src/commands/models/list.registry-load.ts @@ -1,6 +1,6 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../../agents/agent-scope.js"; import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -51,7 +51,7 @@ export function loadConfiguredListModelRegistry( entries: ConfiguredEntry[], opts?: { providerFilter?: string; workspaceDir?: string }, ) { - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir, { readOnly: true, config: cfg, diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 6513044ed8c..ce810efe797 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -1,6 +1,6 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../../agents/agent-scope.js"; import { shouldSuppressBuiltInModel, shouldSuppressBuiltInModelFromManifest, @@ -95,7 +95,7 @@ export async function loadModelRegistry( }, ) { const runtimeSuppression = opts?.normalizeModels !== false; - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir, { readOnly: true, skipCredentials: opts?.loadAvailability === false, diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index c55d615e386..6517e099a55 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -1,5 +1,4 @@ import path from "node:path"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; import { resolveAgentDir, resolveAgentExplicitModelPrimary, @@ -45,7 +44,7 @@ import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { colorize, theme } from "../../terminal/theme.js"; -import { shortenHomePath } from "../../utils.js"; +import { resolveUserPath, shortenHomePath } from "../../utils.js"; import { resolveProviderAuthOverview } from "./list.auth-overview.js"; import { isRich } from "./list.format.js"; import { type AuthProbeSummary } from "./list.probe.js"; @@ -59,6 +58,11 @@ import { type ProviderUsageRuntime = typeof import("../../infra/provider-usage.js"); type ProgressRuntime = typeof import("../../cli/progress.js"); + +function resolveEnvAgentDirOverride(env: NodeJS.ProcessEnv = process.env): string | undefined { + const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim(); + return override ? resolveUserPath(override, env) : undefined; +} type TerminalTableRuntime = typeof import("../../terminal/table.js"); type ListProbeRuntime = typeof import("./list.probe.js"); @@ -173,8 +177,10 @@ export async function modelsStatusCommand( const configPath = createConfigIO().configPath; const cfg = await loadModelsConfig({ commandName: "models status", runtime }); const agentId = resolveKnownAgentId({ cfg, rawAgentId: opts.agent }); - const agentDir = agentId ? resolveAgentDir(cfg, agentId) : resolveOpenClawAgentDir(); const workspaceAgentId = agentId ?? resolveDefaultAgentId(cfg); + const agentDir = agentId + ? resolveAgentDir(cfg, agentId) + : (resolveEnvAgentDirOverride() ?? resolveAgentDir(cfg, workspaceAgentId)); const workspaceDir = resolveAgentWorkspaceDir(cfg, workspaceAgentId) ?? resolveDefaultAgentWorkspaceDir(); const agentModelPrimary = agentId ? resolveAgentExplicitModelPrimary(cfg, agentId) : undefined; diff --git a/src/commands/models/list.status.test.ts b/src/commands/models/list.status.test.ts index 82d16db8b3b..44e7602e9e2 100644 --- a/src/commands/models/list.status.test.ts +++ b/src/commands/models/list.status.test.ts @@ -35,7 +35,6 @@ const mocks = vi.hoisted(() => { return { store, - resolveOpenClawAgentDir: vi.fn().mockReturnValue("/tmp/openclaw-agent"), resolveAgentDir: vi.fn().mockReturnValue("/tmp/openclaw-agent"), resolveAgentWorkspaceDir: vi.fn().mockReturnValue("/tmp/openclaw-agent/workspace"), resolveDefaultAgentId: vi.fn().mockReturnValue("main"), @@ -139,9 +138,6 @@ const mocks = vi.hoisted(() => { }; }); -vi.mock("../../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir, -})); vi.mock("../../agents/agent-scope.js", () => ({ resolveAgentDir: mocks.resolveAgentDir, resolveAgentWorkspaceDir: mocks.resolveAgentWorkspaceDir, @@ -310,7 +306,7 @@ describe("modelsStatusCommand auth overview", () => { await modelsStatusCommand({ json: true }, runtime as never); const payload = JSON.parse(String((runtime.log as Mock).mock.calls[0]?.[0])); - expect(mocks.resolveOpenClawAgentDir).toHaveBeenCalled(); + expect(mocks.resolveAgentDir).toHaveBeenCalledWith(expect.anything(), "main"); expect(mocks.ensureAuthProfileStore).toHaveBeenCalled(); expect(payload.defaultModel).toBe("anthropic/claude-opus-4-6"); expect(payload.configPath).toBe("/tmp/openclaw-dev/openclaw.json"); @@ -362,6 +358,28 @@ describe("modelsStatusCommand auth overview", () => { ).toBe(true); }); + it("honors OPENCLAW_AGENT_DIR when no --agent override is provided", async () => { + const localRuntime = createRuntime(); + const previous = process.env.OPENCLAW_AGENT_DIR; + process.env.OPENCLAW_AGENT_DIR = "/tmp/openclaw-isolated-agent"; + mocks.resolveAgentDir.mockClear(); + try { + await modelsStatusCommand({ json: true }, localRuntime as never); + } finally { + if (previous === undefined) { + delete process.env.OPENCLAW_AGENT_DIR; + } else { + process.env.OPENCLAW_AGENT_DIR = previous; + } + } + + expect(mocks.resolveAgentDir).not.toHaveBeenCalled(); + expect(mocks.ensureAuthProfileStore).toHaveBeenCalledWith("/tmp/openclaw-isolated-agent"); + const payload = JSON.parse(String((localRuntime.log as Mock).mock.calls[0]?.[0])); + expect(payload.agentDir).toBe("/tmp/openclaw-isolated-agent"); + expect(payload.auth.storePath).toBe("/tmp/openclaw-isolated-agent/auth-profiles.json"); + }); + it("uses agent overrides and reports sources", async () => { const localRuntime = createRuntime(); await withAgentScopeOverrides( diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index 2db248408be..fe8f340be21 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -26,10 +26,6 @@ const providerEnvVarsById = vi.hoisted( }), ); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => process.env.OPENCLAW_AGENT_DIR ?? "/tmp/openclaw-agent", -})); - vi.mock("../config/paths.js", () => ({ resolveStateDir: () => process.env.OPENCLAW_STATE_DIR ?? "/tmp/openclaw-state", })); @@ -39,7 +35,8 @@ vi.mock("../agents/auth-profiles/profiles.js", async () => { const path = await import("node:path"); return { upsertAuthProfile: (params: { profileId: string; credential: unknown; agentDir?: string }) => { - const agentDir = params.agentDir ?? process.env.OPENCLAW_AGENT_DIR ?? "/tmp/openclaw-agent"; + const stateDir = process.env.OPENCLAW_STATE_DIR ?? "/tmp/openclaw-state"; + const agentDir = params.agentDir ?? path.join(stateDir, "agents", "main", "agent"); const file = path.join(agentDir, "auth-profiles.json"); fs.mkdirSync(agentDir, { recursive: true }); const existing = (() => { @@ -99,9 +96,10 @@ describe("writeOAuthCredentials", () => { await lifecycle.cleanup(); }); - it("writes auth-profiles.json under OPENCLAW_AGENT_DIR when set", async () => { + it("writes auth-profiles.json under the default agent dir", async () => { const env = await setupAuthTestEnv("openclaw-oauth-"); lifecycle.setStateDir(env.stateDir); + const defaultAgentDir = path.join(env.stateDir, "agents", "main", "agent"); const creds = { refresh: "refresh-token", @@ -113,7 +111,7 @@ describe("writeOAuthCredentials", () => { const parsed = await readAuthProfilesForAgent<{ profiles?: Record; - }>(env.agentDir); + }>(defaultAgentDir); expect(parsed.profiles?.["openai-codex:default"]).toMatchObject({ refresh: "refresh-token", access: "access-token", @@ -121,7 +119,7 @@ describe("writeOAuthCredentials", () => { }); await expect( - fs.readFile(path.join(env.stateDir, "agents", "main", "agent", "auth-profiles.json"), "utf8"), + fs.readFile(path.join(env.agentDir, "auth-profiles.json"), "utf8"), ).rejects.toThrow(); }); @@ -393,15 +391,16 @@ describe("upsertApiKeyProfile", () => { await lifecycle.cleanup(); }); - it("writes to OPENCLAW_AGENT_DIR when set", async () => { + it("writes to the default agent dir", async () => { const env = await setupAuthTestEnv("openclaw-minimax-", { agentSubdir: "custom-agent" }); lifecycle.setStateDir(env.stateDir); + const defaultAgentDir = path.join(env.stateDir, "agents", "main", "agent"); upsertApiKeyProfile({ provider: "minimax", input: "sk-minimax-test" }); const parsed = await readAuthProfilesForAgent<{ profiles?: Record; - }>(env.agentDir); + }>(defaultAgentDir); expect(parsed.profiles?.["minimax:default"]).toMatchObject({ type: "api_key", provider: "minimax", @@ -409,7 +408,7 @@ describe("upsertApiKeyProfile", () => { }); await expect( - fs.readFile(path.join(env.stateDir, "agents", "main", "agent", "auth-profiles.json"), "utf8"), + fs.readFile(path.join(env.agentDir, "auth-profiles.json"), "utf8"), ).rejects.toThrow(); }); }); diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 58567207dbe..7dfcedd4095 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -5,8 +5,7 @@ import os from "node:os"; import path from "node:path"; import type { Api, Model } from "@mariozechner/pi-ai"; import { afterEach, describe, expect, it } from "vitest"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; -import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; +import { resolveAgentWorkspaceDir, resolveDefaultAgentDir } from "../agents/agent-scope.js"; import { ensureAuthProfileStore, saveAuthProfileStore } from "../agents/auth-profiles/store.js"; import type { AuthProfileStore } from "../agents/auth-profiles/types.js"; import { @@ -1425,7 +1424,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { process.env.OPENCLAW_GATEWAY_TOKEN = token; const agentId = "dev"; - const hostAgentDir = resolveOpenClawAgentDir(); + const hostAgentDir = resolveDefaultAgentDir(getRuntimeConfig()); const hostStore = ensureAuthProfileStore(hostAgentDir, { allowKeychainPrompt: false, }); @@ -1469,7 +1468,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { const toolProbePath = path.join(workspaceDir, `.openclaw-live-tool-probe.${nonceA}.txt`); await fs.writeFile(toolProbePath, `nonceA=${nonceA}\nnonceB=${nonceB}\n`); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(params.cfg); const sanitizedCfg: OpenClawConfig = { ...params.cfg, auth: await sanitizeAuthConfig({ cfg: params.cfg, agentDir }), @@ -2169,7 +2168,7 @@ describeLive("gateway live (dev agent, profile keys)", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const all = modelRegistry.getAll(); @@ -2319,7 +2318,7 @@ describeLive("gateway live (dev agent, profile keys)", () => { const cfg = getRuntimeConfig(); await ensureOpenClawModelsJson(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); const anthropic = modelRegistry.find("anthropic", "claude-opus-4-6") as Model | null; diff --git a/src/gateway/server-methods/models-auth-status.test.ts b/src/gateway/server-methods/models-auth-status.test.ts index 445578c0286..26cd1c30c71 100644 --- a/src/gateway/server-methods/models-auth-status.test.ts +++ b/src/gateway/server-methods/models-auth-status.test.ts @@ -4,7 +4,7 @@ import type { GatewayRequestHandlerOptions } from "./types.js"; const mocks = vi.hoisted(() => ({ getRuntimeConfig: vi.fn(() => ({})), - resolveOpenClawAgentDir: vi.fn(() => "/tmp/agent"), + resolveDefaultAgentDir: vi.fn(() => "/tmp/agent"), ensureAuthProfileStore: vi.fn((agentDir?: string, options?: unknown) => { void agentDir; void options; @@ -20,8 +20,8 @@ vi.mock("../../config/config.js", () => ({ getRuntimeConfig: mocks.getRuntimeConfig, })); -vi.mock("../../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir, +vi.mock("../../agents/agent-scope.js", () => ({ + resolveDefaultAgentDir: mocks.resolveDefaultAgentDir, })); vi.mock("../../agents/auth-profiles.js", async () => { diff --git a/src/gateway/server-methods/models-auth-status.ts b/src/gateway/server-methods/models-auth-status.ts index 51de215ed3d..6f7d637046c 100644 --- a/src/gateway/server-methods/models-auth-status.ts +++ b/src/gateway/server-methods/models-auth-status.ts @@ -1,4 +1,4 @@ -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../../agents/agent-scope.js"; import { type AuthHealthSummary, type AuthProfileHealthStatus, @@ -291,7 +291,7 @@ export const modelsAuthStatusHandlers: GatewayRequestHandlers = { } try { const cfg = context.getRuntimeConfig(); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const store = ensureAuthProfileStore(agentDir, { externalCli: externalCliDiscoveryForConfigStatus({ cfg }), }); diff --git a/src/gateway/server-startup-post-attach.test.ts b/src/gateway/server-startup-post-attach.test.ts index e7efc7b1359..07272ecbb1e 100644 --- a/src/gateway/server-startup-post-attach.test.ts +++ b/src/gateway/server-startup-post-attach.test.ts @@ -35,7 +35,7 @@ const hoisted = vi.hoisted(() => { })); const resolveAgentModelPrimaryValue = vi.fn(() => ""); const normalizeProviderId = vi.fn((provider: string) => provider.toLowerCase()); - const resolveOpenClawAgentDir = vi.fn(() => "/tmp/openclaw-state/agents/default/agent"); + const resolveDefaultAgentDir = vi.fn(() => "/tmp/openclaw-state/agents/default/agent"); const isCliProvider = vi.fn(() => false); const resolveConfiguredModelRef = vi.fn(() => ({ provider: "openai", @@ -64,7 +64,7 @@ const hoisted = vi.hoisted(() => { reconcilePendingSessionIdentities, resolveAgentModelPrimaryValue, normalizeProviderId, - resolveOpenClawAgentDir, + resolveDefaultAgentDir, isCliProvider, resolveConfiguredModelRef, resolveEmbeddedAgentRuntime, @@ -154,11 +154,8 @@ vi.mock("../agents/provider-id.js", () => ({ normalizeProviderId: hoisted.normalizeProviderId, })); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: hoisted.resolveOpenClawAgentDir, -})); - vi.mock("../agents/agent-scope.js", () => ({ + resolveDefaultAgentDir: hoisted.resolveDefaultAgentDir, resolveAgentWorkspaceDir: vi.fn(() => "/tmp/openclaw-workspace"), resolveDefaultAgentId: vi.fn(() => "default"), })); @@ -218,7 +215,7 @@ describe("startGatewayPostAttachRuntime", () => { hoisted.resolveAgentModelPrimaryValue.mockReset(); hoisted.resolveAgentModelPrimaryValue.mockReturnValue(""); hoisted.normalizeProviderId.mockClear(); - hoisted.resolveOpenClawAgentDir.mockClear(); + hoisted.resolveDefaultAgentDir.mockClear(); hoisted.isCliProvider.mockReset(); hoisted.isCliProvider.mockReturnValue(false); hoisted.resolveConfiguredModelRef.mockClear(); @@ -576,6 +573,33 @@ describe("startGatewayPostAttachRuntime", () => { } }); + it("prewarms models.json in the configured default agent dir", async () => { + const cfg = { + agents: { + defaults: { model: "openai/gpt-5.4" }, + list: [{ id: "main" }, { id: "ops", default: true }], + }, + } as never; + hoisted.resolveAgentModelPrimaryValue.mockReturnValue("openai/gpt-5.4"); + hoisted.resolveDefaultAgentDir.mockReturnValue("/tmp/openclaw-state/agents/ops/agent"); + + await __testing.prewarmConfiguredPrimaryModel({ + cfg, + workspaceDir: "/tmp/openclaw-workspace", + log: { warn: vi.fn() }, + }); + + expect(hoisted.resolveDefaultAgentDir).toHaveBeenCalledWith(cfg); + expect(hoisted.ensureOpenClawModelsJson).toHaveBeenCalledWith( + cfg, + "/tmp/openclaw-state/agents/ops/agent", + expect.objectContaining({ + workspaceDir: "/tmp/openclaw-workspace", + providerDiscoveryProviderIds: ["openai"], + }), + ); + }); + it("starts channels without waiting for primary model prewarm completion", async () => { await withEnvAsync( { OPENCLAW_SKIP_CHANNELS: undefined, OPENCLAW_SKIP_PROVIDERS: undefined }, diff --git a/src/gateway/server-startup-post-attach.ts b/src/gateway/server-startup-post-attach.ts index 2c541361859..1a14ba681f7 100644 --- a/src/gateway/server-startup-post-attach.ts +++ b/src/gateway/server-startup-post-attach.ts @@ -271,13 +271,11 @@ async function prewarmConfiguredPrimaryModel(params: { return; } const [ - { resolveOpenClawAgentDir }, - { resolveAgentWorkspaceDir, resolveDefaultAgentId }, + { resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId }, { DEFAULT_MODEL, DEFAULT_PROVIDER }, { isCliProvider, resolveConfiguredModelRef }, { resolveEmbeddedAgentRuntime }, ] = await Promise.all([ - import("../agents/agent-paths.js"), import("../agents/agent-scope.js"), import("../agents/defaults.js"), import("../agents/model-selection.js"), @@ -297,7 +295,7 @@ async function prewarmConfiguredPrimaryModel(params: { } // Keep startup prewarm metadata-only; resolving models can import provider runtimes and block readiness. const { ensureOpenClawModelsJson } = await import("../agents/models-config.js"); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(params.cfg); const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg)); try { diff --git a/src/gateway/server-startup.test.ts b/src/gateway/server-startup.test.ts index abab9c98d19..dfa361b9859 100644 --- a/src/gateway/server-startup.test.ts +++ b/src/gateway/server-startup.test.ts @@ -11,11 +11,8 @@ const ensureOpenClawModelsJsonMock = vi.fn< const piModelModuleLoadedMock = vi.fn(); const resolveEmbeddedAgentRuntimeMock = vi.fn(() => "auto"); -vi.mock("../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => "/tmp/agent", -})); - vi.mock("../agents/agent-scope.js", () => ({ + resolveDefaultAgentDir: () => "/tmp/agent", resolveAgentWorkspaceDir: () => "/tmp/workspace", resolveDefaultAgentId: () => "default", })); diff --git a/src/gateway/server.config-patch.test.ts b/src/gateway/server.config-patch.test.ts index fc87da639b3..98b328d83d8 100644 --- a/src/gateway/server.config-patch.test.ts +++ b/src/gateway/server.config-patch.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../agents/agent-scope.js"; import { AUTH_PROFILE_FILENAME } from "../agents/auth-profiles/constants.js"; import { __testing as controlPlaneRateLimitTesting } from "./control-plane-rate-limit.js"; import { @@ -72,7 +72,7 @@ async function expectSchemaLookupInvalid(path: unknown) { async function writeUnresolvedAuthProfileTokenRef(missingEnvVar: string) { delete process.env[missingEnvVar]; - const authStorePath = path.join(resolveOpenClawAgentDir(), AUTH_PROFILE_FILENAME); + const authStorePath = path.join(resolveDefaultAgentDir({}), AUTH_PROFILE_FILENAME); await fs.mkdir(path.dirname(authStorePath), { recursive: true }); await fs.writeFile( authStorePath, diff --git a/src/plugin-sdk/agent-dir-compat.ts b/src/plugin-sdk/agent-dir-compat.ts new file mode 100644 index 00000000000..04957f860c5 --- /dev/null +++ b/src/plugin-sdk/agent-dir-compat.ts @@ -0,0 +1,11 @@ +import { resolveDefaultAgentDir } from "../agents/agent-scope-config.js"; +import { resolveUserPath } from "../utils.js"; + +/** + * @deprecated Prefer resolveAgentDir(cfg, agentId) or resolveDefaultAgentDir(cfg). + * Kept for third-party plugin SDK compatibility. + */ +export function resolveOpenClawAgentDir(env: NodeJS.ProcessEnv = process.env): string { + const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim(); + return override ? resolveUserPath(override, env) : resolveDefaultAgentDir({}, env); +} diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index fc5a4c81ba6..16698cffabd 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -86,7 +86,7 @@ export { filterToolResultMediaUrls, } from "../agents/pi-embedded-subscribe.tools.js"; export { normalizeUsage } from "../agents/usage.js"; -export { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +export { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; export { resolveSessionAgentIds } from "../agents/agent-scope.js"; export { resolveModelAuthMode } from "../agents/model-auth.js"; export { supportsModelTools } from "../agents/model-tool-support.js"; diff --git a/src/plugin-sdk/agent-runtime.ts b/src/plugin-sdk/agent-runtime.ts index 4f562c00e5a..3a689433964 100644 --- a/src/plugin-sdk/agent-runtime.ts +++ b/src/plugin-sdk/agent-runtime.ts @@ -1,7 +1,7 @@ // Public agent/model/runtime helpers for plugins that integrate with core agent flows. export * from "../agents/agent-scope.js"; -export { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +export { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; export * from "../agents/current-time.js"; export * from "../agents/date-time.js"; export * from "../agents/defaults.js"; diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 00f95adb21d..07191d4988c 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -1,7 +1,7 @@ // Public auth/onboarding helpers for provider plugins. import path from "node:path"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../agents/agent-scope-config.js"; import { resolveApiKeyForProfile } from "../agents/auth-profiles/oauth.js"; import { resolveAuthProfileOrder } from "../agents/auth-profiles/order.js"; import { listProfilesForProvider } from "../agents/auth-profiles/profiles.js"; @@ -72,7 +72,7 @@ export { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth export { coerceSecretRef, hasConfiguredSecretInput } from "../config/types.secrets.js"; export { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js"; export { resolveRequiredHomeDir } from "../infra/home-dir.js"; -export { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +export { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; export { normalizeOptionalSecretInput, normalizeSecretInput, @@ -279,7 +279,7 @@ export function listUsableProviderAuthProfileIds(params: { agentDir?: string; }): { agentDir: string; profileIds: string[] } { try { - const agentDir = params.agentDir?.trim() || resolveOpenClawAgentDir(); + const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.cfg ?? {}); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false, }); diff --git a/src/plugins/provider-auth-choice.ts b/src/plugins/provider-auth-choice.ts index 497735fad7d..0f9184e9e35 100644 --- a/src/plugins/provider-auth-choice.ts +++ b/src/plugins/provider-auth-choice.ts @@ -1,4 +1,3 @@ -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; import { resolveDefaultAgentId, resolveAgentDir, @@ -221,12 +220,7 @@ export async function runProviderPluginAuthMethod(params: { opts?: Partial; }): Promise<{ config: OpenClawConfig; defaultModel?: string }> { const agentId = params.agentId ?? resolveDefaultAgentId(params.config); - const defaultAgentId = resolveDefaultAgentId(params.config); - const agentDir = - params.agentDir ?? - (agentId === defaultAgentId - ? resolveOpenClawAgentDir() - : resolveAgentDir(params.config, agentId)); + const agentDir = params.agentDir ?? resolveAgentDir(params.config, agentId); const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.config, agentId) ?? @@ -469,10 +463,7 @@ export async function applyAuthChoicePluginProvider( } const agentId = params.agentId ?? resolveDefaultAgentId(nextConfig); - const defaultAgentId = resolveDefaultAgentId(nextConfig); - const agentDir = - params.agentDir ?? - (agentId === defaultAgentId ? resolveOpenClawAgentDir() : resolveAgentDir(nextConfig, agentId)); + const agentDir = params.agentDir ?? resolveAgentDir(nextConfig, agentId); const workspaceDir = resolveAgentWorkspaceDir(nextConfig, agentId) ?? resolveDefaultAgentWorkspaceDir(); diff --git a/src/plugins/provider-auth-helpers.ts b/src/plugins/provider-auth-helpers.ts index 8730904a6b9..b8e56335c3a 100644 --- a/src/plugins/provider-auth-helpers.ts +++ b/src/plugins/provider-auth-helpers.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import type { OAuthCredentials } from "@mariozechner/pi-ai"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../agents/agent-scope-config.js"; import { buildAuthProfileId } from "../agents/auth-profiles/identity.js"; import { upsertAuthProfile } from "../agents/auth-profiles/profiles.js"; import { resolveProviderIdForAuth } from "../agents/provider-auth-aliases.js"; @@ -19,7 +19,8 @@ import type { SecretInputMode } from "./provider-auth-types.js"; const ENV_REF_PATTERN = /^\$\{([A-Z][A-Z0-9_]*)\}$/; -const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); +const resolveAuthAgentDir = (agentDir?: string, config?: OpenClawConfig) => + agentDir ?? resolveDefaultAgentDir(config ?? {}); export type ApiKeyStorageOptions = { secretInputMode?: SecretInputMode; @@ -127,7 +128,7 @@ export function upsertApiKeyProfile(params: { params.metadata, params.options, ), - agentDir: resolveAuthAgentDir(params.agentDir), + agentDir: resolveAuthAgentDir(params.agentDir, params.options?.config), }); return profileId; } diff --git a/src/secrets/runtime.ts b/src/secrets/runtime.ts index 0bcc48386b7..b6faba6e896 100644 --- a/src/secrets/runtime.ts +++ b/src/secrets/runtime.ts @@ -1,8 +1,8 @@ -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; import { listAgentIds, resolveAgentDir, resolveAgentWorkspaceDir, + resolveDefaultAgentDir, resolveDefaultAgentId, } from "../agents/agent-scope.js"; import { @@ -114,7 +114,7 @@ function collectCandidateAgentDirs( env: NodeJS.ProcessEnv = process.env, ): string[] { const dirs = new Set(); - dirs.add(resolveUserPath(resolveOpenClawAgentDir(env), env)); + dirs.add(resolveUserPath(resolveDefaultAgentDir(config, env), env)); for (const agentId of listAgentIds(config)) { dirs.add(resolveUserPath(resolveAgentDir(config, agentId, env), env)); } diff --git a/src/utils/usage-format.ts b/src/utils/usage-format.ts index 02033ca9c27..b940cad1415 100644 --- a/src/utils/usage-format.ts +++ b/src/utils/usage-format.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../agents/agent-scope-config.js"; import { modelKey, normalizeModelRef, normalizeProviderId } from "../agents/model-selection.js"; import type { NormalizedUsage } from "../agents/usage.js"; import type { ModelProviderConfig } from "../config/types.models.js"; @@ -204,7 +204,7 @@ function loadModelsJsonCostIndex(options?: { allowPluginNormalization?: boolean; }): Map { const useRawEntries = options?.allowPluginNormalization === false; - const modelsPath = path.join(resolveOpenClawAgentDir(), "models.json"); + const modelsPath = path.join(resolveDefaultAgentDir({}), "models.json"); try { const stat = fs.statSync(modelsPath); if ( diff --git a/test/image-generation.runtime.live.test.ts b/test/image-generation.runtime.live.test.ts index b134ed7f568..176ed07ee51 100644 --- a/test/image-generation.runtime.live.test.ts +++ b/test/image-generation.runtime.live.test.ts @@ -3,7 +3,7 @@ import { requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; import { describe, expect, it } from "vitest"; -import { resolveOpenClawAgentDir } from "../src/agents/agent-paths.js"; +import { resolveDefaultAgentDir } from "../src/agents/agent-scope.js"; import { collectProviderApiKeys } from "../src/agents/live-auth-keys.js"; import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "../src/agents/live-test-helpers.js"; import { resolveApiKeyForProvider } from "../src/agents/model-auth.js"; @@ -199,7 +199,7 @@ describeLive("image generation live (provider sweep)", () => { async () => { const cfg = withPluginsEnabled(loadConfig()); const configuredModels = resolveConfiguredLiveImageModels(cfg); - const agentDir = resolveOpenClawAgentDir(); + const agentDir = resolveDefaultAgentDir(cfg); const attempted: string[] = []; const skipped: string[] = []; const failures: string[] = [];