@@ -143,7 +143,7 @@ export class Cline {
143143 this . fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
144144 this . providerRef = new WeakRef ( provider )
145145 this . diffViewProvider = new DiffViewProvider ( cwd )
146- this . checkpointsEnabled = process . platform !== "win32" && ! ! enableCheckpoints
146+ this . checkpointsEnabled = enableCheckpoints ?? false
147147
148148 if ( historyItem ) {
149149 this . taskId = historyItem . id
@@ -370,7 +370,13 @@ export class Cline {
370370 this . askResponseImages = images
371371 }
372372
373- async say ( type : ClineSay , text ?: string , images ?: string [ ] , partial ?: boolean ) : Promise < undefined > {
373+ async say (
374+ type : ClineSay ,
375+ text ?: string ,
376+ images ?: string [ ] ,
377+ partial ?: boolean ,
378+ checkpoint ?: Record < string , unknown > ,
379+ ) : Promise < undefined > {
374380 if ( this . abort ) {
375381 throw new Error ( "Roo Code instance aborted" )
376382 }
@@ -423,7 +429,7 @@ export class Cline {
423429 // this is a new non-partial message, so add it like normal
424430 const sayTs = Date . now ( )
425431 this . lastMessageTs = sayTs
426- await this . addToClineMessages ( { ts : sayTs , type : "say" , say : type , text, images } )
432+ await this . addToClineMessages ( { ts : sayTs , type : "say" , say : type , text, images, checkpoint } )
427433 await this . providerRef . deref ( ) ?. postStateToWebview ( )
428434 }
429435 }
@@ -726,13 +732,18 @@ export class Cline {
726732 }
727733
728734 async abortTask ( ) {
729- this . abort = true // Will stop any autonomously running promises.
735+ // Will stop any autonomously running promises.
736+ this . abort = true
737+
730738 this . terminalManager . disposeAll ( )
731739 this . urlContentFetcher . closeBrowser ( )
732740 this . browserSession . closeBrowser ( )
733- // Need to await for when we want to make sure directories/files are
734- // reverted before re-starting the task from a checkpoint.
735- await this . diffViewProvider . revertChanges ( )
741+
742+ // If we're not streaming then `abortStream` (which reverts the diff
743+ // view changes) won't be called, so we need to revert the changes here.
744+ if ( this . isStreaming && this . diffViewProvider . isEditing ) {
745+ await this . diffViewProvider . revertChanges ( )
746+ }
736747 }
737748
738749 // Tools
@@ -822,29 +833,21 @@ export class Cline {
822833 const { mcpEnabled, alwaysApproveResubmit, requestDelaySeconds, rateLimitSeconds } =
823834 ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
824835
825- let finalDelay = 0
836+ let rateLimitDelay = 0
826837
827838 // Only apply rate limiting if this isn't the first request
828839 if ( this . lastApiRequestTime ) {
829840 const now = Date . now ( )
830841 const timeSinceLastRequest = now - this . lastApiRequestTime
831842 const rateLimit = rateLimitSeconds || 0
832- const rateLimitDelay = Math . max ( 0 , rateLimit * 1000 - timeSinceLastRequest )
833- finalDelay = rateLimitDelay
834- }
835-
836- // Add exponential backoff delay for retries
837- if ( retryAttempt > 0 ) {
838- const baseDelay = requestDelaySeconds || 5
839- const exponentialDelay = Math . ceil ( baseDelay * Math . pow ( 2 , retryAttempt ) ) * 1000
840- finalDelay = Math . max ( finalDelay , exponentialDelay )
843+ rateLimitDelay = Math . ceil ( Math . max ( 0 , rateLimit * 1000 - timeSinceLastRequest ) / 1000 )
841844 }
842845
843- if ( finalDelay > 0 ) {
846+ // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there.
847+ if ( rateLimitDelay > 0 && retryAttempt === 0 ) {
844848 // Show countdown timer
845- for ( let i = Math . ceil ( finalDelay / 1000 ) ; i > 0 ; i -- ) {
846- const delayMessage =
847- retryAttempt > 0 ? `Retrying in ${ i } seconds...` : `Rate limiting for ${ i } seconds...`
849+ for ( let i = rateLimitDelay ; i > 0 ; i -- ) {
850+ const delayMessage = `Rate limiting for ${ i } seconds...`
848851 await this . say ( "api_req_retry_delayed" , delayMessage , undefined , true )
849852 await delay ( 1000 )
850853 }
@@ -959,9 +962,11 @@ export class Cline {
959962 const errorMsg = error . message ?? "Unknown error"
960963 const baseDelay = requestDelaySeconds || 5
961964 const exponentialDelay = Math . ceil ( baseDelay * Math . pow ( 2 , retryAttempt ) )
965+ // Wait for the greater of the exponential delay or the rate limit delay
966+ const finalDelay = Math . max ( exponentialDelay , rateLimitDelay )
962967
963968 // Show countdown timer with exponential backoff
964- for ( let i = exponentialDelay ; i > 0 ; i -- ) {
969+ for ( let i = finalDelay ; i > 0 ; i -- ) {
965970 await this . say (
966971 "api_req_retry_delayed" ,
967972 `${ errorMsg } \n\nRetry attempt ${ retryAttempt + 1 } \nRetrying in ${ i } seconds...` ,
@@ -2753,6 +2758,13 @@ export class Cline {
27532758 // get previous api req's index to check token usage and determine if we need to truncate conversation history
27542759 const previousApiReqIndex = findLastIndex ( this . clineMessages , ( m ) => m . say === "api_req_started" )
27552760
2761+ // Save checkpoint if this is the first API request.
2762+ const isFirstRequest = this . clineMessages . filter ( ( m ) => m . say === "api_req_started" ) . length === 0
2763+
2764+ if ( isFirstRequest ) {
2765+ await this . checkpointSave ( )
2766+ }
2767+
27562768 // getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
27572769 // for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
27582770 await this . say (
@@ -2810,6 +2822,8 @@ export class Cline {
28102822 }
28112823
28122824 const abortStream = async ( cancelReason : ClineApiReqCancelReason , streamingFailedMessage ?: string ) => {
2825+ console . log ( `[Cline#abortStream] cancelReason = ${ cancelReason } ` )
2826+
28132827 if ( this . diffViewProvider . isEditing ) {
28142828 await this . diffViewProvider . revertChanges ( ) // closes diff view
28152829 }
@@ -2864,6 +2878,7 @@ export class Cline {
28642878 const stream = this . attemptApiRequest ( previousApiReqIndex ) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
28652879 let assistantMessage = ""
28662880 let reasoningMessage = ""
2881+ this . isStreaming = true
28672882 try {
28682883 for await ( const chunk of stream ) {
28692884 if ( ! chunk ) {
@@ -2896,11 +2911,13 @@ export class Cline {
28962911 }
28972912
28982913 if ( this . abort ) {
2899- console . log ( "aborting stream..." )
2914+ console . log ( `aborting stream, this.abandoned = ${ this . abandoned } ` )
2915+
29002916 if ( ! this . abandoned ) {
29012917 // only need to gracefully abort if this instance isn't abandoned (sometimes openrouter stream hangs, in which case this would affect future instances of cline)
29022918 await abortStream ( "user_cancelled" )
29032919 }
2920+
29042921 break // aborts the stream
29052922 }
29062923
@@ -2933,6 +2950,8 @@ export class Cline {
29332950 // await this.providerRef.deref()?.postStateToWebview()
29342951 }
29352952 }
2953+ } finally {
2954+ this . isStreaming = false
29362955 }
29372956
29382957 // need to call here in case the stream was aborted
@@ -3294,12 +3313,7 @@ export class Cline {
32943313 ] ) ,
32953314 )
32963315 } catch ( err ) {
3297- this . providerRef
3298- . deref ( )
3299- ?. log (
3300- `[checkpointDiff] Encountered unexpected error: $${ err instanceof Error ? err . message : String ( err ) } ` ,
3301- )
3302-
3316+ this . providerRef . deref ( ) ?. log ( "[checkpointDiff] disabling checkpoints for this task" )
33033317 this . checkpointsEnabled = false
33043318 }
33053319 }
@@ -3310,6 +3324,7 @@ export class Cline {
33103324 }
33113325
33123326 try {
3327+ const isFirst = ! this . checkpointService
33133328 const service = await this . getCheckpointService ( )
33143329 const commit = await service . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
33153330
@@ -3318,15 +3333,17 @@ export class Cline {
33183333 . deref ( )
33193334 ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : service . currentCheckpoint } )
33203335
3321- await this . say ( "checkpoint_saved" , commit . commit )
3336+ // Checkpoint metadata required by the UI.
3337+ const checkpoint = {
3338+ isFirst,
3339+ from : service . baseCommitHash ,
3340+ to : commit . commit ,
3341+ }
3342+
3343+ await this . say ( "checkpoint_saved" , commit . commit , undefined , undefined , checkpoint )
33223344 }
33233345 } catch ( err ) {
3324- this . providerRef
3325- . deref ( )
3326- ?. log (
3327- `[checkpointSave] Encountered unexpected error: $${ err instanceof Error ? err . message : String ( err ) } ` ,
3328- )
3329-
3346+ this . providerRef . deref ( ) ?. log ( "[checkpointSave] disabling checkpoints for this task" )
33303347 this . checkpointsEnabled = false
33313348 }
33323349 }
@@ -3396,12 +3413,7 @@ export class Cline {
33963413 // Cline instance.
33973414 this . providerRef . deref ( ) ?. cancelTask ( )
33983415 } catch ( err ) {
3399- this . providerRef
3400- . deref ( )
3401- ?. log (
3402- `[restoreCheckpoint] Encountered unexpected error: $${ err instanceof Error ? err . message : String ( err ) } ` ,
3403- )
3404-
3416+ this . providerRef . deref ( ) ?. log ( "[checkpointRestore] disabling checkpoints for this task" )
34053417 this . checkpointsEnabled = false
34063418 }
34073419 }
0 commit comments