@@ -92,6 +92,7 @@ interface WatcherState {
9292 isBootstrapping : boolean ;
9393 hasEmittedSnapshot : boolean ;
9494 bufferedLogBatches : StoredLogEntry [ ] [ ] ;
95+ emittedLogEntries : StoredLogEntry [ ] ;
9596 failed : boolean ;
9697 needsPostBootstrapReconnect : boolean ;
9798 needsStopAfterBootstrap : boolean ;
@@ -224,6 +225,7 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
224225 key,
225226 subscribers : existing . subscriberCount ,
226227 } ) ;
228+ void this . emitCurrentSnapshot ( key ) ;
227229 return ;
228230 }
229231
@@ -399,6 +401,7 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
399401 isBootstrapping : false ,
400402 hasEmittedSnapshot : false ,
401403 bufferedLogBatches : [ ] ,
404+ emittedLogEntries : [ ] ,
402405 failed : false ,
403406 needsPostBootstrapReconnect : false ,
404407 needsStopAfterBootstrap : false ,
@@ -767,6 +770,7 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
767770 }
768771
769772 watcher . totalEntryCount += entries . length ;
773+ this . rememberEmittedLogEntries ( watcher , entries ) ;
770774
771775 this . emit ( CloudTaskEvent . Update , {
772776 taskId : watcher . taskId ,
@@ -812,6 +816,7 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
812816 }
813817
814818 watcher . totalEntryCount += dedupedEntries . length ;
819+ this . rememberEmittedLogEntries ( watcher , dedupedEntries ) ;
815820 this . emit ( CloudTaskEvent . Update , {
816821 taskId : watcher . taskId ,
817822 runId : watcher . runId ,
@@ -824,6 +829,92 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
824829 watcher . bufferedLogBatches = [ ] ;
825830 }
826831
832+ private rememberEmittedLogEntries (
833+ watcher : WatcherState ,
834+ entries : StoredLogEntry [ ] ,
835+ ) : void {
836+ watcher . emittedLogEntries . push ( ...entries ) ;
837+ }
838+
839+ private mergeHistoricalAndEmittedEntries (
840+ historicalEntries : StoredLogEntry [ ] ,
841+ emittedEntries : StoredLogEntry [ ] ,
842+ ) : {
843+ snapshotEntries : StoredLogEntry [ ] ;
844+ missingEmittedEntries : StoredLogEntry [ ] ;
845+ } {
846+ if ( emittedEntries . length === 0 ) {
847+ return { snapshotEntries : historicalEntries , missingEmittedEntries : [ ] } ;
848+ }
849+
850+ const historicalCounts = new Map < string , number > ( ) ;
851+ for ( const entry of historicalEntries ) {
852+ const serialized = JSON . stringify ( entry ) ;
853+ historicalCounts . set (
854+ serialized ,
855+ ( historicalCounts . get ( serialized ) ?? 0 ) + 1 ,
856+ ) ;
857+ }
858+
859+ const missingEmittedEntries = emittedEntries . filter ( ( entry ) => {
860+ const serialized = JSON . stringify ( entry ) ;
861+ const remaining = historicalCounts . get ( serialized ) ?? 0 ;
862+ if ( remaining <= 0 ) {
863+ return true ;
864+ }
865+
866+ historicalCounts . set ( serialized , remaining - 1 ) ;
867+ return false ;
868+ } ) ;
869+
870+ return {
871+ snapshotEntries : [ ...historicalEntries , ...missingEmittedEntries ] ,
872+ missingEmittedEntries,
873+ } ;
874+ }
875+
876+ private async emitCurrentSnapshot ( key : string ) : Promise < void > {
877+ const watcher = this . watchers . get ( key ) ;
878+ if ( ! watcher || watcher . failed ) return ;
879+
880+ const historicalEntries = await this . fetchAllSessionLogs ( watcher ) ;
881+ const currentWatcher = this . watchers . get ( key ) ;
882+ if ( ! currentWatcher || currentWatcher !== watcher || watcher . failed ) {
883+ return ;
884+ }
885+
886+ if ( ! historicalEntries ) {
887+ log . warn ( "Cloud task snapshot replay failed" , {
888+ taskId : watcher . taskId ,
889+ runId : watcher . runId ,
890+ } ) ;
891+ return ;
892+ }
893+
894+ const { snapshotEntries, missingEmittedEntries } =
895+ this . mergeHistoricalAndEmittedEntries (
896+ historicalEntries ,
897+ watcher . emittedLogEntries ,
898+ ) ;
899+ watcher . emittedLogEntries = missingEmittedEntries ;
900+ if ( snapshotEntries . length > watcher . totalEntryCount ) {
901+ watcher . totalEntryCount = snapshotEntries . length ;
902+ }
903+
904+ this . emit ( CloudTaskEvent . Update , {
905+ taskId : watcher . taskId ,
906+ runId : watcher . runId ,
907+ kind : "snapshot" ,
908+ newEntries : snapshotEntries ,
909+ totalEntryCount : snapshotEntries . length ,
910+ status : watcher . lastStatus ?? undefined ,
911+ stage : watcher . lastStage ,
912+ output : watcher . lastOutput ,
913+ errorMessage : watcher . lastErrorMessage ,
914+ branch : watcher . lastBranch ,
915+ } ) ;
916+ }
917+
827918 private failWatcher ( key : string , error : CloudTaskConnectionError ) : void {
828919 const watcher = this . watchers . get ( key ) ;
829920 if ( ! watcher ) return ;
0 commit comments