From 045a7148d4f60df833d0665cabae0424debc2d93 Mon Sep 17 00:00:00 2001 From: Shakker Date: Tue, 16 Jun 2026 17:30:51 +0100 Subject: [PATCH] fix: select CLI auth profile for runtime prep --- .../auth-profiles.external-cli-scope.test.ts | 16 +++ .../auth-profiles/external-cli-scope.ts | 1 + src/agents/cli-runner/prepare.test.ts | 102 ++++++++++++++++++ src/agents/cli-runner/prepare.ts | 39 +++---- 4 files changed, 139 insertions(+), 19 deletions(-) diff --git a/src/agents/auth-profiles.external-cli-scope.test.ts b/src/agents/auth-profiles.external-cli-scope.test.ts index 205d181c4b6e..712b3e447634 100644 --- a/src/agents/auth-profiles.external-cli-scope.test.ts +++ b/src/agents/auth-profiles.external-cli-scope.test.ts @@ -109,4 +109,20 @@ describe("external CLI auth scope", () => { expect(scope?.providerIds).toContain("claude-cli"); }); + + it("includes Gemini CLI when it is the configured Google model runtime", () => { + const scope = resolveExternalCliAuthScopeFromConfig({ + agents: { + defaults: { + models: { + "google/gemini-3.1-pro-preview": { + agentRuntime: { id: "google-gemini-cli" }, + }, + }, + }, + }, + }); + + expect(scope?.providerIds).toContain("google-gemini-cli"); + }); }); diff --git a/src/agents/auth-profiles/external-cli-scope.ts b/src/agents/auth-profiles/external-cli-scope.ts index c65af857846f..d0b5ed3cf0a9 100644 --- a/src/agents/auth-profiles/external-cli-scope.ts +++ b/src/agents/auth-profiles/external-cli-scope.ts @@ -61,6 +61,7 @@ function addExternalCliRuntimeScope(out: Set, value: string | undefined) normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || + normalized === "google-gemini-cli" || normalized === "openai" || normalized === "minimax" || normalized === "minimax-cli" || diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index 36a16daff6ea..738024253910 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -509,6 +509,108 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { } }); + it("selects the configured Gemini CLI OAuth profile when no explicit profile is passed", async () => { + const { dir, sessionFile } = createSessionFile(); + const agentDir = path.join(dir, "agents", "main", "agent"); + const authProfileId = "google-gemini-cli:user@example.test"; + const prepareExecution = vi.fn(async () => ({ + env: { GEMINI_CLI_HOME: path.join(agentDir, "gemini-home") }, + })); + const resolveApiKeyForProfile = vi.fn(async () => ({ + apiKey: JSON.stringify({ token: "provider-formatted-access", projectId: "project-1" }), + profileId: authProfileId, + profileType: "oauth" as const, + provider: "google-gemini-cli", + email: "user@example.test", + })); + fs.mkdirSync(agentDir, { recursive: true }); + saveAuthProfileStore( + { + version: 1, + profiles: { + [authProfileId]: { + type: "oauth", + provider: "google-gemini-cli", + access: "raw-access-token", + refresh: "raw-refresh-token", + expires: 1_800_000_000_000, + projectId: "project-1", + email: "user@example.test", + }, + }, + }, + agentDir, + ); + cliBackendsTesting.setDepsForTest({ + resolvePluginSetupCliBackend: () => undefined, + resolveRuntimeCliBackends: () => [ + { + id: "google-gemini-cli", + pluginId: "google", + bundleMcp: false, + authEpochMode: "profile-only", + prepareExecution, + config: { + command: "gemini", + args: ["--prompt", "{prompt}"], + output: "json", + input: "arg", + sessionMode: "existing", + }, + }, + ], + }); + setCliRunnerPrepareTestDeps({ + resolveApiKeyForProfile, + }); + + try { + await prepareCliRunContext({ + sessionId: "session-test", + sessionKey: "agent:main:main", + sessionFile, + workspaceDir: dir, + prompt: "latest ask", + provider: "google-gemini-cli", + model: "gemini-3.1-pro-preview", + timeoutMs: 1_000, + runId: "run-test-gemini-oauth-default-profile", + config: { + auth: { + profiles: { + [authProfileId]: { + provider: "google-gemini-cli", + mode: "oauth", + email: "user@example.test", + }, + }, + }, + } as OpenClawConfig, + }); + + expect(resolveApiKeyForProfile).toHaveBeenCalledWith( + expect.objectContaining({ + profileId: authProfileId, + agentDir, + }), + ); + expect(prepareExecution).toHaveBeenCalledWith( + expect.objectContaining({ + authProfileId, + authCredential: expect.objectContaining({ + type: "oauth", + provider: "google-gemini-cli", + access: "raw-access-token", + refresh: "raw-refresh-token", + expires: 1_800_000_000_000, + }), + }), + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + it("stages adopted OAuth credentials for Gemini CLI preparation", async () => { const { dir, sessionFile } = createSessionFile(); const agentDir = path.join(dir, "agents", "main", "agent"); diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index d3eda084f3cc..330efd639959 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -33,6 +33,7 @@ import { normalizeMessageChannel } from "../../utils/message-channel.js"; import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js"; import { externalCliDiscoveryForProviderAuth } from "../auth-profiles/external-cli-discovery.js"; import { resolveApiKeyForProfile } from "../auth-profiles/oauth.js"; +import { resolveAuthProfileOrder } from "../auth-profiles/order.js"; import { loadAuthProfileStoreForRuntime } from "../auth-profiles/store.js"; import type { AuthProfileCredential, AuthProfileStore } from "../auth-profiles/types.js"; import { @@ -290,16 +291,29 @@ export async function prepareCliRunContext( requestedAuthProfileId ?? backendResolved.defaultAuthProfileId?.trim() ?? undefined; let authStore: AuthProfileStore | undefined; let authCredential: AuthProfileCredential | undefined; - if (effectiveAuthProfileId) { - authStore = loadAuthProfileStoreForRuntime(agentDir, { - readOnly: true, + const loadScopedAuthStore = (options: { profileId?: string; readOnly?: boolean } = {}) => + loadAuthProfileStoreForRuntime(agentDir, { + readOnly: options.readOnly ?? true, externalCli: externalCliDiscoveryForProviderAuth({ cfg: params.config, provider: params.provider, - profileId: effectiveAuthProfileId, + ...(options.profileId ? { profileId: options.profileId } : {}), }), }); + if (effectiveAuthProfileId) { + authStore = loadScopedAuthStore({ profileId: effectiveAuthProfileId }); authCredential = authStore.profiles[effectiveAuthProfileId]; + } else if (backendResolved.prepareExecution || backendResolved.authEpochMode === "profile-only") { + authStore = loadScopedAuthStore(); + effectiveAuthProfileId = + resolveAuthProfileOrder({ + cfg: params.config, + store: authStore, + provider: params.provider, + })[0]?.trim() || undefined; + if (effectiveAuthProfileId) { + authCredential = authStore.profiles[effectiveAuthProfileId]; + } } if ( effectiveAuthProfileId && @@ -310,13 +324,7 @@ export async function prepareCliRunContext( }) ) { const authProfileId = effectiveAuthProfileId; - const writableAuthStore = loadAuthProfileStoreForRuntime(agentDir, { - externalCli: externalCliDiscoveryForProviderAuth({ - cfg: params.config, - provider: params.provider, - profileId: authProfileId, - }), - }); + const writableAuthStore = loadScopedAuthStore({ profileId: authProfileId, readOnly: false }); const resolvedAuth = await prepareDeps.resolveApiKeyForProfile({ cfg: params.config, store: writableAuthStore, @@ -325,14 +333,7 @@ export async function prepareCliRunContext( }); const resolvedAuthProfileId = resolvedAuth?.profileId ?? authProfileId; const resolvedAuthCredential = resolvedAuth?.credential; - authStore = loadAuthProfileStoreForRuntime(agentDir, { - readOnly: true, - externalCli: externalCliDiscoveryForProviderAuth({ - cfg: params.config, - provider: params.provider, - profileId: resolvedAuthProfileId, - }), - }); + authStore = loadScopedAuthStore({ profileId: resolvedAuthProfileId }); authCredential = resolvedAuthCredential ?? authStore.profiles[resolvedAuthProfileId]; if (resolvedAuth && authCredential) { effectiveAuthProfileId = resolvedAuthProfileId;