From 3f0bf322c1e2397cffaf22739df8d41e1957f5ba Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:18:42 +0000 Subject: [PATCH] fix(vscode): allow clearing agent model override by sending null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clearing the Model Override field for native agents (e.g. Explore, General) sent the patch as `{ model: undefined }`. JSON.stringify drops undefined, so the backend received an empty patch, the override stayed in kilo.json, and the next configUpdated push repopulated the field. Send `null` instead — the backend's patchJsonc already treats null as a delete sentinel (matches the pattern already used in ModelsTab and AutoApproveTab). Fixes #9527. --- .changeset/clear-agent-model-override.md | 5 ++ .../tests/unit/config-utils.test.ts | 49 +++++++++++++++++++ .../src/components/settings/ModeEditView.tsx | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 .changeset/clear-agent-model-override.md diff --git a/.changeset/clear-agent-model-override.md b/.changeset/clear-agent-model-override.md new file mode 100644 index 00000000000..ca28b26e94b --- /dev/null +++ b/.changeset/clear-agent-model-override.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Fix clearing an agent's Model Override in Agent Behaviour settings. Previously, clearing the field and saving would repopulate the old value because the empty input was sent as `undefined` and dropped by `JSON.stringify`, so the backend never received a delete instruction. The field now reverts to the global default model as expected. diff --git a/packages/kilo-vscode/tests/unit/config-utils.test.ts b/packages/kilo-vscode/tests/unit/config-utils.test.ts index c8974427c11..15ae52b443e 100644 --- a/packages/kilo-vscode/tests/unit/config-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/config-utils.test.ts @@ -155,4 +155,53 @@ describe("ConfigState", () => { expect(s.config.snapshot).toBe(true) expect(s.dirty).toBe(false) }) + + // ------------------------------------------------------------------------- + // Issue #9527: clearing an agent model override must unset it, not repopulate + // ------------------------------------------------------------------------- + describe("clearing an agent model override (issue #9527)", () => { + it("keeps null in the draft so the backend receives a delete sentinel", () => { + const s = new ConfigState() + s.handleConfigLoaded({ agent: { explore: { model: "anthropic/claude-sonnet-4-20250514" } } }) + + // User clears the Model Override field. ModeEditView now sends `null` + // instead of `undefined` (the fix). null is the delete sentinel that + // patchJsonc maps to jsonc-parser's remove operation. + s.updateConfig({ agent: { explore: { model: null } } }) + + // Optimistic UI: stripNulls removes the key so the field renders empty. + expect(s.config.agent?.explore?.model).toBeUndefined() + expect(s.dirty).toBe(true) + + // Draft must retain the null so it survives JSON.stringify on the wire + // and reaches patchJsonc as an explicit delete. + expect(s.draft.agent?.explore?.model).toBeNull() + expect(JSON.parse(JSON.stringify(s.draft))).toEqual({ + agent: { explore: { model: null } }, + }) + }) + + it("undefined (the old buggy behavior) is dropped by JSON.stringify", () => { + // Reproduction of the pre-fix bug: sending `undefined` results in an + // empty patch on the wire, so the backend never deletes the override + // and the next configUpdated pushes the stale model back into the UI. + const draft = { agent: { explore: { model: undefined } } } + expect(JSON.parse(JSON.stringify(draft))).toEqual({ agent: { explore: {} } }) + }) + + it("confirms the save and drops the draft once the backend acks", () => { + const s = new ConfigState() + s.handleConfigLoaded({ agent: { explore: { model: "anthropic/claude-sonnet-4-20250514" } } }) + s.updateConfig({ agent: { explore: { model: null } } }) + s.saveConfig() + + // Backend removed the override and pushes the stripped config back. + s.handleConfigUpdated({ agent: { explore: {} } }) + + expect(s.config.agent?.explore?.model).toBeUndefined() + expect(s.dirty).toBe(false) + expect(s.saving).toBe(false) + expect(Object.keys(s.draft).length).toBe(0) + }) + }) }) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx index c8570207367..2c436efd8ef 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx @@ -145,7 +145,7 @@ const ModeEditView: Component = (props) => { update({ model: val || undefined })} + onChange={(val) => update({ model: val || null })} />