@@ -266,6 +266,65 @@ describe("plan follow-up", () => {
266266 await expect ( pending ) . resolves . toBe ( "break" )
267267 } ) )
268268
269+ test ( "ask - emits a single-select question with the canonical answers and custom enabled on CLI" , ( ) =>
270+ withInstance ( async ( ) => {
271+ const seeded = await seed ( { text : "1. Build" } )
272+ const pending = PlanFollowup . ask ( {
273+ sessionID : seeded . sessionID ,
274+ messages : seeded . messages ,
275+ abort : AbortSignal . any ( [ ] ) ,
276+ } )
277+
278+ const item = await waitQuestion ( seeded . sessionID )
279+ expect ( item ) . toBeDefined ( )
280+ if ( ! item ) return
281+ const q = item . questions [ 0 ]
282+ expect ( q ) . toBeDefined ( )
283+ if ( ! q ) return
284+
285+ // On CLI the main prompt input is hidden while a blocking question is active, so
286+ // "Type your own answer" must remain available — i.e. custom must not be false.
287+ expect ( q . custom ) . not . toBe ( false )
288+ expect ( q . multiple ) . not . toBe ( true )
289+ expect ( q . options . map ( ( item ) => item . label ) ) . toEqual ( [
290+ PlanFollowup . ANSWER_NEW_SESSION ,
291+ PlanFollowup . ANSWER_CONTINUE ,
292+ ] )
293+
294+ await question . reject ( item . id )
295+ await expect ( pending ) . resolves . toBe ( "break" )
296+ } ) )
297+
298+ test ( "ask - hides custom answer row on VS Code where the main prompt input handles typed replies" , ( ) =>
299+ withInstance ( async ( ) => {
300+ const prev = process . env . KILO_CLIENT
301+ try {
302+ process . env . KILO_CLIENT = "vscode"
303+ const seeded = await seed ( { text : "1. Build" } )
304+ const pending = PlanFollowup . ask ( {
305+ sessionID : seeded . sessionID ,
306+ messages : seeded . messages ,
307+ abort : AbortSignal . any ( [ ] ) ,
308+ } )
309+
310+ const item = await waitQuestion ( seeded . sessionID )
311+ expect ( item ) . toBeDefined ( )
312+ if ( ! item ) return
313+ const q = item . questions [ 0 ]
314+ expect ( q ) . toBeDefined ( )
315+ if ( ! q ) return
316+
317+ // On VS Code the dock's main prompt input already accepts free text as a reply,
318+ // so the "Type your own answer" row is redundant and must be hidden.
319+ expect ( q . custom ) . toBe ( false )
320+
321+ await question . reject ( item . id )
322+ await expect ( pending ) . resolves . toBe ( "break" )
323+ } finally {
324+ process . env . KILO_CLIENT = prev
325+ }
326+ } ) )
327+
269328 test ( "ask - returns continue and creates code message on Continue here" , ( ) =>
270329 withInstance ( async ( ) => {
271330 const get = spyOn ( PlanFollowupRuntime , "agent" ) . mockImplementation ( async ( name : string ) => {
@@ -349,6 +408,40 @@ describe("plan follow-up", () => {
349408 expect ( part . synthetic ) . toBe ( true )
350409 } ) )
351410
411+ test ( "ask - retargets prompt queue so injected message is visible in scope" , ( ) =>
412+ withInstance ( async ( ) => {
413+ const { KiloSessionPromptQueue } = await import ( "../../src/kilocode/session/prompt-queue" )
414+ const seeded = await seed ( { text : "1. Refactor\n2. Ship" } )
415+
416+ // Simulate the prompt queue having a target set (like during a running loop)
417+ const original = seeded . messages . find ( ( m ) => m . info . role === "user" ) ! . info . id
418+ KiloSessionPromptQueue . retarget ( seeded . sessionID , original )
419+
420+ const pending = PlanFollowup . ask ( {
421+ sessionID : seeded . sessionID ,
422+ messages : seeded . messages ,
423+ abort : AbortSignal . any ( [ ] ) ,
424+ } )
425+
426+ const item = await waitQuestion ( seeded . sessionID )
427+ expect ( item ) . toBeDefined ( )
428+ if ( ! item ) return
429+ await question . reply ( {
430+ requestID : item . id ,
431+ answers : [ [ PlanFollowup . ANSWER_CONTINUE ] ] ,
432+ } )
433+
434+ await expect ( pending ) . resolves . toBe ( "continue" )
435+
436+ // The injected user message must be visible when scoped
437+ const all = await Session . messages ( { sessionID : seeded . sessionID } )
438+ const scoped = KiloSessionPromptQueue . scope ( seeded . sessionID , all )
439+ const injected = scoped . findLast ( ( m ) => m . info . role === "user" )
440+ expect ( injected ) . toBeDefined ( )
441+ const part = injected ! . parts . find ( ( p ) => p . type === "text" )
442+ expect ( part ?. type === "text" && part . text ) . toBe ( "Implement the plan above." )
443+ } ) )
444+
352445 test ( "ask - creates a new session on Start new session with handover and todos" , ( ) =>
353446 withInstance ( async ( ) => {
354447 const get = spyOn ( PlanFollowupRuntime , "agent" ) . mockImplementation ( async ( name : string ) => {
0 commit comments