From ff55cd5c1622f487261e1192ba763e08ccfaec1b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 17 Apr 2026 13:51:57 -0700 Subject: [PATCH] refactor(auth): drop legacy external cli oauth sync path --- CHANGELOG.md | 1 + .../auth-profiles.external-cli-sync.test.ts | 238 +++++++----------- src/agents/auth-profiles.store-cache.test.ts | 71 +++--- src/agents/auth-profiles/external-cli-sync.ts | 135 ++-------- .../auth-profiles/oauth-refresh-queue.test.ts | 2 +- .../oauth.adopt-identity.test.ts | 2 +- .../oauth.concurrent-20-agents.test.ts | 2 +- .../oauth.mirror-refresh.test.ts | 2 +- ...s-writing-models-json-no-env-token.test.ts | 4 - src/agents/pi-auth-json.test.ts | 2 +- 10 files changed, 147 insertions(+), 312 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a590096e6..27f64c76e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai - OpenAI Codex/OAuth: treat the OpenAI TLS prerequisites probe as advisory instead of a hard blocker, so Codex sign-in can still proceed when the speculative Node/OpenSSL precheck fails but the real OAuth flow still works. Thanks @vincentkoc. - Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc. - OpenAI Codex/OAuth: keep external CLI OAuth imports runtime-only by overlaying fresher Codex CLI credentials without mutating `auth-profiles.json`, so `.codex` stays a bootstrap/runtime input instead of becoming durable OpenClaw state. Thanks @vincentkoc. +- OpenAI Codex/OAuth: drop legacy CLI-manager routing from the remaining bootstrap path so Codex and MiniMax CLI imports are matched by their canonical OpenClaw profile ids instead of stale `managedBy` metadata. Thanks @vincentkoc. - Twitch/setup: load Twitch through the bundled setup-entry discovery path and keep setup/status account detection aligned with runtime config. (#68008) Thanks @gumadeiras. - Feishu/card actions: resolve card-action chat type from the Feishu chat API when stored context is missing, preferring `chat_mode` over `chat_type`, so DM-originated card actions no longer bypass `dmPolicy` by falling through to the group handling path. (#68201) - Cron/isolated-agent: preserve `trusted: false` on isolated cron awareness events mirrored into the main session, and forward the optional `trusted` flag through the gateway cron wrapper so explicit trust downgrades survive session-key scoping. (#68210) diff --git a/src/agents/auth-profiles.external-cli-sync.test.ts b/src/agents/auth-profiles.external-cli-sync.test.ts index fdabb6e6741..06460b47edc 100644 --- a/src/agents/auth-profiles.external-cli-sync.test.ts +++ b/src/agents/auth-profiles.external-cli-sync.test.ts @@ -6,9 +6,9 @@ const mocks = vi.hoisted(() => ({ readMiniMaxCliCredentialsCached: vi.fn<() => OAuthCredential | null>(() => null), })); -let syncExternalCliCredentials: typeof import("./auth-profiles/external-cli-sync.js").syncExternalCliCredentials; +let readManagedExternalCliCredential: typeof import("./auth-profiles/external-cli-sync.js").readManagedExternalCliCredential; +let resolveExternalCliAuthProfiles: typeof import("./auth-profiles/external-cli-sync.js").resolveExternalCliAuthProfiles; let shouldReplaceStoredOAuthCredential: typeof import("./auth-profiles/external-cli-sync.js").shouldReplaceStoredOAuthCredential; -let CODEX_CLI_PROFILE_ID: typeof import("./auth-profiles/constants.js").CODEX_CLI_PROFILE_ID; let OPENAI_CODEX_DEFAULT_PROFILE_ID: typeof import("./auth-profiles/constants.js").OPENAI_CODEX_DEFAULT_PROFILE_ID; let MINIMAX_CLI_PROFILE_ID: typeof import("./auth-profiles/constants.js").MINIMAX_CLI_PROFILE_ID; @@ -35,37 +35,21 @@ function makeStore(profileId?: string, credential?: OAuthCredential): AuthProfil }; } -function getProviderCases() { - return [ - { - label: "Codex", - profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, - provider: "openai-codex" as const, - readMock: mocks.readCodexCliCredentialsCached, - legacyProfileId: CODEX_CLI_PROFILE_ID, - }, - { - label: "MiniMax", - profileId: MINIMAX_CLI_PROFILE_ID, - provider: "minimax-portal" as const, - readMock: mocks.readMiniMaxCliCredentialsCached, - }, - ]; -} - -describe("syncExternalCliCredentials", () => { +describe("external cli oauth resolution", () => { beforeEach(async () => { vi.resetModules(); - vi.doUnmock("./auth-profiles/external-cli-sync.js"); - mocks.readCodexCliCredentialsCached.mockReset().mockReturnValue(null); - mocks.readMiniMaxCliCredentialsCached.mockReset().mockReturnValue(null); vi.doMock("./cli-credentials.js", () => ({ readCodexCliCredentialsCached: mocks.readCodexCliCredentialsCached, readMiniMaxCliCredentialsCached: mocks.readMiniMaxCliCredentialsCached, })); - ({ syncExternalCliCredentials, shouldReplaceStoredOAuthCredential } = - await import("./auth-profiles/external-cli-sync.js")); - ({ CODEX_CLI_PROFILE_ID, OPENAI_CODEX_DEFAULT_PROFILE_ID, MINIMAX_CLI_PROFILE_ID } = + mocks.readCodexCliCredentialsCached.mockReset().mockReturnValue(null); + mocks.readMiniMaxCliCredentialsCached.mockReset().mockReturnValue(null); + ({ + readManagedExternalCliCredential, + resolveExternalCliAuthProfiles, + shouldReplaceStoredOAuthCredential, + } = await import("./auth-profiles/external-cli-sync.js")); + ({ OPENAI_CODEX_DEFAULT_PROFILE_ID, MINIMAX_CLI_PROFILE_ID } = await import("./auth-profiles/constants.js")); }); @@ -120,150 +104,110 @@ describe("syncExternalCliCredentials", () => { }); }); - it.each([{ providerLabel: "Codex" }, { providerLabel: "MiniMax" }])( - "syncs $providerLabel CLI credentials into the target auth profile", - ({ providerLabel }) => { - const providerCase = getProviderCases().find((entry) => entry.label === providerLabel); - expect(providerCase).toBeDefined(); - const current = providerCase!; - const expires = Date.now() + 60_000; - current.readMock.mockReturnValue( - makeOAuthCredential({ - provider: current.provider, - access: `${current.provider}-access-token`, - refresh: `${current.provider}-refresh-token`, - expires, - accountId: "acct_123", - }), - ); - - const store = makeStore(); - - const mutated = syncExternalCliCredentials(store); - - expect(mutated).toBe(true); - expect(current.readMock).toHaveBeenCalledWith( - expect.objectContaining({ ttlMs: expect.any(Number) }), - ); - expect(store.profiles[current.profileId]).toMatchObject({ - type: "oauth", - provider: current.provider, - access: `${current.provider}-access-token`, - refresh: `${current.provider}-refresh-token`, - expires, - accountId: "acct_123", - managedBy: current.provider === "openai-codex" ? "codex-cli" : ("minimax-cli" as const), - }); - if (current.legacyProfileId) { - expect(store.profiles[current.legacyProfileId]).toBeUndefined(); - } - }, - ); - - it("refreshes stored Codex expiry from external CLI even when the cached profile looks fresh", () => { - const staleExpiry = Date.now() + 30 * 60_000; - const freshExpiry = Date.now() + 5 * 24 * 60 * 60_000; + it("reads codex external cli credentials by profile id", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ provider: "openai-codex", - access: "new-access-token", - refresh: "new-refresh-token", - expires: freshExpiry, - accountId: "acct_456", + access: "codex-access-token", + refresh: "codex-refresh-token", }), ); - const store = makeStore( - OPENAI_CODEX_DEFAULT_PROFILE_ID, - makeOAuthCredential({ - provider: "openai-codex", - access: "old-access-token", - refresh: "old-refresh-token", - expires: staleExpiry, - accountId: "acct_456", - }), - ); + const credential = readManagedExternalCliCredential({ + profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, + credential: makeOAuthCredential({ provider: "openai-codex" }), + }); - const mutated = syncExternalCliCredentials(store); - - expect(mutated).toBe(true); - expect(store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]).toMatchObject({ - access: "new-access-token", - refresh: "new-refresh-token", - expires: freshExpiry, - managedBy: "codex-cli", + expect(credential).toMatchObject({ + access: "codex-access-token", + refresh: "codex-refresh-token", }); }); - it.each([{ providerLabel: "Codex" }, { providerLabel: "MiniMax" }])( - "does not overwrite newer stored $providerLabel credentials", - ({ providerLabel }) => { - const providerCase = getProviderCases().find((entry) => entry.label === providerLabel); - expect(providerCase).toBeDefined(); - const current = providerCase!; - const staleExpiry = Date.now() + 30 * 60_000; - const freshExpiry = Date.now() + 5 * 24 * 60 * 60_000; - current.readMock.mockReturnValue( - makeOAuthCredential({ - provider: current.provider, - access: `stale-${current.provider}-access-token`, - refresh: `stale-${current.provider}-refresh-token`, - expires: staleExpiry, - accountId: "acct_789", - }), - ); + it("returns null when the profile id/provider do not map to the same external source", () => { + mocks.readCodexCliCredentialsCached.mockReturnValue( + makeOAuthCredential({ provider: "openai-codex" }), + ); - const store = makeStore( - current.profileId, - makeOAuthCredential({ - provider: current.provider, - access: `fresh-${current.provider}-access-token`, - refresh: `fresh-${current.provider}-refresh-token`, - expires: freshExpiry, - accountId: "acct_789", - }), - ); + const credential = readManagedExternalCliCredential({ + profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, + credential: makeOAuthCredential({ provider: "anthropic" }), + }); - const mutated = syncExternalCliCredentials(store); + expect(credential).toBeNull(); + }); - expect(mutated).toBe(false); - expect(store.profiles[current.profileId]).toMatchObject({ - access: `fresh-${current.provider}-access-token`, - refresh: `fresh-${current.provider}-refresh-token`, - expires: freshExpiry, - }); - }, - ); - - it("upgrades matching Codex CLI credentials with external ownership metadata", () => { - const expires = Date.now() + 60_000; + it("resolves fresher codex and minimax external oauth profiles as runtime overlays", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ provider: "openai-codex", - access: "same-access-token", - refresh: "same-refresh-token", - expires, + access: "codex-fresh-access", + refresh: "codex-fresh-refresh", + expires: Date.now() + 5 * 24 * 60 * 60_000, + }), + ); + mocks.readMiniMaxCliCredentialsCached.mockReturnValue( + makeOAuthCredential({ + provider: "minimax-portal", + access: "minimax-fresh-access", + refresh: "minimax-fresh-refresh", + expires: Date.now() + 5 * 24 * 60 * 60_000, }), ); - const store = makeStore( - OPENAI_CODEX_DEFAULT_PROFILE_ID, + const profiles = resolveExternalCliAuthProfiles({ + version: 1, + profiles: { + [OPENAI_CODEX_DEFAULT_PROFILE_ID]: makeOAuthCredential({ + provider: "openai-codex", + access: "codex-stale-access", + refresh: "codex-stale-refresh", + expires: Date.now() - 5_000, + }), + [MINIMAX_CLI_PROFILE_ID]: makeOAuthCredential({ + provider: "minimax-portal", + access: "minimax-stale-access", + refresh: "minimax-stale-refresh", + expires: Date.now() - 5_000, + }), + }, + }); + + const profilesById = new Map( + profiles.map((profile) => [profile.profileId, profile.credential]), + ); + expect(profilesById.get(OPENAI_CODEX_DEFAULT_PROFILE_ID)).toMatchObject({ + access: "codex-fresh-access", + refresh: "codex-fresh-refresh", + }); + expect(profilesById.get(MINIMAX_CLI_PROFILE_ID)).toMatchObject({ + access: "minimax-fresh-access", + refresh: "minimax-fresh-refresh", + }); + }); + + it("does not emit runtime overlays when the stored credential is newer", () => { + mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ provider: "openai-codex", - access: "same-access-token", - refresh: "same-refresh-token", - expires, + access: "stale-external-access", + refresh: "stale-external-refresh", + expires: Date.now() - 5_000, }), ); - const mutated = syncExternalCliCredentials(store); + const profiles = resolveExternalCliAuthProfiles( + makeStore( + OPENAI_CODEX_DEFAULT_PROFILE_ID, + makeOAuthCredential({ + provider: "openai-codex", + access: "fresh-store-access", + refresh: "fresh-store-refresh", + expires: Date.now() + 5 * 24 * 60 * 60_000, + }), + ), + ); - expect(mutated).toBe(true); - expect(store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]).toMatchObject({ - access: "same-access-token", - refresh: "same-refresh-token", - expires, - managedBy: "codex-cli", - }); + expect(profiles).toEqual([]); }); }); diff --git a/src/agents/auth-profiles.store-cache.test.ts b/src/agents/auth-profiles.store-cache.test.ts index 668fd76d97b..f619b7d9bb3 100644 --- a/src/agents/auth-profiles.store-cache.test.ts +++ b/src/agents/auth-profiles.store-cache.test.ts @@ -3,20 +3,11 @@ import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AUTH_STORE_VERSION } from "./auth-profiles/constants.js"; -import type { AuthProfileStore } from "./auth-profiles/types.js"; -const AUTH_STORE_CACHE_TTL_MS = 15 * 60 * 1000; - -const mocks = vi.hoisted(() => ({ - syncExternalCliCredentials: vi.fn((_: AuthProfileStore) => false), -})); - -vi.mock("./auth-profiles/external-cli-sync.js", () => ({ - syncExternalCliCredentials: mocks.syncExternalCliCredentials, -})); +const resolveExternalAuthProfilesWithPluginsMock = vi.fn(() => []); vi.mock("../plugins/provider-runtime.js", () => ({ - resolveExternalAuthProfilesWithPlugins: () => [], + resolveExternalAuthProfilesWithPlugins: resolveExternalAuthProfilesWithPluginsMock, })); let clearRuntimeAuthProfileStoreSnapshots: typeof import("./auth-profiles.js").clearRuntimeAuthProfileStoreSnapshots; @@ -82,23 +73,29 @@ describe("auth profile store cache", () => { afterEach(() => { vi.useRealTimers(); clearRuntimeAuthProfileStoreSnapshots(); + resolveExternalAuthProfilesWithPluginsMock.mockReset(); + resolveExternalAuthProfilesWithPluginsMock.mockReturnValue([]); vi.clearAllMocks(); }); - it("reuses the synced auth store while auth-profiles.json is unchanged", async () => { + it("reuses the cached auth store while auth-profiles.json is unchanged", async () => { await withAgentDirEnv("openclaw-auth-store-cache-", (agentDir) => { - writeAuthStore(agentDir, "sk-test"); + const authPath = writeAuthStore(agentDir, "sk-test"); + const readFileSyncSpy = vi.spyOn(fs, "readFileSync"); ensureAuthProfileStore(agentDir); ensureAuthProfileStore(agentDir); - expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(1); + expect( + readFileSyncSpy.mock.calls.filter(([target]) => String(target) === authPath), + ).toHaveLength(1); }); }); it("refreshes the cached auth store after auth-profiles.json changes", async () => { await withAgentDirEnv("openclaw-auth-store-refresh-", async (agentDir) => { const authPath = writeAuthStore(agentDir, "sk-test-1"); + const readFileSyncSpy = vi.spyOn(fs, "readFileSync"); ensureAuthProfileStore(agentDir); @@ -108,30 +105,35 @@ describe("auth profile store cache", () => { const reloaded = ensureAuthProfileStore(agentDir); - expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(2); + expect( + readFileSyncSpy.mock.calls.filter(([target]) => String(target) === authPath), + ).toHaveLength(2); expect(reloaded.profiles["openai:default"]).toMatchObject({ key: "sk-test-2", }); }); }); - it("re-syncs external CLI credentials after the cache ttl when auth-profiles.json is absent", () => { + it("reapplies runtime-only external auth overlays over a cached missing auth store", () => { const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-store-missing-")); const previousAgentDir = process.env.OPENCLAW_AGENT_DIR; const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR; - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-21T15:00:00.000Z")); - let syncCount = 0; - mocks.syncExternalCliCredentials.mockImplementation((store) => { - syncCount += 1; - store.profiles["openai-codex:default"] = { - type: "oauth", - provider: "openai-codex", - access: `access-${syncCount}`, - refresh: `refresh-${syncCount}`, - expires: Date.now() + 60_000, - }; - return true; + let overlayCount = 0; + resolveExternalAuthProfilesWithPluginsMock.mockImplementation(() => { + overlayCount += 1; + return [ + { + profileId: "openai-codex:default", + credential: { + type: "oauth" as const, + provider: "openai-codex", + access: `access-${overlayCount}`, + refresh: `refresh-${overlayCount}`, + expires: Date.now() + 60_000, + }, + persistence: "runtime-only" as const, + }, + ]; }); try { process.env.OPENCLAW_AGENT_DIR = agentDir; @@ -141,15 +143,8 @@ describe("auth profile store cache", () => { const second = ensureAuthProfileStore(agentDir); expect(first.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" }); - expect(second.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" }); - expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(1); - - vi.advanceTimersByTime(AUTH_STORE_CACHE_TTL_MS + 1); - - const third = ensureAuthProfileStore(agentDir); - - expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(2); - expect(third.profiles["openai-codex:default"]).toMatchObject({ access: "access-2" }); + expect(second.profiles["openai-codex:default"]).toMatchObject({ access: "access-2" }); + expect(resolveExternalAuthProfilesWithPluginsMock).toHaveBeenCalledTimes(2); } finally { if (previousAgentDir === undefined) { delete process.env.OPENCLAW_AGENT_DIR; diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index 759b354061c..62f3e3cc4de 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -4,15 +4,10 @@ import { } from "../cli-credentials.js"; import { EXTERNAL_CLI_SYNC_TTL_MS, - OPENAI_CODEX_DEFAULT_PROFILE_ID, MINIMAX_CLI_PROFILE_ID, - log, + OPENAI_CODEX_DEFAULT_PROFILE_ID, } from "./constants.js"; -import type { AuthProfileStore, ExternalOAuthManager, OAuthCredential } from "./types.js"; - -type ExternalCliSyncOptions = { - log?: boolean; -}; +import type { AuthProfileStore, OAuthCredential } from "./types.js"; export type ExternalCliResolvedProfile = { profileId: string; @@ -22,7 +17,6 @@ export type ExternalCliResolvedProfile = { type ExternalCliSyncProvider = { profileId: string; provider: string; - managedBy: ExternalOAuthManager; readCredentials: () => OAuthCredential | null; }; @@ -44,8 +38,7 @@ export function areOAuthCredentialsEquivalent( a.email === b.email && a.enterpriseUrl === b.enterpriseUrl && a.projectId === b.projectId && - a.accountId === b.accountId && - a.managedBy === b.managedBy + a.accountId === b.accountId ); } @@ -78,69 +71,40 @@ const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [ { profileId: MINIMAX_CLI_PROFILE_ID, provider: "minimax-portal", - managedBy: "minimax-cli", readCredentials: () => readMiniMaxCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }), }, { profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, provider: "openai-codex", - managedBy: "codex-cli", readCredentials: () => readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }), }, ]; -function withExternalCliManager( - creds: OAuthCredential, - managedBy: ExternalOAuthManager, -): OAuthCredential { - return { - ...creds, - managedBy, - }; -} - -function stripExternalCliManager(creds: OAuthCredential): OAuthCredential { - const { managedBy: _managedBy, ...runtimeCredential } = creds; - return runtimeCredential; -} - function resolveExternalCliSyncProvider(params: { - profileId?: string; + profileId: string; credential?: OAuthCredential; }): ExternalCliSyncProvider | null { - const byProfileId = - typeof params.profileId === "string" - ? EXTERNAL_CLI_SYNC_PROVIDERS.find((entry) => entry.profileId === params.profileId) - : undefined; - if (byProfileId) { - return byProfileId; - } - const managedBy = params.credential?.managedBy; - if (!managedBy) { + const provider = EXTERNAL_CLI_SYNC_PROVIDERS.find( + (entry) => entry.profileId === params.profileId, + ); + if (!provider) { return null; } - return ( - EXTERNAL_CLI_SYNC_PROVIDERS.find( - (entry) => - entry.managedBy === managedBy && - (!params.credential || entry.provider === params.credential.provider), - ) ?? null - ); + if (params.credential && provider.provider !== params.credential.provider) { + return null; + } + return provider; } export function readManagedExternalCliCredential(params: { - profileId?: string; + profileId: string; credential: OAuthCredential; }): OAuthCredential | null { const provider = resolveExternalCliSyncProvider(params); if (!provider) { return null; } - const creds = provider.readCredentials(); - if (!creds) { - return null; - } - return withExternalCliManager(creds, provider.managedBy); + return provider.readCredentials(); } export function resolveExternalCliAuthProfiles( @@ -152,83 +116,18 @@ export function resolveExternalCliAuthProfiles( if (!creds) { continue; } - const runtimeCredential = stripExternalCliManager( - withExternalCliManager(creds, providerConfig.managedBy), - ); const existing = store.profiles[providerConfig.profileId]; const existingOAuth = existing?.type === "oauth" ? existing : undefined; if ( - !shouldReplaceStoredOAuthCredential(existingOAuth, runtimeCredential) && - !areOAuthCredentialsEquivalent(existingOAuth, runtimeCredential) + !shouldReplaceStoredOAuthCredential(existingOAuth, creds) && + !areOAuthCredentialsEquivalent(existingOAuth, creds) ) { continue; } profiles.push({ profileId: providerConfig.profileId, - credential: runtimeCredential, + credential: creds, }); } return profiles; } - -/** Sync external CLI credentials into the store for a given provider. */ -function syncExternalCliCredentialsForProvider( - store: AuthProfileStore, - providerConfig: ExternalCliSyncProvider, - options: ExternalCliSyncOptions, -): boolean { - const { profileId, provider, managedBy, readCredentials } = providerConfig; - const existing = store.profiles[profileId]; - const creds = readCredentials(); - if (!creds) { - return false; - } - const managedCreds = withExternalCliManager(creds, managedBy); - - const existingOAuth = existing?.type === "oauth" ? existing : undefined; - if (!shouldReplaceStoredOAuthCredential(existingOAuth, managedCreds)) { - if (options.log !== false) { - if (!areOAuthCredentialsEquivalent(existingOAuth, managedCreds) && existingOAuth) { - log.debug(`kept newer stored ${provider} credentials over external cli sync`, { - profileId, - storedExpires: new Date(existingOAuth.expires).toISOString(), - externalExpires: Number.isFinite(managedCreds.expires) - ? new Date(managedCreds.expires).toISOString() - : null, - }); - } - } - return false; - } - - store.profiles[profileId] = managedCreds; - if (options.log !== false) { - log.info(`synced ${provider} credentials from external cli`, { - profileId, - expires: new Date(managedCreds.expires).toISOString(), - managedBy, - }); - } - return true; -} - -/** - * Sync OAuth credentials from external CLI tools (MiniMax CLI, Codex CLI) - * into the store. - * - * Returns true if any credentials were updated. - */ -export function syncExternalCliCredentials( - store: AuthProfileStore, - options: ExternalCliSyncOptions = {}, -): boolean { - let mutated = false; - - for (const provider of EXTERNAL_CLI_SYNC_PROVIDERS) { - if (syncExternalCliCredentialsForProvider(store, provider, options)) { - mutated = true; - } - } - - return mutated; -} diff --git a/src/agents/auth-profiles/oauth-refresh-queue.test.ts b/src/agents/auth-profiles/oauth-refresh-queue.test.ts index 73ad164573d..bbb9991d49d 100644 --- a/src/agents/auth-profiles/oauth-refresh-queue.test.ts +++ b/src/agents/auth-profiles/oauth-refresh-queue.test.ts @@ -72,8 +72,8 @@ vi.mock("./external-auth.js", () => ({ })); vi.mock("./external-cli-sync.js", () => ({ - syncExternalCliCredentials: () => false, readManagedExternalCliCredential: () => null, + resolveExternalCliAuthProfiles: () => [], areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b, })); diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index 75eb8273faf..e6e4f94c10a 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -79,8 +79,8 @@ vi.mock("./doctor.js", () => ({ })); vi.mock("./external-cli-sync.js", () => ({ - syncExternalCliCredentials: () => false, readManagedExternalCliCredential: () => null, + resolveExternalCliAuthProfiles: () => [], areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b, })); diff --git a/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts b/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts index e89a2e609d0..5045ea97073 100644 --- a/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts +++ b/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts @@ -65,8 +65,8 @@ vi.mock("./doctor.js", () => ({ // credential files; it is slow and can pollute test state. Stub it to a no-op // so the suite only exercises in-repo auth-profile logic. vi.mock("./external-cli-sync.js", () => ({ - syncExternalCliCredentials: () => false, readManagedExternalCliCredential: () => null, + resolveExternalCliAuthProfiles: () => [], areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b, })); diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index f9d7d87c663..81a68275d29 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -76,8 +76,8 @@ vi.mock("./doctor.js", () => ({ })); vi.mock("./external-cli-sync.js", () => ({ - syncExternalCliCredentials: () => false, readManagedExternalCliCredential: () => null, + resolveExternalCliAuthProfiles: () => [], areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b, })); 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 25b90981068..3a65dc17db1 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 @@ -12,10 +12,6 @@ import { } from "./models-config.e2e-harness.js"; import type { ProviderConfig as ModelsProviderConfig } from "./models-config.providers.secrets.js"; -vi.mock("./auth-profiles/external-cli-sync.js", () => ({ - syncExternalCliCredentials: () => false, -})); - vi.mock("./models-config.providers.js", async () => { function createImplicitProvider(baseUrl: string): ModelsProviderConfig { return { diff --git a/src/agents/pi-auth-json.test.ts b/src/agents/pi-auth-json.test.ts index 2213030ebc2..692fdc852cf 100644 --- a/src/agents/pi-auth-json.test.ts +++ b/src/agents/pi-auth-json.test.ts @@ -11,7 +11,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({ vi.mock("./auth-profiles/external-cli-sync.js", () => ({ readManagedExternalCliCredential: () => null, - syncExternalCliCredentials: () => false, + resolveExternalCliAuthProfiles: () => [], })); type AuthProfileStore = Parameters[0];