diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index e58c23078..b05c91424 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -41,6 +41,7 @@ export function registerModelsCli(program: Command) { .description("Model discovery, scanning, and configuration") .option("--status-json", "Output JSON (alias for `models status --json`)", false) .option("--status-plain", "Plain output (alias for `models status --plain`)", false) + .option("--agent ", "Agent id (default: configured default agent)") .addHelpText( "after", () => @@ -85,6 +86,7 @@ export function registerModelsCli(program: Command) { .option("--probe-timeout ", "Per-probe timeout in ms") .option("--probe-concurrency ", "Concurrent probes") .option("--probe-max-tokens ", "Probe max tokens (best-effort)") + .option("--agent ", "Agent id (default: configured default agent)") .action(async (opts) => { await runModelsCommand(async () => { await modelsStatusCommand( @@ -98,6 +100,7 @@ export function registerModelsCli(program: Command) { probeTimeout: opts.probeTimeout as string | undefined, probeConcurrency: opts.probeConcurrency as string | undefined, probeMaxTokens: opts.probeMaxTokens as string | undefined, + agent: opts.agent as string | undefined, }, defaultRuntime, ); @@ -271,6 +274,7 @@ export function registerModelsCli(program: Command) { { json: Boolean(opts?.statusJson), plain: Boolean(opts?.statusPlain), + agent: opts?.agent as string | undefined, }, defaultRuntime, ); diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index ce1d05489..ad628f08c 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; +import { resolveAgentDir, resolveDefaultAgentId } from "../../agents/agent-scope.js"; import { buildAuthHealthSummary, DEFAULT_OAUTH_WARN_MS, @@ -40,6 +40,7 @@ import { sortProbeResults, type AuthProbeSummary, } from "./list.probe.js"; +import { normalizeAgentId } from "../../routing/session-key.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER, ensureFlagCompatibility } from "./shared.js"; export async function modelsStatusCommand( @@ -53,6 +54,7 @@ export async function modelsStatusCommand( probeTimeout?: string; probeConcurrency?: string; probeMaxTokens?: string; + agent?: string; }, runtime: RuntimeEnv, ) { @@ -93,8 +95,11 @@ export async function modelsStatusCommand( ); const allowed = Object.keys(cfg.agents?.defaults?.models ?? {}); - const agentDir = resolveOpenClawAgentDir(); - const store = ensureAuthProfileStore(); + const agentId = opts.agent?.trim() + ? normalizeAgentId(opts.agent.trim()) + : resolveDefaultAgentId(cfg); + const agentDir = resolveAgentDir(cfg, agentId); + const store = ensureAuthProfileStore(agentDir); const modelsPath = path.join(agentDir, "models.json"); const providersFromStore = new Set( @@ -304,7 +309,7 @@ export async function modelsStatusCommand( aliases, allowed, auth: { - storePath: resolveAuthStorePathForDisplay(), + storePath: resolveAuthStorePathForDisplay(agentDir), shellEnvFallback: { enabled: shellFallbackEnabled, appliedKeys: applied, @@ -411,7 +416,7 @@ export async function modelsStatusCommand( `${label("Auth store")}${colorize(rich, theme.muted, ":")} ${colorize( rich, theme.info, - shortenHomePath(resolveAuthStorePathForDisplay()), + shortenHomePath(resolveAuthStorePathForDisplay(agentDir)), )}`, ); runtime.log( diff --git a/src/commands/models/list.status.test.ts b/src/commands/models/list.status.test.ts index 7d363f059..48d11e46a 100644 --- a/src/commands/models/list.status.test.ts +++ b/src/commands/models/list.status.test.ts @@ -29,7 +29,8 @@ const mocks = vi.hoisted(() => { return { store, - resolveOpenClawAgentDir: vi.fn().mockReturnValue("/tmp/openclaw-agent"), + resolveAgentDir: vi.fn().mockReturnValue("/tmp/openclaw-agent"), + resolveDefaultAgentId: vi.fn().mockReturnValue("main"), ensureAuthProfileStore: vi.fn().mockReturnValue(store), listProfilesForProvider: vi.fn((s: typeof store, provider: string) => { return Object.entries(s.profiles) @@ -71,10 +72,19 @@ const mocks = vi.hoisted(() => { }; }); -vi.mock("../../agents/agent-paths.js", () => ({ - resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir, +vi.mock("../../agents/agent-scope.js", () => ({ + resolveAgentDir: mocks.resolveAgentDir, + resolveDefaultAgentId: mocks.resolveDefaultAgentId, })); +vi.mock("../../routing/session-key.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + normalizeAgentId: (id: string) => id.toLowerCase().replace(/\s+/g, "-"), + }; +}); + vi.mock("../../agents/auth-profiles.js", async (importOriginal) => { const actual = await importOriginal(); return { @@ -147,6 +157,23 @@ describe("modelsStatusCommand auth overview", () => { ).toBe(true); }); + it("resolves agent dir from --agent flag", async () => { + mocks.resolveAgentDir.mockReturnValue("/tmp/openclaw-agent-custom"); + const localRuntime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), + }; + try { + await modelsStatusCommand({ json: true, agent: "jeremiah" }, localRuntime as never); + expect(mocks.resolveAgentDir).toHaveBeenCalledWith(expect.anything(), "jeremiah"); + const payload = JSON.parse(String((localRuntime.log as vi.Mock).mock.calls[0][0])); + expect(payload.agentDir).toBe("/tmp/openclaw-agent-custom"); + } finally { + mocks.resolveAgentDir.mockReturnValue("/tmp/openclaw-agent"); + } + }); + it("exits non-zero when auth is missing", async () => { const originalProfiles = { ...mocks.store.profiles }; mocks.store.profiles = {};