From fc47c1f55eaaee8bde4e51e1c063aaa2a2b02669 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 22 May 2026 13:46:07 +0100 Subject: [PATCH] fix(cli): honor agent for model auth logout (#85326) --- CHANGELOG.md | 1 + src/cli/capability-cli.test.ts | 35 +++++++++++++++++++++++++++++++++- src/cli/capability-cli.ts | 11 ++++++++--- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3287e7d27..f99b0ab2b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai - Cron: honor `cron.retry.retryOn: ["network"]` for common network error codes such as `EAI_AGAIN`, `EHOSTUNREACH`, and `ENETUNREACH`. - Agents/OpenAI: preserve structured provider error code, type, and redacted body metadata on boundary-aware transport failures. - Doctor/Codex: point native Codex asset warnings at the canonical `openclaw migrate plan codex` preview command. Fixes #84948. Thanks @markoa. +- CLI/models: make `capability model auth logout --agent` remove auth profiles from the selected non-default agent store. Fixes #85092. Thanks @islandpreneur007. - CLI/agents: retry transient normal-close Gateway handshakes before falling back to embedded `openclaw agent` execution. - CLI/update: keep managed Gateway service stop/restart status lines out of `openclaw update --json` stdout so package-update automation can parse the JSON payload. - Plugins: resolve OpenClaw plugin SDK subpaths for native external plugin runtimes without mutating package installs or broadening process-wide module resolution. diff --git a/src/cli/capability-cli.test.ts b/src/cli/capability-cli.test.ts index 39f69542c9a..4968b2e129e 100644 --- a/src/cli/capability-cli.test.ts +++ b/src/cli/capability-cli.test.ts @@ -24,6 +24,7 @@ const mocks = vi.hoisted(() => ({ setRuntimeConfigSnapshot: vi.fn(), loadAuthProfileStoreForRuntime: vi.fn(() => ({ profiles: {}, order: {} })), listProfilesForProvider: vi.fn(() => []), + resolveAgentDir: vi.fn((_cfg: unknown, agentId: string) => `/tmp/agent-${agentId}`), updateAuthProfileStoreWithLock: vi.fn( async ({ updater }: { updater: (store: any) => boolean }) => { const store = { @@ -170,7 +171,7 @@ vi.mock("./command-config-resolution.js", () => ({ vi.mock("../agents/agent-scope.js", () => ({ resolveDefaultAgentId: () => "main", - resolveAgentDir: () => "/tmp/agent", + resolveAgentDir: mocks.resolveAgentDir, resolveAgentConfig: () => ({}), resolveAgentEffectiveModelPrimary: () => undefined, resolveAgentModelFallbacksOverride: () => [], @@ -401,6 +402,7 @@ describe("capability cli", () => { .mockResolvedValue([{ id: "gpt-5.4", provider: "openai", name: "GPT-5.4" }] as never); mocks.loadAuthProfileStoreForRuntime.mockReset().mockReturnValue({ profiles: {}, order: {} }); mocks.listProfilesForProvider.mockReset().mockReturnValue([]); + mocks.resolveAgentDir.mockClear(); mocks.getRuntimeConfigSourceSnapshot.mockReset().mockReturnValue(null); mocks.setRuntimeConfigSnapshot.mockClear(); mocks.updateAuthProfileStoreWithLock @@ -2176,6 +2178,37 @@ describe("capability cli", () => { provider: "openai", removedProfiles: ["openai:default", "openai:secondary"], }); + expect(mocks.updateAuthProfileStoreWithLock).toHaveBeenCalledWith( + expect.objectContaining({ agentDir: "/tmp/agent-main" }), + ); + }); + + it("removes model auth profiles from the selected agent store", async () => { + mocks.listProfilesForProvider.mockReturnValue(["openai:default"] as never); + + await runRegisteredCli({ + register: registerCapabilityCli as (program: Command) => void, + argv: [ + "capability", + "model", + "auth", + "logout", + "--provider", + "openai", + "--agent", + "poe", + "--json", + ], + }); + + expect(mocks.loadAuthProfileStoreForRuntime).toHaveBeenCalledWith("/tmp/agent-poe"); + expect(mocks.updateAuthProfileStoreWithLock).toHaveBeenCalledWith( + expect.objectContaining({ agentDir: "/tmp/agent-poe" }), + ); + expect(mocks.runtime.writeJson).toHaveBeenCalledWith({ + provider: "openai", + removedProfiles: ["openai:default"], + }); }); it("fails logout if the auth store update does not complete", async () => { diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index eb1921bde2c..702cdb33d5c 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -920,9 +920,10 @@ async function runModelAuthStatus() { return raw ? (JSON.parse(raw) as Record) : {}; } -async function runModelAuthLogout(provider: string) { +async function runModelAuthLogout(provider: string, agent?: string) { const cfg = getRuntimeConfig(); - const agentDir = resolveAgentDir(cfg, resolveDefaultAgentId(cfg)); + const agentId = agent?.trim() || resolveDefaultAgentId(cfg); + const agentDir = resolveAgentDir(cfg, agentId); const store = loadAuthProfileStoreForRuntime(agentDir); const profileIds = listProfilesForProvider(store, provider); const updated = await updateAuthProfileStoreWithLock({ @@ -1882,10 +1883,14 @@ export function registerCapabilityCli(program: Command) { .command("logout") .description("Remove saved auth profiles for one provider") .requiredOption("--provider ", "Provider id") + .option("--agent ", "Agent id (default: configured default agent)") .option("--json", "Output JSON", false) .action(async (opts) => { await runCommandWithRuntime(defaultRuntime, async () => { - const result = await runModelAuthLogout(String(opts.provider)); + const result = await runModelAuthLogout( + String(opts.provider), + typeof opts.agent === "string" ? opts.agent : undefined, + ); emitJsonOrText(defaultRuntime, Boolean(opts.json), result, (value) => JSON.stringify(value, null, 2), );