From ec5ac2e19f69052dae375c9ddc2f7edb67c62358 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 14:56:40 +0300 Subject: [PATCH] fix(vscode,cli): make plan_exit "Continue here" work again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore custom: false on the plan follow-up question — the "Type your own answer" row was redundant because the main prompt already routes typed text as a question reply. Auto-submit single-question single-select option picks in the VS Code QuestionDock so the button behaves like the TUI instead of silently waiting for a second Submit click. --- .changeset/plan-followup-continue-click.md | 5 ++++ .../tests/unit/question-dock-utils.test.ts | 23 ++++++++++++++ .../src/components/chat/QuestionDock.tsx | 16 ++++++++-- .../components/chat/question-dock-utils.ts | 16 ++++++++++ .../opencode/src/kilocode/plan-followup.ts | 6 +++- .../test/kilocode/plan-followup.test.ts | 30 +++++++++++++++++++ 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 .changeset/plan-followup-continue-click.md diff --git a/.changeset/plan-followup-continue-click.md b/.changeset/plan-followup-continue-click.md new file mode 100644 index 0000000000..b3a6be4d39 --- /dev/null +++ b/.changeset/plan-followup-continue-click.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Fix the "Continue here" button not submitting after a plan is finished. Picking an option on a single-question prompt now sends the reply immediately — matching the CLI behaviour — and the redundant "Type your own answer" row no longer appears on the plan follow-up question. diff --git a/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts b/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts index 2048e2b5e3..46d61385c2 100644 --- a/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "bun:test" import { + pickOutcome, resolveOptimisticQuestionAgent, resolveQuestionMode, resolveSelectedQuestionMode, @@ -128,3 +129,25 @@ describe("resolveOptimisticQuestionAgent", () => { expect(result).toEqual({ base: "ask", agent: "architect" }) }) }) + +describe("pickOutcome", () => { + it("submits immediately on a single-question single-select option pick", () => { + expect(pickOutcome({ single: true, multi: false, custom: false })).toEqual({ kind: "submit" }) + }) + + it("advances to the next tab on a multi-question single-select option pick", () => { + expect(pickOutcome({ single: false, multi: false, custom: false })).toEqual({ kind: "advance" }) + }) + + it("stays on the current tab for a multi-select pick", () => { + expect(pickOutcome({ single: true, multi: true, custom: false })).toEqual({ kind: "stay" }) + }) + + it("defers submission for a single-select custom-input pick (handleCustomSubmit owns the submit)", () => { + expect(pickOutcome({ single: true, multi: false, custom: true })).toEqual({ kind: "advance" }) + }) + + it("stays on the current tab for a multi-select custom-input pick", () => { + expect(pickOutcome({ single: false, multi: true, custom: true })).toEqual({ kind: "stay" }) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx index c095328ea6..67edc29e64 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx @@ -12,7 +12,12 @@ import { Icon } from "@kilocode/kilo-ui/icon" import { useSession } from "../../context/session" import { useLanguage } from "../../context/language" import type { QuestionRequest } from "../../types/messages" -import { resolveOptimisticQuestionAgent, resolveSelectedQuestionMode, toggleAnswer } from "./question-dock-utils" +import { + pickOutcome, + resolveOptimisticQuestionAgent, + resolveSelectedQuestionMode, + toggleAnswer, +} from "./question-dock-utils" export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => { const session = useSession() @@ -119,7 +124,14 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => syncAgent(answers, kinds) - if (!single() && !multi()) { + const outcome = pickOutcome({ single: single(), multi: multi(), custom }) + if (outcome.kind === "submit") { + // Mirror TUI behaviour: a single-question single-select option pick submits immediately. + // handleCustomSubmit covers the custom-input path via its own submit() call. + reply([[answer]]) + return + } + if (outcome.kind === "advance") { setStore("tab", store.tab + 1) } } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/question-dock-utils.ts b/packages/kilo-vscode/webview-ui/src/components/chat/question-dock-utils.ts index 9911fece1f..ff518b14d2 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/question-dock-utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/chat/question-dock-utils.ts @@ -1,5 +1,21 @@ import type { QuestionOption } from "../../types/messages" +export type PickOutcome = { kind: "submit" } | { kind: "advance" } | { kind: "stay" } + +/** + * Decide what should happen after a user picks an option in the question dock. + * + * - Multi-select prompts: the pick only toggles local state; no tab change, no submit. + * - Single-question single-select, option pick: submit immediately (matches the TUI). + * - Multi-question single-select, option pick: advance to the next tab. + * - Custom-input path for a single-select is handled separately in handleCustomSubmit. + */ +export function pickOutcome(input: { single: boolean; multi: boolean; custom: boolean }): PickOutcome { + if (input.multi) return { kind: "stay" } + if (input.single && !input.custom) return { kind: "submit" } + return { kind: "advance" } +} + export function toggleAnswer(existing: string[], answer: string): string[] { const next = [...existing] const index = next.indexOf(answer) diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts index 32f69e5d0e..b3098ac3bd 100644 --- a/packages/opencode/src/kilocode/plan-followup.ts +++ b/packages/opencode/src/kilocode/plan-followup.ts @@ -264,7 +264,11 @@ export namespace PlanFollowup { { question: "Ready to implement?", header: "Implement", - custom: true, + // Keep false: the main prompt input already routes typed text as a question reply, + // so "Type your own answer" would be redundant. This was set to false intentionally + // in 65566af7f8 and got flipped back to true during the v1.4.4 upstream merge — + // do not change without updating that history. + custom: false, options: [ { label: ANSWER_NEW_SESSION, diff --git a/packages/opencode/test/kilocode/plan-followup.test.ts b/packages/opencode/test/kilocode/plan-followup.test.ts index 779c7c6b0b..342ab5dc6b 100644 --- a/packages/opencode/test/kilocode/plan-followup.test.ts +++ b/packages/opencode/test/kilocode/plan-followup.test.ts @@ -266,6 +266,36 @@ describe("plan follow-up", () => { await expect(pending).resolves.toBe("break") })) + test("ask - emits a non-custom single-select question with the canonical answers", () => + withInstance(async () => { + const seeded = await seed({ text: "1. Build" }) + const pending = PlanFollowup.ask({ + sessionID: seeded.sessionID, + messages: seeded.messages, + abort: AbortSignal.any([]), + }) + + const item = await waitQuestion(seeded.sessionID) + expect(item).toBeDefined() + if (!item) return + const q = item.questions[0] + expect(q).toBeDefined() + if (!q) return + + // custom must stay false — "Type your own answer" is redundant because the main prompt + // input already routes typed text as a question reply. Regressed once during the v1.4.4 + // upstream merge, so pin it here. + expect(q.custom).toBe(false) + expect(q.multiple).not.toBe(true) + expect(q.options.map((item) => item.label)).toEqual([ + PlanFollowup.ANSWER_NEW_SESSION, + PlanFollowup.ANSWER_CONTINUE, + ]) + + await question.reject(item.id) + await expect(pending).resolves.toBe("break") + })) + test("ask - returns continue and creates code message on Continue here", () => withInstance(async () => { const get = spyOn(PlanFollowupRuntime, "agent").mockImplementation(async (name: string) => {