import { describe, expect, it } from "vitest"; import { getAgentSettings, setAgentSettings } from "./agent-settings"; // --------------------------------------------------------------------------- // Helpers — drill into the written `[thinking]` / `[loop_control]` sections // --------------------------------------------------------------------------- function thinkingOf(raw: unknown): Record { const t = (raw as { thinking?: unknown })?.thinking; return t && typeof t === "object" && !Array.isArray(t) ? (t as Record) : {}; } function loopOf(raw: unknown): Record { const l = (raw as { loop_control?: unknown })?.loop_control; return l && typeof l === "object" && !Array.isArray(l) ? (l as Record) : {}; } // --------------------------------------------------------------------------- // Reading — legacy off values normalized to "off", "max" → "high" // --------------------------------------------------------------------------- describe("getAgentSettings — thinking.keep normalization", () => { it("normalizes a legacy boolean `false` to \"off\"", () => { expect(getAgentSettings({ thinking: { keep: false } }).thinking?.keep).toBe( "off" ); }); it("normalizes the other legacy off values (0, null, \"no\", \"none\") to \"off\"", () => { for (const v of [0, null, "no", "none"]) { expect( getAgentSettings({ thinking: { keep: v } }).thinking?.keep ).toBe("off"); } }); it("keeps \"all\" and \"off\" as-is on read", () => { expect(getAgentSettings({ thinking: { keep: "all" } }).thinking?.keep).toBe( "all" ); expect(getAgentSettings({ thinking: { keep: "off" } }).thinking?.keep).toBe( "off" ); }); it("keeps the default \"all\" when the key is absent", () => { expect(getAgentSettings({}).thinking?.keep).toBe("all"); }); }); describe("getAgentSettings — effort \"max\" read mapping", () => { it("normalizes a stored \"max\" to \"high\" on read", () => { expect(getAgentSettings({ thinking: { effort: "max" } }).thinking?.effort).toBe( "high" ); }); }); // --------------------------------------------------------------------------- // Writing — keep: "all" round-trips, "off" is written for legacy off values, // absent keep is not materialized // --------------------------------------------------------------------------- describe("setAgentSettings — keep serialization", () => { it("round-trips keep = \"all\"", () => { const raw = { thinking: { keep: "all" } }; const next = setAgentSettings(raw, { thinking: { keep: "all" } }); expect(thinkingOf(next).keep).toBe("all"); }); it("writes \"off\" (string) for a legacy keep = false, never a boolean", () => { const next = setAgentSettings({ thinking: { keep: false } }, {}); expect(thinkingOf(next).keep).toBe("off"); }); it("does not write the keep key when it was never set (absent in raw and patch)", () => { expect(thinkingOf(setAgentSettings({}, {}))).not.toHaveProperty("keep"); const next = setAgentSettings( { thinking: { enabled: true } }, { thinking: { effort: "high" } } ); expect(thinkingOf(next)).not.toHaveProperty("keep"); expect(thinkingOf(next).effort).toBe("high"); }); it("writes the key when an absent raw config's keep is explicitly toggled off", () => { const next = setAgentSettings({}, { thinking: { keep: "off" } }); expect(thinkingOf(next).keep).toBe("off"); }); }); // --------------------------------------------------------------------------- // loop_control — v2 default writes only max_attempts_per_step; legacyV1 opts // dual-writes; read fallback from the v1 key is preserved // --------------------------------------------------------------------------- describe("loop_control — v1/v2 key handling", () => { const raw = { loop_control: { max_attempts_per_step: 5, max_retries_per_step: 5 } }; it("default (v2): a save strips the legacy max_retries_per_step key", () => { const next = setAgentSettings(raw, { loop_control: { reserved_context_size: 60000 } }); expect(loopOf(next).max_attempts_per_step).toBe(5); expect(loopOf(next)).not.toHaveProperty("max_retries_per_step"); }); it("default (v2): a dual-write patch ends up with only the v2 key", () => { const next = setAgentSettings( raw, { loop_control: { max_attempts_per_step: 7, max_retries_per_step: 7 } } ); expect(loopOf(next).max_attempts_per_step).toBe(7); expect(loopOf(next)).not.toHaveProperty("max_retries_per_step"); }); it("legacyV1: both keys are written", () => { const next = setAgentSettings( raw, { loop_control: { max_attempts_per_step: 7, max_retries_per_step: 7 } }, { legacyV1: true } ); expect(loopOf(next).max_attempts_per_step).toBe(7); expect(loopOf(next).max_retries_per_step).toBe(7); }); it("read fallback keeps a max_retries_per_step-only config editable", () => { const s = getAgentSettings({ loop_control: { max_retries_per_step: 4 } }); expect(s.loop_control?.max_attempts_per_step).toBe(4); expect(s.loop_control?.max_retries_per_step).toBe(4); }); }); // --------------------------------------------------------------------------- // [permission] dangerous_command_guard (kimi-code 0.40.1) — absent key means // upstream default ON; an explicit false must survive even with no rules // --------------------------------------------------------------------------- describe("permission.dangerous_command_guard", () => { it("reads as undefined when the key is absent (default on)", () => { expect( getAgentSettings({}).permission?.dangerous_command_guard ).toBeUndefined(); expect( getAgentSettings({ permission: { rules: [] } }).permission ?.dangerous_command_guard ).toBeUndefined(); }); it("reads an explicit true / false", () => { expect( getAgentSettings({ permission: { dangerous_command_guard: false } }) .permission?.dangerous_command_guard ).toBe(false); expect( getAgentSettings({ permission: { dangerous_command_guard: true } }) .permission?.dangerous_command_guard ).toBe(true); }); it("writes guard=false even with no rules (section kept)", () => { const next = setAgentSettings( {}, { permission: { rules: [], dangerous_command_guard: false } } ) as { permission?: Record }; expect(next.permission).toEqual({ dangerous_command_guard: false }); }); it("writes guard together with rules", () => { const rules = [{ decision: "allow" as const, pattern: "Read" }]; const next = setAgentSettings( {}, { permission: { rules, dangerous_command_guard: true } } ) as { permission?: Record }; expect(next.permission).toEqual({ rules, dangerous_command_guard: true, }); }); it("a rules-only update carries the existing guard through", () => { const raw = { permission: { dangerous_command_guard: false } }; const next = setAgentSettings(raw, { permission: { rules: [{ decision: "deny" as const, pattern: "Bash" }] }, }) as { permission?: Record }; expect(next.permission?.dangerous_command_guard).toBe(false); expect(next.permission?.rules).toHaveLength(1); }); it("omits an empty rules array; keeps an explicit guard on plain saves", () => { const raw = { permission: { dangerous_command_guard: true } }; const off = setAgentSettings(raw, { permission: { rules: [], dangerous_command_guard: false }, }) as { permission?: Record }; expect(off.permission).toEqual({ dangerous_command_guard: false }); expect(off.permission).not.toHaveProperty("rules"); // An explicit true is materialized on plain saves (harmless, and it // documents the state the UI toggle shows). const on = setAgentSettings(raw, {}) as { permission?: Record; }; expect(on.permission).toEqual({ dangerous_command_guard: true }); }); });