From d56831f81b7d91d7e993aa3cf6b58ed8a58998c1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 09:05:43 +0100 Subject: [PATCH] fix: align gemini cli live backend runs --- extensions/google/cli-backend.ts | 13 ++++- scripts/lib/live-docker-auth.sh | 8 +-- src/agents/cli-backends.test.ts | 79 ++++++++++++++++++++------- src/agents/cli-runner.helpers.test.ts | 22 ++++++++ src/agents/cli-runner.test-support.ts | 4 +- src/agents/cli-runner/helpers.ts | 10 ++++ test/test-env.ts | 3 +- 7 files changed, 110 insertions(+), 29 deletions(-) diff --git a/extensions/google/cli-backend.ts b/extensions/google/cli-backend.ts index d6ed1ec9b5f..b051cc06a0b 100644 --- a/extensions/google/cli-backend.ts +++ b/extensions/google/cli-backend.ts @@ -9,14 +9,23 @@ const GEMINI_MODEL_ALIASES: Record = { flash: "gemini-3.1-flash-preview", "flash-lite": "gemini-3.1-flash-lite-preview", }; +const GEMINI_CLI_DEFAULT_MODEL_REF = "google-gemini-cli/gemini-3.1-pro-preview"; export function buildGoogleGeminiCliBackend(): CliBackendPlugin { return { id: "google-gemini-cli", + liveTest: { + defaultModelRef: GEMINI_CLI_DEFAULT_MODEL_REF, + defaultImageProbe: true, + docker: { + npmPackage: "@google/gemini-cli", + binaryName: "gemini", + }, + }, config: { command: "gemini", - args: ["--prompt", "--output-format", "json"], - resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"], + args: ["--output-format", "json", "--prompt", "{prompt}"], + resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"], output: "json", input: "arg", modelArg: "--model", diff --git a/scripts/lib/live-docker-auth.sh b/scripts/lib/live-docker-auth.sh index d51663aaefb..84229150313 100644 --- a/scripts/lib/live-docker-auth.sh +++ b/scripts/lib/live-docker-auth.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -OPENCLAW_DOCKER_LIVE_AUTH_ALL=(.minimax) +OPENCLAW_DOCKER_LIVE_AUTH_ALL=(.gemini .minimax) OPENCLAW_DOCKER_LIVE_AUTH_FILES_ALL=( .codex/auth.json .codex/config.toml @@ -30,6 +30,9 @@ openclaw_live_should_include_auth_dir_for_provider() { local provider provider="$(openclaw_live_trim "${1:-}")" case "$provider" in + gemini | gemini-cli | google-gemini-cli) + printf '%s\n' ".gemini" + ;; minimax | minimax-portal) printf '%s\n' ".minimax" ;; @@ -50,9 +53,6 @@ openclaw_live_should_include_auth_file_for_provider() { printf '%s\n' ".claude/settings.json" printf '%s\n' ".claude/settings.local.json" ;; - gemini | gemini-cli | google-gemini-cli) - printf '%s\n' ".gemini/settings.json" - ;; esac } diff --git a/src/agents/cli-backends.test.ts b/src/agents/cli-backends.test.ts index be42308f5d0..c42b3835da7 100644 --- a/src/agents/cli-backends.test.ts +++ b/src/agents/cli-backends.test.ts @@ -6,6 +6,7 @@ let createEmptyPluginRegistry: typeof import("../plugins/registry.js").createEmp let setActivePluginRegistry: typeof import("../plugins/runtime.js").setActivePluginRegistry; let normalizeClaudeBackendConfig: typeof import("./cli-backends.js").normalizeClaudeBackendConfig; let resolveCliBackendConfig: typeof import("./cli-backends.js").resolveCliBackendConfig; +let resolveCliBackendLiveTest: typeof import("./cli-backends.js").resolveCliBackendLiveTest; function createBackendEntry(params: { pluginId: string; @@ -22,6 +23,35 @@ function createBackendEntry(params: { config: params.config, ...(params.bundleMcp ? { bundleMcp: params.bundleMcp } : {}), ...(params.normalizeConfig ? { normalizeConfig: params.normalizeConfig } : {}), + liveTest: { + defaultModelRef: + params.id === "claude-cli" + ? "claude-cli/claude-sonnet-4-6" + : params.id === "codex-cli" + ? "codex-cli/gpt-5.4" + : params.id === "google-gemini-cli" + ? "google-gemini-cli/gemini-3.1-pro-preview" + : undefined, + defaultImageProbe: true, + docker: { + npmPackage: + params.id === "claude-cli" + ? "@anthropic-ai/claude-code" + : params.id === "codex-cli" + ? "@openai/codex" + : params.id === "google-gemini-cli" + ? "@google/gemini-cli" + : undefined, + binaryName: + params.id === "claude-cli" + ? "claude" + : params.id === "codex-cli" + ? "codex" + : params.id === "google-gemini-cli" + ? "gemini" + : undefined, + }, + }, }, }; } @@ -32,7 +62,8 @@ beforeEach(async () => { vi.resetModules(); ({ createEmptyPluginRegistry } = await import("../plugins/registry.js")); ({ setActivePluginRegistry } = await import("../plugins/runtime.js")); - ({ normalizeClaudeBackendConfig, resolveCliBackendConfig } = await import("./cli-backends.js")); + ({ normalizeClaudeBackendConfig, resolveCliBackendConfig, resolveCliBackendLiveTest } = + await import("./cli-backends.js")); const registry = createEmptyPluginRegistry(); registry.cliBackends = [ createBackendEntry({ @@ -103,16 +134,7 @@ beforeEach(async () => { "workspace-write", "--skip-git-repo-check", ], - resumeArgs: [ - "exec", - "resume", - "{sessionId}", - "--color", - "never", - "--sandbox", - "workspace-write", - "--skip-git-repo-check", - ], + resumeArgs: ["exec", "resume", "{sessionId}", "--dangerously-bypass-approvals-and-sandbox"], reliability: { watchdog: { fresh: { @@ -135,8 +157,8 @@ beforeEach(async () => { bundleMcp: false, config: { command: "gemini", - args: ["--prompt", "--output-format", "json"], - resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"], + args: ["--output-format", "json", "--prompt", "{prompt}"], + resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"], modelArg: "--model", sessionMode: "existing", sessionIdFields: ["session_id", "sessionId"], @@ -165,11 +187,7 @@ describe("resolveCliBackendConfig reliability merge", () => { "exec", "resume", "{sessionId}", - "--color", - "never", - "--sandbox", - "workspace-write", - "--skip-git-repo-check", + "--dangerously-bypass-approvals-and-sandbox", ]); }); @@ -205,6 +223,26 @@ describe("resolveCliBackendConfig reliability merge", () => { }); }); +describe("resolveCliBackendLiveTest", () => { + it("returns plugin-owned live smoke metadata for codex", () => { + expect(resolveCliBackendLiveTest("codex-cli")).toEqual({ + defaultModelRef: "codex-cli/gpt-5.4", + defaultImageProbe: true, + dockerNpmPackage: "@openai/codex", + dockerBinaryName: "codex", + }); + }); + + it("returns plugin-owned live smoke metadata for gemini", () => { + expect(resolveCliBackendLiveTest("google-gemini-cli")).toEqual({ + defaultModelRef: "google-gemini-cli/gemini-3.1-pro-preview", + defaultImageProbe: true, + dockerNpmPackage: "@google/gemini-cli", + dockerBinaryName: "gemini", + }); + }); +}); + describe("resolveCliBackendConfig claude-cli defaults", () => { it("uses non-interactive permission-mode defaults for fresh and resume args", () => { const resolved = resolveCliBackendConfig("claude-cli"); @@ -585,13 +623,14 @@ describe("resolveCliBackendConfig google-gemini-cli defaults", () => { expect(resolved).not.toBeNull(); expect(resolved?.bundleMcp).toBe(false); - expect(resolved?.config.args).toEqual(["--prompt", "--output-format", "json"]); + expect(resolved?.config.args).toEqual(["--output-format", "json", "--prompt", "{prompt}"]); expect(resolved?.config.resumeArgs).toEqual([ "--resume", "{sessionId}", - "--prompt", "--output-format", "json", + "--prompt", + "{prompt}", ]); expect(resolved?.config.modelArg).toBe("--model"); expect(resolved?.config.sessionMode).toBe("existing"); diff --git a/src/agents/cli-runner.helpers.test.ts b/src/agents/cli-runner.helpers.test.ts index 62d0b58c710..9b553d0d971 100644 --- a/src/agents/cli-runner.helpers.test.ts +++ b/src/agents/cli-runner.helpers.test.ts @@ -142,6 +142,28 @@ describe("buildCliArgs", () => { }), ).toEqual(["-p", "--append-system-prompt", "Stable prefix\nDynamic suffix"]); }); + + it("replaces prompt placeholders before falling back to a trailing positional prompt", () => { + expect( + buildCliArgs({ + backend: { + command: "gemini", + modelArg: "--model", + }, + baseArgs: ["--output-format", "json", "--prompt", "{prompt}"], + modelId: "gemini-3.1-pro-preview", + promptArg: "describe the image", + useResume: false, + }), + ).toEqual([ + "--output-format", + "json", + "--prompt", + "describe the image", + "--model", + "gemini-3.1-pro-preview", + ]); + }); }); describe("writeCliImages", () => { diff --git a/src/agents/cli-runner.test-support.ts b/src/agents/cli-runner.test-support.ts index d4a1662811a..a95f88d1f95 100644 --- a/src/agents/cli-runner.test-support.ts +++ b/src/agents/cli-runner.test-support.ts @@ -250,8 +250,8 @@ function buildGoogleGeminiCliBackendFixture(): CliBackendPlugin { id: "google-gemini-cli", config: { command: "gemini", - args: ["--prompt", "--output-format", "json"], - resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"], + args: ["--output-format", "json", "--prompt", "{prompt}"], + resumeArgs: ["--resume", "{sessionId}", "--output-format", "json", "--prompt", "{prompt}"], output: "json", input: "arg", modelArg: "--model", diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 33dce9f3dc0..2aeee66c372 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -333,6 +333,16 @@ export function buildCliArgs(params: { } } if (params.promptArg !== undefined) { + let replacedPromptPlaceholder = false; + for (let i = 0; i < args.length; i += 1) { + if (args[i] === "{prompt}") { + args[i] = params.promptArg; + replacedPromptPlaceholder = true; + } + } + if (replacedPromptPlaceholder) { + return args; + } args.push(params.promptArg); } return args; diff --git a/test/test-env.ts b/test/test-env.ts index a19f4b4cc7f..11a46535120 100644 --- a/test/test-env.ts +++ b/test/test-env.ts @@ -7,7 +7,7 @@ import JSON5 from "json5"; type RestoreEntry = { key: string; value: string | undefined }; -const LIVE_EXTERNAL_AUTH_DIRS = [".claude", ".codex", ".minimax"] as const; +const LIVE_EXTERNAL_AUTH_DIRS = [".claude", ".codex", ".gemini", ".minimax"] as const; const LIVE_EXTERNAL_AUTH_FILES = [".claude.json"] as const; const requireFromHere = createRequire(import.meta.url); @@ -366,6 +366,7 @@ function stageLiveTestState(params: { } const tempStateDir = path.join(params.tempHome, ".openclaw"); fs.mkdirSync(tempStateDir, { recursive: true }); + fs.mkdirSync(path.join(params.tempHome, ".gemini"), { recursive: true }); const realConfigPath = params.env.OPENCLAW_CONFIG_PATH?.trim() ? resolveHomeRelativePath(params.env.OPENCLAW_CONFIG_PATH, params.realHome)