11import { Anthropic } from "@anthropic-ai/sdk"
22import cloneDeep from "clone-deep"
3- import { DiffStrategy , getDiffStrategy , UnifiedDiffStrategy } from "./diff/DiffStrategy"
3+ import { DiffStrategy , getDiffStrategy } from "./diff/DiffStrategy"
44import { validateToolUse , isToolAllowedForMode , ToolName } from "./mode-validator"
55import delay from "delay"
66import fs from "fs/promises"
@@ -13,7 +13,11 @@ import * as vscode from "vscode"
1313import { ApiHandler , buildApiHandler } from "../api"
1414import { ApiStream } from "../api/transform/stream"
1515import { DIFF_VIEW_URI_SCHEME , DiffViewProvider } from "../integrations/editor/DiffViewProvider"
16- import { ShadowCheckpointService } from "../services/checkpoints/ShadowCheckpointService"
16+ import {
17+ CheckpointServiceOptions ,
18+ RepoPerTaskCheckpointService ,
19+ RepoPerWorkspaceCheckpointService ,
20+ } from "../services/checkpoints"
1721import { findToolName , formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
1822import {
1923 extractTextFromFile ,
@@ -77,6 +81,7 @@ export type ClineOptions = {
7781 customInstructions ?: string
7882 enableDiff ?: boolean
7983 enableCheckpoints ?: boolean
84+ checkpointStorage ?: "task" | "workspace"
8085 fuzzyMatchThreshold ?: number
8186 task ?: string
8287 images ?: string [ ]
@@ -115,8 +120,9 @@ export class Cline {
115120 isInitialized = false
116121
117122 // checkpoints
118- enableCheckpoints : boolean = false
119- private checkpointService ?: ShadowCheckpointService
123+ private enableCheckpoints : boolean
124+ private checkpointStorage : "task" | "workspace"
125+ private checkpointService ?: RepoPerTaskCheckpointService | RepoPerWorkspaceCheckpointService
120126
121127 // streaming
122128 isWaitingForFirstChunk = false
@@ -136,7 +142,8 @@ export class Cline {
136142 apiConfiguration,
137143 customInstructions,
138144 enableDiff,
139- enableCheckpoints,
145+ enableCheckpoints = false ,
146+ checkpointStorage = "task" ,
140147 fuzzyMatchThreshold,
141148 task,
142149 images,
@@ -160,7 +167,8 @@ export class Cline {
160167 this . fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
161168 this . providerRef = new WeakRef ( provider )
162169 this . diffViewProvider = new DiffViewProvider ( cwd )
163- this . enableCheckpoints = enableCheckpoints ?? false
170+ this . enableCheckpoints = enableCheckpoints
171+ this . checkpointStorage = checkpointStorage
164172
165173 // Initialize diffStrategy based on current state
166174 this . updateDiffStrategy ( Experiments . isEnabled ( experiments ?? { } , EXPERIMENT_IDS . DIFF_STRATEGY ) )
@@ -747,7 +755,8 @@ export class Cline {
747755 }
748756
749757 private async initiateTaskLoop ( userContent : UserContent ) : Promise < void > {
750- this . initializeCheckpoints ( )
758+ // Kicks off the checkpoints initialization process in the background.
759+ this . getCheckpointService ( )
751760
752761 let nextUserContent = userContent
753762 let includeFileDetails = true
@@ -3352,9 +3361,13 @@ export class Cline {
33523361
33533362 // Checkpoints
33543363
3355- private async initializeCheckpoints ( ) {
3364+ private getCheckpointService ( ) {
33563365 if ( ! this . enableCheckpoints ) {
3357- return
3366+ return undefined
3367+ }
3368+
3369+ if ( this . checkpointService ) {
3370+ return this . checkpointService
33583371 }
33593372
33603373 const log = ( message : string ) => {
@@ -3368,47 +3381,45 @@ export class Cline {
33683381 }
33693382
33703383 try {
3371- if ( this . checkpointService ) {
3372- log ( "[Cline#initializeCheckpoints] checkpointService already initialized" )
3373- return
3374- }
3375-
33763384 const workspaceDir = vscode . workspace . workspaceFolders ?. map ( ( folder ) => folder . uri . fsPath ) . at ( 0 )
33773385
33783386 if ( ! workspaceDir ) {
33793387 log ( "[Cline#initializeCheckpoints] workspace folder not found, disabling checkpoints" )
33803388 this . enableCheckpoints = false
3381- return
3389+ return undefined
33823390 }
33833391
3384- const shadowDir = this . providerRef . deref ( ) ?. context . globalStorageUri . fsPath
3392+ const globalStorageDir = this . providerRef . deref ( ) ?. context . globalStorageUri . fsPath
33853393
3386- if ( ! shadowDir ) {
3387- log ( "[Cline#initializeCheckpoints] shadowDir not found, disabling checkpoints" )
3394+ if ( ! globalStorageDir ) {
3395+ log ( "[Cline#initializeCheckpoints] globalStorageDir not found, disabling checkpoints" )
33883396 this . enableCheckpoints = false
3389- return
3397+ return undefined
33903398 }
33913399
3392- const service = await ShadowCheckpointService . create ( { taskId : this . taskId , workspaceDir, shadowDir, log } )
3393-
3394- if ( ! service ) {
3395- log ( "[Cline#initializeCheckpoints] failed to create checkpoint service, disabling checkpoints" )
3396- this . enableCheckpoints = false
3397- return
3400+ const options : CheckpointServiceOptions = {
3401+ taskId : this . taskId ,
3402+ workspaceDir,
3403+ shadowDir : globalStorageDir ,
3404+ log,
33983405 }
33993406
3400- service . on ( "initialize" , ( { workspaceDir, created, duration } ) => {
3407+ const service =
3408+ this . checkpointStorage === "task"
3409+ ? RepoPerTaskCheckpointService . create ( options )
3410+ : RepoPerWorkspaceCheckpointService . create ( options )
3411+
3412+ service . on ( "initialize" , ( ) => {
34013413 try {
3402- if ( created ) {
3403- log ( `[Cline#initializeCheckpoints] created new shadow repo (${ workspaceDir } ) in ${ duration } ms` )
3404- } else {
3405- log (
3406- `[Cline#initializeCheckpoints] found existing shadow repo (${ workspaceDir } ) in ${ duration } ms` ,
3407- )
3408- }
3414+ const isCheckpointNeeded =
3415+ typeof this . clineMessages . find ( ( { say } ) => say === "checkpoint_saved" ) === "undefined"
34093416
34103417 this . checkpointService = service
3411- this . checkpointSave ( )
3418+
3419+ if ( isCheckpointNeeded ) {
3420+ log ( "[Cline#initializeCheckpoints] no checkpoints found, saving initial checkpoint" )
3421+ this . checkpointSave ( )
3422+ }
34123423 } catch ( err ) {
34133424 log ( "[Cline#initializeCheckpoints] caught error in on('initialize'), disabling checkpoints" )
34143425 this . enableCheckpoints = false
@@ -3417,41 +3428,77 @@ export class Cline {
34173428
34183429 service . on ( "checkpoint" , ( { isFirst, fromHash : from , toHash : to } ) => {
34193430 try {
3420- log ( `[Cline#initializeCheckpoints] ${ isFirst ? "initial" : "incremental" } checkpoint saved: ${ to } ` )
34213431 this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : to } )
34223432
3423- this . say ( "checkpoint_saved" , to , undefined , undefined , { isFirst, from, to } ) . catch ( ( e ) =>
3424- console . error ( "Error saving checkpoint message:" , e ) ,
3425- )
3433+ this . say ( "checkpoint_saved" , to , undefined , undefined , { isFirst, from, to } ) . catch ( ( err ) => {
3434+ log ( "[Cline#initializeCheckpoints] caught unexpected error in say('checkpoint_saved')" )
3435+ console . error ( err )
3436+ } )
34263437 } catch ( err ) {
3427- log ( "[Cline#initializeCheckpoints] caught error in on('checkpoint'), disabling checkpoints" )
3438+ log (
3439+ "[Cline#initializeCheckpoints] caught unexpected error in on('checkpoint'), disabling checkpoints" ,
3440+ )
3441+ console . error ( err )
34283442 this . enableCheckpoints = false
34293443 }
34303444 } )
34313445
3432- service . initShadowGit ( )
3446+ service . initShadowGit ( ) . catch ( ( err ) => {
3447+ log ( "[Cline#initializeCheckpoints] caught unexpected error in initShadowGit, disabling checkpoints" )
3448+ console . error ( err )
3449+ this . enableCheckpoints = false
3450+ } )
3451+
3452+ return service
34333453 } catch ( err ) {
3434- log ( "[Cline#initializeCheckpoints] caught error in initializeCheckpoints() , disabling checkpoints" )
3454+ log ( "[Cline#initializeCheckpoints] caught unexpected error , disabling checkpoints" )
34353455 this . enableCheckpoints = false
3456+ return undefined
3457+ }
3458+ }
3459+
3460+ private async getInitializedCheckpointService ( {
3461+ interval = 250 ,
3462+ timeout = 15_000 ,
3463+ } : { interval ?: number ; timeout ?: number } = { } ) {
3464+ const service = this . getCheckpointService ( )
3465+
3466+ if ( ! service || service . isInitialized ) {
3467+ return service
3468+ }
3469+
3470+ try {
3471+ await pWaitFor (
3472+ ( ) => {
3473+ console . log ( "[Cline#getCheckpointService] waiting for service to initialize" )
3474+ return service . isInitialized
3475+ } ,
3476+ { interval, timeout } ,
3477+ )
3478+ return service
3479+ } catch ( err ) {
3480+ return undefined
34363481 }
34373482 }
34383483
34393484 public async checkpointDiff ( {
34403485 ts,
3486+ previousCommitHash,
34413487 commitHash,
34423488 mode,
34433489 } : {
34443490 ts : number
3491+ previousCommitHash ?: string
34453492 commitHash : string
34463493 mode : "full" | "checkpoint"
34473494 } ) {
3448- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3495+ const service = await this . getInitializedCheckpointService ( )
3496+
3497+ if ( ! service ) {
34493498 return
34503499 }
34513500
3452- let previousCommitHash = undefined
3453-
3454- if ( mode === "checkpoint" ) {
3501+ if ( ! previousCommitHash && mode === "checkpoint" ) {
34553502 const previousCheckpoint = this . clineMessages
34563503 . filter ( ( { say } ) => say === "checkpoint_saved" )
34573504 . sort ( ( a , b ) => b . ts - a . ts )
@@ -3461,7 +3508,7 @@ export class Cline {
34613508 }
34623509
34633510 try {
3464- const changes = await this . checkpointService . getDiff ( { from : previousCommitHash , to : commitHash } )
3511+ const changes = await service . getDiff ( { from : previousCommitHash , to : commitHash } )
34653512
34663513 if ( ! changes ?. length ) {
34673514 vscode . window . showInformationMessage ( "No changes found." )
@@ -3488,12 +3535,25 @@ export class Cline {
34883535 }
34893536
34903537 public checkpointSave ( ) {
3491- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3538+ const service = this . getCheckpointService ( )
3539+
3540+ if ( ! service ) {
3541+ return
3542+ }
3543+
3544+ if ( ! service . isInitialized ) {
3545+ this . providerRef
3546+ . deref ( )
3547+ ?. log ( "[checkpointSave] checkpoints didn't initialize in time, disabling checkpoints for this task" )
3548+ this . enableCheckpoints = false
34923549 return
34933550 }
34943551
34953552 // Start the checkpoint process in the background.
3496- this . checkpointService . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
3553+ service . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` ) . catch ( ( err ) => {
3554+ console . error ( "[Cline#checkpointSave] caught unexpected error, disabling checkpoints" , err )
3555+ this . enableCheckpoints = false
3556+ } )
34973557 }
34983558
34993559 public async checkpointRestore ( {
@@ -3505,7 +3565,9 @@ export class Cline {
35053565 commitHash : string
35063566 mode : "preview" | "restore"
35073567 } ) {
3508- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3568+ const service = await this . getInitializedCheckpointService ( )
3569+
3570+ if ( ! service ) {
35093571 return
35103572 }
35113573
@@ -3516,7 +3578,7 @@ export class Cline {
35163578 }
35173579
35183580 try {
3519- await this . checkpointService . restoreCheckpoint ( commitHash )
3581+ await service . restoreCheckpoint ( commitHash )
35203582
35213583 await this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : commitHash } )
35223584
0 commit comments