From b98a1bcfcfb0b32f8787f6899d88edbd691648c0 Mon Sep 17 00:00:00 2001 From: Vu Anh Nguyen Date: Thu, 26 Mar 2026 17:20:31 +0700 Subject: [PATCH] fix(ask): preserve prior answer when custom editor dismissed in single-select --- packages/coding-agent/src/tools/ask.ts | 8 +-- packages/coding-agent/test/tools/ask.test.ts | 62 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/packages/coding-agent/src/tools/ask.ts b/packages/coding-agent/src/tools/ask.ts index bc8b2f127..13d4bb9eb 100644 --- a/packages/coding-agent/src/tools/ask.ts +++ b/packages/coding-agent/src/tools/ask.ts @@ -331,12 +331,12 @@ async function askSingleQuestion( } else if (choice === OTHER_OPTION) { if (!selectTimedOut) { const customResult = await promptForCustomInput(); - if (customResult.input === undefined) { - return { selectedOptions, customInput, timedOut, cancelled: true }; + if (customResult.input !== undefined) { + customInput = customResult.input; + selectedOptions = []; } - customInput = customResult.input; + // If editor was dismissed (undefined), keep prior selectedOptions/customInput intact } - selectedOptions = []; } else { selectedOptions = [stripRecommendedSuffix(choice)]; customInput = undefined; diff --git a/packages/coding-agent/test/tools/ask.test.ts b/packages/coding-agent/test/tools/ask.test.ts index 629574f97..d1b681f85 100644 --- a/packages/coding-agent/test/tools/ask.test.ts +++ b/packages/coding-agent/test/tools/ask.test.ts @@ -411,7 +411,7 @@ describe("AskTool custom input", () => { expect(abort).toHaveBeenCalledTimes(1); }); - it("aborts when editor is cancelled after choosing Other in multi-question flow", async () => { + it("continues multi-question flow when editor is dismissed on a fresh question", async () => { const tool = new AskTool(createSession()); const abort = vi.fn(); const editor = vi.fn(async () => undefined); @@ -437,11 +437,14 @@ describe("AskTool custom input", () => { abort, }); - await expect( - tool.execute("call-editor-multi-cancel", { questions }, undefined, undefined, context), - ).rejects.toBeInstanceOf(ToolAbortError); + const result = await tool.execute("call-editor-multi-dismiss", { questions }, undefined, undefined, context); + + // Editor dismissed on "Details?" — flow continues with empty answer, not abort + expect(result.details?.results?.[0]?.selectedOptions).toEqual(["one"]); + expect(result.details?.results?.[1]?.selectedOptions).toEqual([]); + expect(result.details?.results?.[1]?.customInput).toBeUndefined(); expect(editor).toHaveBeenCalledTimes(1); - expect(abort).toHaveBeenCalledTimes(1); + expect(abort).not.toHaveBeenCalled(); }); it("surfaces external abort during editor mode as ToolAbortError", async () => { @@ -895,4 +898,53 @@ describe("AskTool multi-question navigation", () => { expect(result.details?.results?.[1]?.selectedOptions).toEqual(["two"]); expect(editor).toHaveBeenCalledTimes(1); }); + + it("preserves prior single-select answer when custom editor is dismissed during navigation", async () => { + const tool = new AskTool(createSession()); + let detailVisits = 0; + const editor = vi.fn(async () => undefined); + const questions = [ + { + id: "details", + question: "Details?", + options: [{ label: "short" }, { label: "long" }], + }, + { + id: "summary", + question: "Summary?", + options: [{ label: "one" }, { label: "two" }], + }, + ]; + const context = createContext({ + select: async (prompt, _options, dialogOptions) => { + if (prompt.includes("Details?")) { + detailVisits += 1; + if (detailVisits === 1) return "short"; + // Second visit: try Other then dismiss editor, then forward + if (detailVisits === 2) return "Other (type your own)"; + dialogOptions?.onRight?.(); + return undefined; + } + if (prompt.includes("Summary?")) { + const summaryVisit = detailVisits; + if (summaryVisit <= 2) { + // Navigate back to re-visit details + dialogOptions?.onLeft?.(); + return undefined; + } + return "two"; + } + return undefined; + }, + editor, + }); + + const result = await tool.execute("call-nav-single-dismiss", { questions }, undefined, undefined, context); + + // The prior selection "short" should survive the editor dismiss + expect(result.details?.results?.[0]?.selectedOptions).toEqual(["short"]); + expect(result.details?.results?.[0]?.customInput).toBeUndefined(); + expect(result.details?.results?.[1]?.selectedOptions).toEqual(["two"]); + expect(editor).toHaveBeenCalledTimes(1); + }); });