From ec5ac2e19f69052dae375c9ddc2f7edb67c62358 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 14:56:40 +0300 Subject: [PATCH 1/6] 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 00000000000..b3a6be4d396 --- /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 2048e2b5e39..46d61385c2b 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 c095328ea69..67edc29e642 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 9911fece1f0..ff518b14d21 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 32f69e5d0e9..b3098ac3bd7 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 779c7c6b0b3..342ab5dc6b3 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) => { From 64e2f096c4364ee55a5c1fb7856b5147f557353a Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 17:06:04 +0300 Subject: [PATCH 2/6] fix(cli): make plan follow-up "Continue here" continue the loop The prompt queue's scope() hid the user message injected by PlanFollowup.inject() because its ID was newer than the queue target, so the loop saw the same plan_exit messages and re-asked the question in an infinite cycle. Retarget the queue after inject so the new message is visible on the next iteration. --- .../opencode/src/kilocode/plan-followup.ts | 8 +++-- .../src/kilocode/session/prompt-queue.ts | 9 +++++ .../test/kilocode/plan-followup.test.ts | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts index b3098ac3bd7..5dc903a2a57 100644 --- a/packages/opencode/src/kilocode/plan-followup.ts +++ b/packages/opencode/src/kilocode/plan-followup.ts @@ -16,6 +16,7 @@ import { MessageV2 } from "@/session/message-v2" import { Todo } from "@/session/todo" import { makeRuntime } from "@/effect/run-service" import { Log } from "@/util/log" +import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" import path from "path" import z from "zod" @@ -255,6 +256,7 @@ export namespace PlanFollowup { text: input.text, synthetic: input.synthetic ?? true, } satisfies MessageV2.TextPart) + return msg } function prompt(input: { sessionID: SessionID; abort: AbortSignal }) { @@ -402,22 +404,24 @@ export namespace PlanFollowup { const code = await resolveCodeModel({ model: user.model, }) - await inject({ + const msg = await inject({ sessionID: input.sessionID, agent: "code", model: code.model, text: "Implement the plan above.", }) + KiloSessionPromptQueue.retarget(input.sessionID, msg.id) return "continue" } Telemetry.trackPlanFollowup(input.sessionID, "custom") - await inject({ + const msg = await inject({ sessionID: input.sessionID, agent: "plan", model: user.model, text: answer, }) + KiloSessionPromptQueue.retarget(input.sessionID, msg.id) return "continue" } } diff --git a/packages/opencode/src/kilocode/session/prompt-queue.ts b/packages/opencode/src/kilocode/session/prompt-queue.ts index ab08c24346d..b0c70207301 100644 --- a/packages/opencode/src/kilocode/session/prompt-queue.ts +++ b/packages/opencode/src/kilocode/session/prompt-queue.ts @@ -27,6 +27,15 @@ export namespace KiloSessionPromptQueue { }) } + /** + * Advance the visible-message boundary for a running loop. + * Called after PlanFollowup.inject() so the injected user message is not + * hidden by scope(). + */ + export function retarget(sessionID: SessionID, id: MessageID) { + targets.set(sessionID, id) + } + export function scope(sessionID: SessionID, messages: MessageV2.WithParts[]) { const target = targets.get(sessionID) if (!target) return messages diff --git a/packages/opencode/test/kilocode/plan-followup.test.ts b/packages/opencode/test/kilocode/plan-followup.test.ts index 342ab5dc6b3..b3e1fa11d0b 100644 --- a/packages/opencode/test/kilocode/plan-followup.test.ts +++ b/packages/opencode/test/kilocode/plan-followup.test.ts @@ -379,6 +379,40 @@ describe("plan follow-up", () => { expect(part.synthetic).toBe(true) })) + test("ask - retargets prompt queue so injected message is visible in scope", () => + withInstance(async () => { + const { KiloSessionPromptQueue } = await import("../../src/kilocode/session/prompt-queue") + const seeded = await seed({ text: "1. Refactor\n2. Ship" }) + + // Simulate the prompt queue having a target set (like during a running loop) + const original = seeded.messages.find((m) => m.info.role === "user")!.info.id + KiloSessionPromptQueue.retarget(seeded.sessionID, original) + + 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 + await question.reply({ + requestID: item.id, + answers: [[PlanFollowup.ANSWER_CONTINUE]], + }) + + await expect(pending).resolves.toBe("continue") + + // The injected user message must be visible when scoped + const all = await Session.messages({ sessionID: seeded.sessionID }) + const scoped = KiloSessionPromptQueue.scope(seeded.sessionID, all) + const injected = scoped.findLast((m) => m.info.role === "user") + expect(injected).toBeDefined() + const part = injected!.parts.find((p) => p.type === "text") + expect(part?.type === "text" && part.text).toBe("Implement the plan above.") + })) + test("ask - creates a new session on Start new session with handover and todos", () => withInstance(async () => { const get = spyOn(PlanFollowupRuntime, "agent").mockImplementation(async (name: string) => { From b25843962db7f4f5a6e8f4537615d981b650f86a Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 17:15:07 +0300 Subject: [PATCH 3/6] fix(cli): restore "Type your own answer" on plan follow-up in TUI When the plan agent pauses for the follow-up question, CLI users had no way to send a free-text reply: the main prompt input is hidden while a blocking question is active, and the "Type your own answer" row was being forced off to avoid duplicating the VS Code prompt input. CLI now shows the custom-answer row again, while VS Code keeps it hidden. --- .../opencode/src/kilocode/plan-followup.ts | 11 +++--- .../test/kilocode/plan-followup.test.ts | 39 ++++++++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts index 5dc903a2a57..7cc1fc60834 100644 --- a/packages/opencode/src/kilocode/plan-followup.ts +++ b/packages/opencode/src/kilocode/plan-followup.ts @@ -266,11 +266,12 @@ export namespace PlanFollowup { { question: "Ready to implement?", header: "Implement", - // 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, + // On CLI the main prompt input is hidden while a blocking question is active, + // so we need the custom-answer row to allow a free-text reply. On VS Code the + // main prompt input below the dock already routes typed text as a question + // reply, so "Type your own answer" would be redundant (originally hidden in + // 65566af7f8, flipped back during the v1.4.4 upstream merge). + custom: Flag.KILO_CLIENT === "cli", 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 b3e1fa11d0b..27ff3834353 100644 --- a/packages/opencode/test/kilocode/plan-followup.test.ts +++ b/packages/opencode/test/kilocode/plan-followup.test.ts @@ -266,7 +266,7 @@ describe("plan follow-up", () => { await expect(pending).resolves.toBe("break") })) - test("ask - emits a non-custom single-select question with the canonical answers", () => + test("ask - emits a single-select question with the canonical answers and custom enabled on CLI", () => withInstance(async () => { const seeded = await seed({ text: "1. Build" }) const pending = PlanFollowup.ask({ @@ -282,10 +282,9 @@ describe("plan follow-up", () => { 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) + // On CLI the main prompt input is hidden while a blocking question is active, so + // "Type your own answer" must remain available — i.e. custom must not be false. + expect(q.custom).not.toBe(false) expect(q.multiple).not.toBe(true) expect(q.options.map((item) => item.label)).toEqual([ PlanFollowup.ANSWER_NEW_SESSION, @@ -296,6 +295,36 @@ describe("plan follow-up", () => { await expect(pending).resolves.toBe("break") })) + test("ask - hides custom answer row on VS Code where the main prompt input handles typed replies", () => + withInstance(async () => { + const prev = process.env.KILO_CLIENT + try { + process.env.KILO_CLIENT = "vscode" + 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 + + // On VS Code the dock's main prompt input already accepts free text as a reply, + // so the "Type your own answer" row is redundant and must be hidden. + expect(q.custom).toBe(false) + + await question.reject(item.id) + await expect(pending).resolves.toBe("break") + } finally { + process.env.KILO_CLIENT = prev + } + })) + test("ask - returns continue and creates code message on Continue here", () => withInstance(async () => { const get = spyOn(PlanFollowupRuntime, "agent").mockImplementation(async (name: string) => { From 3b9b46d9da621fb28e5c2f9b4d2df90826b9ef78 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 17:23:55 +0300 Subject: [PATCH 4/6] fix(vscode): keep custom single-question on tab --- .../webview-ui/src/components/chat/question-dock-utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ff518b14d21..62395ef0099 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 @@ -12,7 +12,8 @@ export type PickOutcome = { kind: "submit" } | { kind: "advance" } | { kind: "st */ 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" } + if (input.single && input.custom) return { kind: "stay" } + if (input.single) return { kind: "submit" } return { kind: "advance" } } From df44d3847d105aba79b487015a9c2077e32256ae Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 17:26:54 +0300 Subject: [PATCH 5/6] fix(cli): keep older queued prompts hidden on retarget --- .../src/kilocode/session/prompt-queue.ts | 41 ++++++++++++------- .../kilocode/session-prompt-queue.test.ts | 33 +++++++++++++++ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/kilocode/session/prompt-queue.ts b/packages/opencode/src/kilocode/session/prompt-queue.ts index b0c70207301..fa520935b63 100644 --- a/packages/opencode/src/kilocode/session/prompt-queue.ts +++ b/packages/opencode/src/kilocode/session/prompt-queue.ts @@ -9,10 +9,15 @@ type Slot = { readonly tail: Promise } +type Target = { + readonly base: MessageID + readonly extras: ReadonlySet +} + export namespace KiloSessionPromptQueue { const tails = new Map>() const versions = new Map() - const targets = new Map() + const targets = new Map() const version = (sessionID: SessionID) => versions.get(sessionID) ?? 0 const settle = (promise: Promise) => @@ -28,12 +33,16 @@ export namespace KiloSessionPromptQueue { } /** - * Advance the visible-message boundary for a running loop. - * Called after PlanFollowup.inject() so the injected user message is not - * hidden by scope(). + * Exempt an injected user message from being hidden by scope(). + * Called after PlanFollowup.inject() so the injected follow-up is visible + * without also unhiding unrelated prompts that were queued mid-turn. */ export function retarget(sessionID: SessionID, id: MessageID) { - targets.set(sessionID, id) + const current = targets.get(sessionID) + if (!current) return + const extras = new Set(current.extras) + extras.add(id) + targets.set(sessionID, { base: current.base, extras }) } export function scope(sessionID: SessionID, messages: MessageV2.WithParts[]) { @@ -41,10 +50,12 @@ export namespace KiloSessionPromptQueue { if (!target) return messages const hidden = new Set( - messages.filter((item) => item.info.role === "user" && item.info.id > target).map((item) => item.info.id), + messages + .filter((item) => item.info.role === "user" && item.info.id > target.base && !target.extras.has(item.info.id)) + .map((item) => item.info.id), ) const visible = messages.filter((item) => { - if (item.info.role === "user") return item.info.id <= target + if (item.info.role === "user") return !hidden.has(item.info.id) if (item.info.role === "assistant") return !hidden.has(item.info.parentID) return true }) @@ -54,12 +65,14 @@ export namespace KiloSessionPromptQueue { // was written after the queue event). Ordering by time_created alone puts // the queued prompt before the prior turn's final assistant reply, which // makes the next request end with an assistant message and trips Anthropic's - // prefill rejection. Move the target user message and any of its own turn's - // assistant messages to the end so the request always ends with the queued - // user prompt (or with its own turn's latest assistant step). + // prefill rejection. Move the target user message (and any injected + // follow-ups) plus their own turn's assistant messages to the end so the + // request always ends with the queued user prompt (or its latest assistant + // step). + const ownsID = (id: MessageID) => id === target.base || target.extras.has(id) const owns = (item: MessageV2.WithParts) => { - if (item.info.role === "user") return item.info.id === target - if (item.info.role === "assistant") return item.info.parentID === target + if (item.info.role === "user") return ownsID(item.info.id) + if (item.info.role === "assistant") return ownsID(item.info.parentID) return false } const before: MessageV2.WithParts[] = [] @@ -90,12 +103,12 @@ export namespace KiloSessionPromptQueue { if (slot.version !== version(sessionID)) return cancelled return Effect.acquireUseRelease( Effect.sync(() => { - targets.set(sessionID, target) + targets.set(sessionID, { base: target, extras: new Set() }) }), () => work, () => Effect.sync(() => { - if (targets.get(sessionID) === target) targets.delete(sessionID) + if (targets.get(sessionID)?.base === target) targets.delete(sessionID) }), ) }), diff --git a/packages/opencode/test/kilocode/session-prompt-queue.test.ts b/packages/opencode/test/kilocode/session-prompt-queue.test.ts index 5d5e93ac1d0..ee35913122c 100644 --- a/packages/opencode/test/kilocode/session-prompt-queue.test.ts +++ b/packages/opencode/test/kilocode/session-prompt-queue.test.ts @@ -188,6 +188,39 @@ describe("session prompt queue", () => { expect(ids).toEqual([m1, a1, a1tail, m2, a2step1]) }) + test("retarget keeps older queued prompts hidden", async () => { + // Regression: retargeting used to move the visible-message boundary forward, + // which unhid any user prompts queued between the base and the injected + // follow-up. Exempt the follow-up without reopening the boundary. + const sessionID = SessionID.make("session_retarget_hide") + const base = MessageID.make("message_b1") + const ans = MessageID.make("message_b2") + const queued = MessageID.make("message_b3") // queued while base was running + const injected = MessageID.make("message_b4") // injected follow-up + const messages = [ + user(sessionID, base), + assistant(sessionID, ans, base), + user(sessionID, queued), + user(sessionID, injected), + ] + + const ids = await Effect.runPromise( + KiloSessionPromptQueue.enqueue( + sessionID, + base, + Effect.sync(() => { + KiloSessionPromptQueue.retarget(sessionID, injected) + return KiloSessionPromptQueue.scope(sessionID, messages).map((item) => item.info.id) + }), + Effect.succeed([]), + ), + ) + + expect(ids).not.toContain(queued) + expect(ids).toContain(injected) + expect(ids[ids.length - 1]).toBe(injected) + }) + test("continues a queued prompt after the active run finishes", async () => { const ready = Promise.withResolvers() const release = Promise.withResolvers() From bc05d36420f962468db40a0d56dec038d106e5d4 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Mon, 20 Apr 2026 17:38:50 +0300 Subject: [PATCH 6/6] test(vscode): update pickOutcome expectation for single+custom to stay --- packages/kilo-vscode/tests/unit/question-dock-utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 46d61385c2b..e2300c20031 100644 --- a/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/question-dock-utils.test.ts @@ -144,7 +144,7 @@ describe("pickOutcome", () => { }) 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" }) + expect(pickOutcome({ single: true, multi: false, custom: true })).toEqual({ kind: "stay" }) }) it("stays on the current tab for a multi-select custom-input pick", () => {