@@ -131,7 +131,7 @@ describe("Checkpoint functionality", () => {
131131 // saveCheckpoint should have been called
132132 expect ( mockCheckpointService . saveCheckpoint ) . toHaveBeenCalledWith (
133133 expect . stringContaining ( "Task: test-task-id" ) ,
134- { allowEmpty : true , suppressMessage : false } ,
134+ { allowEmpty : true , suppressMessage : false , suppressBroadcast : false } ,
135135 )
136136
137137 // Result should contain the commit hash
@@ -473,6 +473,116 @@ describe("Checkpoint functionality", () => {
473473 } )
474474 } )
475475
476+ describe ( "checkpointSave - task-start baseline broadcast suppression" , ( ) => {
477+ type CheckpointPayload = {
478+ type : "checkpoint"
479+ fromHash : string
480+ toHash : string
481+ duration : number
482+ suppressMessage ?: boolean
483+ suppressBroadcast ?: boolean
484+ }
485+ type PostedWebviewMessage = { type ?: string ; text ?: string ; suppressMessage ?: boolean }
486+
487+ const broadcastPosts = ( ) : PostedWebviewMessage [ ] =>
488+ mockProvider . postMessageToWebview . mock . calls
489+ . map ( ( [ message ] : PostedWebviewMessage [ ] ) => message )
490+ . filter ( ( message : PostedWebviewMessage ) => message ?. type === "currentCheckpointUpdated" )
491+
492+ const registerCheckpointHandler = async ( ) => {
493+ // The handler is registered when the service is created, so drop the pre-made
494+ // double and let getCheckpointService wire it up against the same double.
495+ mockTask . checkpointService = undefined
496+ mockTask . checkpointServiceInitializing = false
497+
498+ await getCheckpointService ( mockTask )
499+
500+ const registration = mockCheckpointService . on . mock . calls . find (
501+ ( [ event ] : [ string ] ) => event === "checkpoint" ,
502+ ) as [ string , ( payload : CheckpointPayload ) => void ] | undefined
503+ if ( ! registration ) {
504+ throw new Error ( "the checkpoint handler was never registered on the service" )
505+ }
506+ return registration [ 1 ]
507+ }
508+
509+ const emitOfLastSave = ( ) => {
510+ const call = mockCheckpointService . saveCheckpoint . mock . calls . at ( - 1 )
511+ if ( ! call ) {
512+ throw new Error ( "no checkpoint save reached the service" )
513+ }
514+ return call [ 1 ] as { suppressMessage ?: boolean ; suppressBroadcast ?: boolean }
515+ }
516+
517+ it ( "threads the suppression flag to the service for the save that asks for it" , async ( ) => {
518+ await checkpointSave ( mockTask , true , true , undefined , true )
519+
520+ expect ( mockCheckpointService . saveCheckpoint ) . toHaveBeenCalledWith (
521+ expect . stringContaining ( "Task: test-task-id" ) ,
522+ { allowEmpty : true , suppressMessage : true , suppressBroadcast : true } ,
523+ )
524+ } )
525+
526+ it ( "keeps allowEmpty in force so a clean workspace still commits the baseline" , async ( ) => {
527+ await expect ( checkpointSave ( mockTask , true , true , undefined , true ) ) . resolves . toMatchObject ( {
528+ commit : "test-commit-hash" ,
529+ } )
530+ } )
531+
532+ it ( "suppresses only the current-checkpoint broadcast for the baseline save" , async ( ) => {
533+ const handler = await registerCheckpointHandler ( )
534+ mockCheckpointService . saveCheckpoint . mockResolvedValue ( { commit : HASH_2 } )
535+
536+ await checkpointSave ( mockTask , true , true , undefined , true )
537+ const options = emitOfLastSave ( )
538+ handler ( {
539+ type : "checkpoint" ,
540+ fromHash : HASH_1 ,
541+ toHash : HASH_2 ,
542+ duration : 1 ,
543+ suppressMessage : options . suppressMessage ,
544+ suppressBroadcast : options . suppressBroadcast ,
545+ } )
546+
547+ expect ( broadcastPosts ( ) ) . toHaveLength ( 0 )
548+ expect ( mockTask . say ) . toHaveBeenCalledWith (
549+ "checkpoint_saved" ,
550+ HASH_2 ,
551+ undefined ,
552+ undefined ,
553+ { from : HASH_1 , to : HASH_2 , suppressMessage : true } ,
554+ undefined ,
555+ { isNonInteractive : true } ,
556+ )
557+ } )
558+
559+ it ( "still broadcasts the current checkpoint after a real user checkpoint" , async ( ) => {
560+ // The user-facing shape at Task.ts:2485 - a save whose chat row is suppressed
561+ // but whose checkpoint the user can still roll back to and see changes for.
562+ const handler = await registerCheckpointHandler ( )
563+ mockCheckpointService . saveCheckpoint . mockResolvedValue ( { commit : HASH_2 } )
564+
565+ await checkpointSave ( mockTask , false , true )
566+ const options = emitOfLastSave ( )
567+ handler ( {
568+ type : "checkpoint" ,
569+ fromHash : HASH_1 ,
570+ toHash : HASH_2 ,
571+ duration : 1 ,
572+ suppressMessage : options . suppressMessage ,
573+ suppressBroadcast : options . suppressBroadcast ,
574+ } )
575+
576+ const broadcasts = broadcastPosts ( )
577+ expect ( broadcasts ) . toHaveLength ( 1 )
578+ expect ( broadcasts [ 0 ] ) . toEqual ( {
579+ type : "currentCheckpointUpdated" ,
580+ text : HASH_2 ,
581+ suppressMessage : true ,
582+ } )
583+ } )
584+ } )
585+
476586 describe ( "getCheckpointService" , ( ) => {
477587 it ( "should return existing service if available" , async ( ) => {
478588 const service = await getCheckpointService ( mockTask )
0 commit comments