fix(vscode,cli): make plan_exit "Continue here" work again
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.
This commit is contained in:
@@ -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.
|
||||
@@ -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" })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user