diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dfa2981103..bfd0b99176d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,7 +117,7 @@ Docs: https://docs.openclaw.ai - MS Teams: replace the deprecated Teams SDK HttpPlugin stub with `httpServerAdapter` so recurring gateway deprecation warnings stop firing and the Express 5 compatibility workaround stays on the supported SDK path. (#60939) Thanks @coolramukaka-sys. - CLI/Commander: preserve Commander-computed exit codes for argument and help-error paths, and cover the user-argv parse mode in the regression tests so invalid CLI invocations no longer report success when exits are intercepted. (#60923) Thanks @Linux2010. - Telegram/native command menu: trim long menu descriptions before dropping commands so sub-100 command sets can still fit Telegram's payload budget and keep more `/` entries visible. (#61129) Thanks @neeravmakwana. -- Agents/Claude CLI: keep non-interactive `--permission-mode bypassPermissions` when custom `cliBackends.claude-cli.args` override defaults, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow. +- Agents/Claude CLI: keep non-interactive `--permission-mode bypassPermissions` when custom `cliBackends.claude-cli.args` override defaults, including fallback resolution before the runtime plugin registry is active, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow. - Agents/skills: skip `.git` and `node_modules` when mirroring skills into sandbox workspaces so read-only sandboxes do not copy repo history or dependency trees. (#61090) Thanks @joelnishanth. - Android/Talk Mode: cancel in-flight `talk.speak` playback when speech is explicitly stopped, so stale replies stop starting after barge-in or manual stop. (#61164) Thanks @obviyus. - Plugins/onboarding: write dotted plugin uiHint paths like Brave `webSearch.mode` as nested plugin config so `llm-context` setup stops failing validation. (#61159) Thanks @obviyus. diff --git a/extensions/anthropic/api.ts b/extensions/anthropic/api.ts index 6fcd8f8e147..1595674a01a 100644 --- a/extensions/anthropic/api.ts +++ b/extensions/anthropic/api.ts @@ -1,4 +1,8 @@ -export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js"; +export { + CLAUDE_CLI_BACKEND_ID, + isClaudeCliProvider, + normalizeClaudeBackendConfig, +} from "./cli-shared.js"; export { createAnthropicBetaHeadersWrapper, createAnthropicFastModeWrapper, diff --git a/src/agents/cli-backends.test.ts b/src/agents/cli-backends.test.ts index c4d22734069..a3b14e36f1a 100644 --- a/src/agents/cli-backends.test.ts +++ b/src/agents/cli-backends.test.ts @@ -331,7 +331,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => { expect(resolved?.config.resumeArgs).toContain("bypassPermissions"); }); - it("keeps bundle MCP enabled for override-only claude-cli config when the plugin registry is absent", () => { + it("normalizes override-only claude-cli config when the plugin registry is absent", () => { const registry = createEmptyPluginRegistry(); setActivePluginRegistry(registry); @@ -342,6 +342,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => { "claude-cli": { command: "/usr/local/bin/claude", args: ["-p", "--output-format", "json"], + resumeArgs: ["-p", "--output-format", "json", "--resume", "{sessionId}"], }, }, }, @@ -352,6 +353,22 @@ describe("resolveCliBackendConfig claude-cli defaults", () => { expect(resolved).not.toBeNull(); expect(resolved?.bundleMcp).toBe(true); + expect(resolved?.config.args).toEqual([ + "-p", + "--output-format", + "json", + "--permission-mode", + "bypassPermissions", + ]); + expect(resolved?.config.resumeArgs).toEqual([ + "-p", + "--output-format", + "json", + "--resume", + "{sessionId}", + "--permission-mode", + "bypassPermissions", + ]); }); }); diff --git a/src/agents/cli-backends.ts b/src/agents/cli-backends.ts index 1bd9286f6e2..11f224e88ab 100644 --- a/src/agents/cli-backends.ts +++ b/src/agents/cli-backends.ts @@ -1,3 +1,7 @@ +import { + CLAUDE_CLI_BACKEND_ID, + normalizeClaudeBackendConfig, +} from "../../extensions/anthropic/api.js"; import type { OpenClawConfig } from "../config/config.js"; import type { CliBackendConfig } from "../config/types.js"; import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js"; @@ -10,12 +14,20 @@ export type ResolvedCliBackend = { pluginId?: string; }; -function resolveFallbackBundleMcpCapability(provider: string): boolean { - // Claude CLI consumes explicit MCP config overlays even when the runtime - // plugin registry is not initialized yet (for example direct runner tests or - // narrow non-gateway entrypoints). - return provider === "claude-cli"; -} +type FallbackCliBackendPolicy = { + bundleMcp: boolean; + normalizeConfig?: (config: CliBackendConfig) => CliBackendConfig; +}; + +const FALLBACK_CLI_BACKEND_POLICIES: Record = { + [CLAUDE_CLI_BACKEND_ID]: { + // Claude CLI consumes explicit MCP config overlays even when the runtime + // plugin registry is not initialized yet (for example direct runner tests + // or narrow non-gateway entrypoints). + bundleMcp: true, + normalizeConfig: normalizeClaudeBackendConfig, + }, +}; function normalizeBackendKey(key: string): string { return normalizeProviderId(key); @@ -96,6 +108,7 @@ export function resolveCliBackendConfig( cfg?: OpenClawConfig, ): ResolvedCliBackend | null { const normalized = normalizeBackendKey(provider); + const fallbackPolicy = FALLBACK_CLI_BACKEND_POLICIES[normalized]; const configured = cfg?.agents?.defaults?.cliBackends ?? {}; const override = pickBackendConfig(configured, normalized); const registered = resolveRegisteredBackend(normalized); @@ -117,13 +130,16 @@ export function resolveCliBackendConfig( if (!override) { return null; } - const command = override.command?.trim(); + const config = fallbackPolicy?.normalizeConfig + ? fallbackPolicy.normalizeConfig(override) + : override; + const command = config.command?.trim(); if (!command) { return null; } return { id: normalized, - config: { ...override, command }, - bundleMcp: resolveFallbackBundleMcpCapability(normalized), + config: { ...config, command }, + bundleMcp: fallbackPolicy?.bundleMcp, }; }