import { describe, expect, it } from "vitest"; import { buildAnthropicCliBackend } from "./cli-backend.js"; import { CLAUDE_CLI_CLEAR_ENV, normalizeClaudeBackendConfig, normalizeClaudePermissionArgs, normalizeClaudeSettingSourcesArgs, resolveClaudePermissionMode, resolveClaudeCliExecutionArgs, } from "./cli-shared.js"; describe("normalizeClaudePermissionArgs", () => { it("leaves args alone when they omit permission flags", () => { expect( normalizeClaudePermissionArgs(["-p", "--output-format", "stream-json", "--verbose"]), ).toEqual(["-p", "--output-format", "stream-json", "--verbose"]); }); it("removes legacy skip-permissions without adding bypassPermissions", () => { expect( normalizeClaudePermissionArgs(["-p", "--dangerously-skip-permissions", "--verbose"]), ).toEqual(["-p", "--verbose"]); }); it("keeps explicit permission-mode overrides", () => { expect(normalizeClaudePermissionArgs(["-p", "--permission-mode", "acceptEdits"])).toEqual([ "-p", "--permission-mode", "acceptEdits", ]); expect(normalizeClaudePermissionArgs(["-p", "--permission-mode=acceptEdits"])).toEqual([ "-p", "--permission-mode=acceptEdits", ]); }); it("drops malformed permission-mode flags in both split and equals forms", () => { expect( normalizeClaudePermissionArgs(["-p", "--permission-mode", "--output-format", "stream-json"]), ).toEqual(["-p", "--output-format", "stream-json"]); expect(normalizeClaudePermissionArgs(["-p", "--permission-mode="])).toEqual(["-p"]); expect(normalizeClaudePermissionArgs(["-p", "--permission-mode=--output-format"])).toEqual([ "-p", ]); }); }); describe("normalizeClaudeSettingSourcesArgs", () => { it("injects user-only setting sources when args omit the flag", () => { expect( normalizeClaudeSettingSourcesArgs(["-p", "--output-format", "stream-json", "--verbose"]), ).toEqual(["-p", "--output-format", "stream-json", "--verbose", "--setting-sources", "user"]); }); it("forces explicit project or local setting sources back to user-only", () => { expect(normalizeClaudeSettingSourcesArgs(["-p", "--setting-sources", "project"])).toEqual([ "-p", "--setting-sources", "user", ]); expect(normalizeClaudeSettingSourcesArgs(["-p", "--setting-sources=local,user"])).toEqual([ "-p", "--setting-sources=user", ]); }); it("treats a bare setting-sources flag as malformed and falls back to user-only", () => { expect( normalizeClaudeSettingSourcesArgs([ "-p", "--setting-sources", "--output-format", "stream-json", ]), ).toEqual(["-p", "--output-format", "stream-json", "--setting-sources", "user"]); }); }); describe("resolveClaudeCliExecutionArgs", () => { it("omits effort args when thinking is off", () => { expect( resolveClaudeCliExecutionArgs({ workspaceDir: "/tmp", provider: "claude-cli", modelId: "claude-sonnet-4-6", thinkingLevel: "off", useResume: false, baseArgs: ["-p", "--output-format", "stream-json"], }), ).toEqual(["-p", "--output-format", "stream-json"]); }); it("maps OpenClaw thinking levels to Claude effort args", () => { expect( resolveClaudeCliExecutionArgs({ workspaceDir: "/tmp", provider: "claude-cli", modelId: "claude-opus-4-7", thinkingLevel: "minimal", useResume: false, baseArgs: ["-p"], }), ).toEqual(["-p", "--effort", "low"]); expect( resolveClaudeCliExecutionArgs({ workspaceDir: "/tmp", provider: "claude-cli", modelId: "claude-opus-4-7", thinkingLevel: "adaptive", useResume: false, baseArgs: ["-p"], }), ).toEqual(["-p", "--effort", "medium"]); expect( resolveClaudeCliExecutionArgs({ workspaceDir: "/tmp", provider: "claude-cli", modelId: "claude-opus-4-7", thinkingLevel: "xhigh", useResume: true, baseArgs: ["-p", "--resume", "{sessionId}"], }), ).toEqual(["-p", "--resume", "{sessionId}", "--effort", "xhigh"]); }); it("replaces static effort args when a session thinking level is active", () => { expect( resolveClaudeCliExecutionArgs({ workspaceDir: "/tmp", provider: "claude-cli", modelId: "claude-opus-4-7", thinkingLevel: "max", useResume: false, baseArgs: ["-p", "--effort", "low", "--effort=high"], }), ).toEqual(["-p", "--effort", "max"]); }); }); describe("normalizeClaudeBackendConfig", () => { it("normalizes both args and resumeArgs for custom overrides", () => { const normalized = normalizeClaudeBackendConfig({ command: "claude", args: ["-p", "--output-format", "stream-json", "--verbose"], resumeArgs: ["-p", "--output-format", "stream-json", "--verbose", "--resume", "{sessionId}"], }); expect(normalized.args).toEqual([ "-p", "--output-format", "stream-json", "--verbose", "--setting-sources", "user", "--permission-mode", "bypassPermissions", ]); expect(normalized.resumeArgs).toEqual([ "-p", "--output-format", "stream-json", "--verbose", "--resume", "{sessionId}", "--setting-sources", "user", "--permission-mode", "bypassPermissions", ]); expect(normalized.output).toBe("jsonl"); expect(normalized.liveSession).toBe("claude-stdio"); expect(normalized.input).toBe("stdin"); }); it("derives Claude bypass from OpenClaw YOLO policy and disables it for safer policy", () => { expect(resolveClaudePermissionMode({ backendId: "claude-cli" })).toEqual({ mode: "bypassPermissions", overrideExisting: false, }); expect( resolveClaudePermissionMode({ backendId: "claude-cli", config: { tools: { exec: { security: "allowlist", ask: "on-miss" } } }, }), ).toEqual({ overrideExisting: false }); }); it("derives Claude bypass from per-agent OpenClaw exec policy", () => { expect( resolveClaudePermissionMode({ backendId: "claude-cli", agentId: "safe-agent", config: { tools: { exec: { security: "full", ask: "off" } }, agents: { list: [ { id: "safe-agent", tools: { exec: { security: "allowlist", ask: "on-miss" } }, }, ], }, }, }), ).toEqual({ overrideExisting: false }); expect( resolveClaudePermissionMode({ backendId: "claude-cli", agentId: "yolo-agent", config: { tools: { exec: { security: "allowlist", ask: "on-miss" } }, agents: { list: [ { id: "yolo-agent", tools: { exec: { security: "full", ask: "off" } }, }, ], }, }, }), ).toEqual({ mode: "bypassPermissions", overrideExisting: false, }); }); it("does not infer live stdio when explicit transport overrides are incompatible", () => { const normalized = normalizeClaudeBackendConfig({ command: "claude", output: "json", input: "arg", }); expect(normalized.output).toBe("json"); expect(normalized.liveSession).toBeUndefined(); expect(normalized.input).toBe("arg"); }); it("is wired through the anthropic cli backend normalize hook", () => { const backend = buildAnthropicCliBackend(); const normalizeConfig = backend.normalizeConfig; expect(normalizeConfig).toBeTypeOf("function"); const normalized = normalizeConfig?.({ ...backend.config, args: ["-p", "--output-format", "stream-json", "--verbose"], resumeArgs: ["-p", "--output-format", "stream-json", "--verbose", "--resume", "{sessionId}"], }); expect(normalized?.args).toContain("--setting-sources"); expect(normalized?.args).toContain("user"); expect(normalized?.args).toContain("--permission-mode"); expect(normalized?.args).toContain("bypassPermissions"); expect(normalized?.resumeArgs).toContain("--setting-sources"); expect(normalized?.resumeArgs).toContain("user"); expect(normalized?.resumeArgs).toContain("--permission-mode"); expect(normalized?.resumeArgs).toContain("bypassPermissions"); expect(normalized?.liveSession).toBe("claude-stdio"); expect(backend.resolveExecutionArgs).toBe(resolveClaudeCliExecutionArgs); }); it("leaves claude cli subscription-managed, restricts setting sources, and clears inherited env overrides", () => { const backend = buildAnthropicCliBackend(); expect(backend.config.env).toBeUndefined(); expect(backend.config.liveSession).toBe("claude-stdio"); expect(backend.config.output).toBe("jsonl"); expect(backend.config.input).toBe("stdin"); expect(backend.config.args).toContain("--setting-sources"); expect(backend.config.args).toContain("user"); expect(backend.config.resumeArgs).toContain("--setting-sources"); expect(backend.config.resumeArgs).toContain("user"); expect(backend.config.clearEnv).toEqual([...CLAUDE_CLI_CLEAR_ENV]); expect(backend.config.clearEnv).toContain("ANTHROPIC_API_TOKEN"); expect(backend.config.clearEnv).toContain("ANTHROPIC_BASE_URL"); expect(backend.config.clearEnv).toContain("ANTHROPIC_CUSTOM_HEADERS"); expect(backend.config.clearEnv).toContain("ANTHROPIC_OAUTH_TOKEN"); expect(backend.config.clearEnv).toContain("CLAUDE_CONFIG_DIR"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_USE_BEDROCK"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_OAUTH_TOKEN"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_PLUGIN_CACHE_DIR"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_PLUGIN_SEED_DIR"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_REMOTE"); expect(backend.config.clearEnv).toContain("CLAUDE_CODE_USE_COWORK_PLUGINS"); expect(backend.config.clearEnv).toContain("OTEL_METRICS_EXPORTER"); expect(backend.config.clearEnv).toContain("OTEL_EXPORTER_OTLP_PROTOCOL"); expect(backend.config.clearEnv).toContain("OTEL_SDK_DISABLED"); }); });