mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 23:40:45 +00:00
* agents: switch claude-cli defaults to bypassPermissions * agents: add claude-cli default args coverage * agents: emit watchdog stall system event for cli runs * agents: test cli watchdog stall system event * acpx: fallback to sessions new when ensure returns no ids * acpx tests: mock sessions new fallback path * acpx tests: cover ensure-empty fallback flow * skills: clarify claude print mode without pty * docs: update cli-backends claude default args * docs: refresh cli live test default args * gateway tests: align live claude args defaults * changelog: credit claude/acpx reliability fixes * Agents: normalize legacy Claude permission flag overrides * Tests: cover legacy Claude permission override normalization * Changelog: note legacy Claude permission flag auto-normalization * ACPX: fail fast when ensure/new return no session IDs * ACPX tests: support empty sessions new fixture output * ACPX tests: assert ensureSession failure when IDs missing * CLI runner: scope watchdog heartbeat wake to session * CLI runner tests: assert session-scoped watchdog wake * Update CHANGELOG.md
244 lines
6.7 KiB
TypeScript
244 lines
6.7 KiB
TypeScript
import type { OpenClawConfig } from "../config/config.js";
|
|
import type { CliBackendConfig } from "../config/types.js";
|
|
import {
|
|
CLI_FRESH_WATCHDOG_DEFAULTS,
|
|
CLI_RESUME_WATCHDOG_DEFAULTS,
|
|
} from "./cli-watchdog-defaults.js";
|
|
import { normalizeProviderId } from "./model-selection.js";
|
|
|
|
export type ResolvedCliBackend = {
|
|
id: string;
|
|
config: CliBackendConfig;
|
|
};
|
|
|
|
const CLAUDE_MODEL_ALIASES: Record<string, string> = {
|
|
opus: "opus",
|
|
"opus-4.6": "opus",
|
|
"opus-4.5": "opus",
|
|
"opus-4": "opus",
|
|
"claude-opus-4-6": "opus",
|
|
"claude-opus-4-5": "opus",
|
|
"claude-opus-4": "opus",
|
|
sonnet: "sonnet",
|
|
"sonnet-4.6": "sonnet",
|
|
"sonnet-4.5": "sonnet",
|
|
"sonnet-4.1": "sonnet",
|
|
"sonnet-4.0": "sonnet",
|
|
"claude-sonnet-4-6": "sonnet",
|
|
"claude-sonnet-4-5": "sonnet",
|
|
"claude-sonnet-4-1": "sonnet",
|
|
"claude-sonnet-4-0": "sonnet",
|
|
haiku: "haiku",
|
|
"haiku-3.5": "haiku",
|
|
"claude-haiku-3-5": "haiku",
|
|
};
|
|
|
|
const CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG = "--dangerously-skip-permissions";
|
|
const CLAUDE_PERMISSION_MODE_ARG = "--permission-mode";
|
|
const CLAUDE_BYPASS_PERMISSIONS_MODE = "bypassPermissions";
|
|
|
|
const DEFAULT_CLAUDE_BACKEND: CliBackendConfig = {
|
|
command: "claude",
|
|
args: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions"],
|
|
resumeArgs: [
|
|
"-p",
|
|
"--output-format",
|
|
"json",
|
|
"--permission-mode",
|
|
"bypassPermissions",
|
|
"--resume",
|
|
"{sessionId}",
|
|
],
|
|
output: "json",
|
|
input: "arg",
|
|
modelArg: "--model",
|
|
modelAliases: CLAUDE_MODEL_ALIASES,
|
|
sessionArg: "--session-id",
|
|
sessionMode: "always",
|
|
sessionIdFields: ["session_id", "sessionId", "conversation_id", "conversationId"],
|
|
systemPromptArg: "--append-system-prompt",
|
|
systemPromptMode: "append",
|
|
systemPromptWhen: "first",
|
|
clearEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"],
|
|
reliability: {
|
|
watchdog: {
|
|
fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS },
|
|
resume: { ...CLI_RESUME_WATCHDOG_DEFAULTS },
|
|
},
|
|
},
|
|
serialize: true,
|
|
};
|
|
|
|
const DEFAULT_CODEX_BACKEND: CliBackendConfig = {
|
|
command: "codex",
|
|
args: ["exec", "--json", "--color", "never", "--sandbox", "read-only", "--skip-git-repo-check"],
|
|
resumeArgs: [
|
|
"exec",
|
|
"resume",
|
|
"{sessionId}",
|
|
"--color",
|
|
"never",
|
|
"--sandbox",
|
|
"read-only",
|
|
"--skip-git-repo-check",
|
|
],
|
|
output: "jsonl",
|
|
resumeOutput: "text",
|
|
input: "arg",
|
|
modelArg: "--model",
|
|
sessionIdFields: ["thread_id"],
|
|
sessionMode: "existing",
|
|
imageArg: "--image",
|
|
imageMode: "repeat",
|
|
reliability: {
|
|
watchdog: {
|
|
fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS },
|
|
resume: { ...CLI_RESUME_WATCHDOG_DEFAULTS },
|
|
},
|
|
},
|
|
serialize: true,
|
|
};
|
|
|
|
function normalizeBackendKey(key: string): string {
|
|
return normalizeProviderId(key);
|
|
}
|
|
|
|
function pickBackendConfig(
|
|
config: Record<string, CliBackendConfig>,
|
|
normalizedId: string,
|
|
): CliBackendConfig | undefined {
|
|
for (const [key, entry] of Object.entries(config)) {
|
|
if (normalizeBackendKey(key) === normalizedId) {
|
|
return entry;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function mergeBackendConfig(base: CliBackendConfig, override?: CliBackendConfig): CliBackendConfig {
|
|
if (!override) {
|
|
return { ...base };
|
|
}
|
|
const baseFresh = base.reliability?.watchdog?.fresh ?? {};
|
|
const baseResume = base.reliability?.watchdog?.resume ?? {};
|
|
const overrideFresh = override.reliability?.watchdog?.fresh ?? {};
|
|
const overrideResume = override.reliability?.watchdog?.resume ?? {};
|
|
return {
|
|
...base,
|
|
...override,
|
|
args: override.args ?? base.args,
|
|
env: { ...base.env, ...override.env },
|
|
modelAliases: { ...base.modelAliases, ...override.modelAliases },
|
|
clearEnv: Array.from(new Set([...(base.clearEnv ?? []), ...(override.clearEnv ?? [])])),
|
|
sessionIdFields: override.sessionIdFields ?? base.sessionIdFields,
|
|
sessionArgs: override.sessionArgs ?? base.sessionArgs,
|
|
resumeArgs: override.resumeArgs ?? base.resumeArgs,
|
|
reliability: {
|
|
...base.reliability,
|
|
...override.reliability,
|
|
watchdog: {
|
|
...base.reliability?.watchdog,
|
|
...override.reliability?.watchdog,
|
|
fresh: {
|
|
...baseFresh,
|
|
...overrideFresh,
|
|
},
|
|
resume: {
|
|
...baseResume,
|
|
...overrideResume,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function normalizeClaudePermissionArgs(args?: string[]): string[] | undefined {
|
|
if (!args) {
|
|
return args;
|
|
}
|
|
const normalized: string[] = [];
|
|
let sawLegacySkip = false;
|
|
let hasPermissionMode = false;
|
|
for (let i = 0; i < args.length; i += 1) {
|
|
const arg = args[i];
|
|
if (arg === CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG) {
|
|
sawLegacySkip = true;
|
|
continue;
|
|
}
|
|
if (arg === CLAUDE_PERMISSION_MODE_ARG) {
|
|
hasPermissionMode = true;
|
|
normalized.push(arg);
|
|
const maybeValue = args[i + 1];
|
|
if (typeof maybeValue === "string") {
|
|
normalized.push(maybeValue);
|
|
i += 1;
|
|
}
|
|
continue;
|
|
}
|
|
if (arg.startsWith(`${CLAUDE_PERMISSION_MODE_ARG}=`)) {
|
|
hasPermissionMode = true;
|
|
}
|
|
normalized.push(arg);
|
|
}
|
|
if (sawLegacySkip && !hasPermissionMode) {
|
|
normalized.push(CLAUDE_PERMISSION_MODE_ARG, CLAUDE_BYPASS_PERMISSIONS_MODE);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function normalizeClaudeBackendConfig(config: CliBackendConfig): CliBackendConfig {
|
|
return {
|
|
...config,
|
|
args: normalizeClaudePermissionArgs(config.args),
|
|
resumeArgs: normalizeClaudePermissionArgs(config.resumeArgs),
|
|
};
|
|
}
|
|
|
|
export function resolveCliBackendIds(cfg?: OpenClawConfig): Set<string> {
|
|
const ids = new Set<string>([
|
|
normalizeBackendKey("claude-cli"),
|
|
normalizeBackendKey("codex-cli"),
|
|
]);
|
|
const configured = cfg?.agents?.defaults?.cliBackends ?? {};
|
|
for (const key of Object.keys(configured)) {
|
|
ids.add(normalizeBackendKey(key));
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
export function resolveCliBackendConfig(
|
|
provider: string,
|
|
cfg?: OpenClawConfig,
|
|
): ResolvedCliBackend | null {
|
|
const normalized = normalizeBackendKey(provider);
|
|
const configured = cfg?.agents?.defaults?.cliBackends ?? {};
|
|
const override = pickBackendConfig(configured, normalized);
|
|
|
|
if (normalized === "claude-cli") {
|
|
const merged = mergeBackendConfig(DEFAULT_CLAUDE_BACKEND, override);
|
|
const config = normalizeClaudeBackendConfig(merged);
|
|
const command = config.command?.trim();
|
|
if (!command) {
|
|
return null;
|
|
}
|
|
return { id: normalized, config: { ...config, command } };
|
|
}
|
|
if (normalized === "codex-cli") {
|
|
const merged = mergeBackendConfig(DEFAULT_CODEX_BACKEND, override);
|
|
const command = merged.command?.trim();
|
|
if (!command) {
|
|
return null;
|
|
}
|
|
return { id: normalized, config: { ...merged, command } };
|
|
}
|
|
|
|
if (!override) {
|
|
return null;
|
|
}
|
|
const command = override.command?.trim();
|
|
if (!command) {
|
|
return null;
|
|
}
|
|
return { id: normalized, config: { ...override, command } };
|
|
}
|