refactor: remove legacy agent dir resolver

This commit is contained in:
Peter Steinberger
2026-05-05 20:06:09 +01:00
parent e8a9c766c2
commit 35da7d2c99
70 changed files with 302 additions and 218 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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` |
</Accordion>

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,
});
}

View File

@@ -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 () => {

View File

@@ -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<typeof resolveCodexAppServerAuthProfileIdForAgent>[0]["config"];
}): Promise<CodexAppServerClient> {
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<typeof resolveCodexAppServerAuthProfileIdForAgent>[0]["config"];
}): Promise<CodexAppServerClient> {
const agentDir = options?.agentDir ?? resolveOpenClawAgentDir();
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
authProfileId: options?.authProfileId,
agentDir,

View File

@@ -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);
});

View File

@@ -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,

View File

@@ -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[] = [];

View File

@@ -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<void> {
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[] = [];

View File

@@ -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,
});

View File

@@ -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);
}

View File

@@ -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: {

View File

@@ -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,

View File

@@ -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<TokenSource> {
};
}
const agentDir = resolveOpenClawAgentDir();
const agentDir = resolveDefaultAgentDir(getRuntimeConfig());
const store = ensureAuthProfileStore(agentDir, {
allowKeychainPrompt: false,
});

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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/);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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<void> {
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,

View File

@@ -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<LiveResolvedModel> {
const cfg = getRuntimeConfig();
await ensureOpenClawModelsJson(cfg);
const agentDir = resolveOpenClawAgentDir();
const agentDir = resolveDefaultAgentDir(cfg);
const authStorage = discoverAuthStorage(agentDir);
const models = discoverModels(authStorage, agentDir).getAll();

View File

@@ -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");

View File

@@ -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<ModelCatalogEntry[]> {
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<string, unknown>;
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(

View File

@@ -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<string, ParsedProviderConfig> };
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<

View File

@@ -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<T>(agentDir = resolveOpenClawAgentDir()): Promise<T> {
export async function readGeneratedModelsJson<T>(
agentDir = resolveDefaultAgentDir({}),
): Promise<T> {
const modelPath = path.join(agentDir, "models.json");
const raw = await fs.readFile(modelPath, "utf8");
return JSON.parse(raw) as T;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,

View File

@@ -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<Api> | 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<Api> | null;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
});

View File

@@ -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()

View File

@@ -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",

View File

@@ -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 },
);
});

View File

@@ -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,

View File

@@ -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(),
);

View File

@@ -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<Api> | 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<Api> | null;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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<string, AuthProfileRepairCandidate>();
addCandidate(candidates, resolveOpenClawAgentDir());
addCandidate(candidates, resolveDefaultAgentDir(cfg));
for (const agentId of listAgentIds(cfg)) {
addCandidate(candidates, resolveAgentDir(cfg, agentId));
}

View File

@@ -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);

View File

@@ -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", () => ({

View File

@@ -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 });

View File

@@ -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) ??

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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(

View File

@@ -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<string, OAuthCredentials & { type?: string }>;
}>(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<string, { type?: string; provider?: string; key?: string }>;
}>(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();
});
});

View File

@@ -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<Api> | null;

View File

@@ -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 () => {

View File

@@ -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 }),
});

View File

@@ -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 },

View File

@@ -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 {

View File

@@ -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",
}));

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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,
});

View File

@@ -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<ProviderAuthOptionBag>;
}): 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();

View File

@@ -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;
}

View File

@@ -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<string>();
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));
}

View File

@@ -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<string, ModelCostConfig> {
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 (

View File

@@ -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[] = [];