@@ -289,39 +289,16 @@ describe("TaskContinuation", () => {
289289 // Model is tracked as string in event but sessionContext stores ModelSpec, so string models become undefined
290290 } )
291291
292- it ( "should proceed without agent/model when no context is available" , async ( ) => {
293- const ctx = createMockContext ( )
294- const mockTodoFn = ctx . client . session . todo as unknown as {
295- mockResolvedValue : ( val : Todo [ ] ) => void
296- }
297- mockTodoFn . mockResolvedValue ( createMockTodos ( 0 , 1 ) )
298-
299- const taskContinuation = createTaskContinuation ( ctx )
300-
301- // Trigger a session idle event without a prior user message
302- // No context in sessionContext, so prompt proceeds without agent/model
303- await taskContinuation . handler ( { event : createIdleEvent ( "session-123" ) } )
304-
305- // The prompt should be called after the countdown
306- await vi . advanceTimersByTimeAsync ( 3000 )
307- expect ( ctx . client . session . prompt ) . toHaveBeenCalled ( )
308-
309- // When no context is available, agent/model are undefined
310- const promptCall = ( ctx . client . session . prompt as any ) . mock . calls [ 0 ] [ 0 ] as PromptCall
311- expect ( promptCall . body . agent ) . toBeUndefined ( )
312- expect ( promptCall . body . model ) . toBeUndefined ( )
313- } )
314-
315- // ===========================================================================
316- // Regression Tests: Message Filtering for Countdown Cancellation
317- // These tests prevent the bug where message updates incorrectly cancelled countdowns
318- // Bug: OpenCode sends multiple message.updated events for the same message
319- // (initial creation, summary updates, metadata changes). The plugin was treating
320- // ALL of these as new user input and cancelling the countdown.
321- // Fix: Filter messages by role, summary presence, and message ID tracking.
322- // ===========================================================================
323-
324- it ( "should NOT cancel countdown when message has summary (message update)" , async ( ) => {
292+ // ===========================================================================
293+ // Regression Tests: Message Filtering for Countdown Cancellation
294+ // These tests prevent the bug where message updates incorrectly cancelled countdowns
295+ // Bug: OpenCode sends multiple message.updated events for the same message
296+ // (initial creation, summary updates, metadata changes). The plugin was treating
297+ // ALL of these as new user input and cancelling the countdown.
298+ // Fix: Filter messages by role, summary presence, and message ID tracking.
299+ // ===========================================================================
300+
301+ it ( "should NOT cancel countdown when message has summary (message update)" , async ( ) => {
325302 const ctx = createMockContext ( )
326303 const mockTodoFn = ctx . client . session . todo as unknown as {
327304 mockResolvedValue : ( val : Todo [ ] ) => void
@@ -397,52 +374,7 @@ describe("TaskContinuation", () => {
397374 expect ( ctx . client . session . prompt ) . not . toHaveBeenCalled ( )
398375 } )
399376
400- it ( "should NOT cancel countdown for repeated message events (same ID)" , async ( ) => {
401- const ctx = createMockContext ( )
402- const mockTodoFn = ctx . client . session . todo as unknown as {
403- mockResolvedValue : ( val : Todo [ ] ) => void
404- }
405- mockTodoFn . mockResolvedValue ( createMockTodos ( 0 , 1 ) )
406-
407- const taskContinuation = createTaskContinuation ( ctx , { countdownSeconds : 2 } )
408-
409- // First, send a user message to establish the message ID tracking
410- const initialMessageEvent : LoopEvent = {
411- type : "message.updated" ,
412- properties : {
413- sessionID : "session-123" ,
414- info : { id : "msg-1" , sessionID : "session-123" , role : "user" } as any ,
415- } ,
416- }
417- await taskContinuation . handler ( { event : initialMessageEvent } )
418-
419- // Now trigger idle - countdown should be scheduled
420- await taskContinuation . handler ( { event : createIdleEvent ( "session-123" ) } )
421- expect ( ctx . client . tui . showToast ) . toHaveBeenCalled ( )
422-
423- // Advance time but not enough to fire countdown
424- await vi . advanceTimersByTimeAsync ( 500 )
425-
426- // Now send the SAME message event again (same ID) - should NOT cancel
427- const repeatedMessageEvent : LoopEvent = {
428- type : "message.updated" ,
429- properties : {
430- sessionID : "session-123" ,
431- info : { id : "msg-1" , sessionID : "session-123" , role : "user" } as any ,
432- } ,
433- }
434- await taskContinuation . handler ( { event : repeatedMessageEvent } )
435- await taskContinuation . handler ( { event : repeatedMessageEvent } )
436- await taskContinuation . handler ( { event : repeatedMessageEvent } )
437-
438- // Advance time past the countdown
439- await vi . advanceTimersByTimeAsync ( 3000 )
440-
441- // Countdown should have fired (repeated message ID shouldn't cancel)
442- expect ( ctx . client . session . prompt ) . toHaveBeenCalled ( )
443- } )
444-
445- it ( "should NOT cancel countdown for assistant messages" , async ( ) => {
377+ it ( "should NOT cancel countdown for assistant messages" , async ( ) => {
446378 const ctx = createMockContext ( )
447379 const mockTodoFn = ctx . client . session . todo as unknown as {
448380 mockResolvedValue : ( val : Todo [ ] ) => void
@@ -475,50 +407,7 @@ describe("TaskContinuation", () => {
475407 expect ( ctx . client . session . prompt ) . toHaveBeenCalled ( )
476408 } )
477409
478- it ( "should complete full flow: idle → countdown → message update (no cancel) → continuation" , async ( ) => {
479- const ctx = createMockContext ( )
480- const mockTodoFn = ctx . client . session . todo as unknown as {
481- mockResolvedValue : ( val : Todo [ ] ) => void
482- }
483- mockTodoFn . mockResolvedValue ( createMockTodos ( 1 , 2 ) )
484-
485- const taskContinuation = createTaskContinuation ( ctx , { countdownSeconds : 2 } )
486-
487- // 1. Session goes idle with incomplete todos
488- await taskContinuation . handler ( { event : createIdleEvent ( "session-456" ) } )
489- expect ( ctx . client . tui . showToast ) . toHaveBeenCalled ( )
490-
491- // 2. Advance time to just before countdown fires
492- await vi . advanceTimersByTimeAsync ( 1500 )
493-
494- // 3. Message gets updated with summary (simulating OpenCode behavior)
495- const messageUpdateEvent : LoopEvent = {
496- type : "message.updated" ,
497- properties : {
498- sessionID : "session-456" ,
499- info : {
500- id : "msg-1" ,
501- sessionID : "session-456" ,
502- role : "user" ,
503- summary : { title : "Creating todos" } ,
504- } as any ,
505- } ,
506- }
507- await taskContinuation . handler ( { event : messageUpdateEvent } )
508-
509- // 4. Advance time past countdown
510- await vi . advanceTimersByTimeAsync ( 2000 )
511-
512- // 5. Continuation should be injected (countdown not cancelled by message update)
513- expect ( ctx . client . session . prompt ) . toHaveBeenCalled ( )
514-
515- // Verify continuation prompt content
516- const promptCall = ( ctx . client . session . prompt as any ) . mock . calls [ 0 ] [ 0 ] as PromptCall
517- expect ( promptCall . body . parts [ 0 ] . text ) . toContain ( "AUTO-CONTINUATION" )
518- expect ( promptCall . body . parts [ 0 ] . text ) . toContain ( "incomplete task" )
519- } )
520-
521- it ( "should handle rapid message updates without cancelling countdown" , async ( ) => {
410+ it ( "should handle rapid message updates without cancelling countdown" , async ( ) => {
522411 const ctx = createMockContext ( )
523412 const mockTodoFn = ctx . client . session . todo as unknown as {
524413 mockResolvedValue : ( val : Todo [ ] ) => void
0 commit comments