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 = "workspace" ,
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,45 +3381,36 @@ 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 } ) => {
3401- 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- }
3407+ const service =
3408+ this . checkpointStorage === "task"
3409+ ? RepoPerTaskCheckpointService . create ( options )
3410+ : RepoPerWorkspaceCheckpointService . create ( options )
34093411
3412+ service . on ( "initialize" , ( ) => {
3413+ try {
34103414 this . checkpointService = service
34113415 this . checkpointSave ( )
34123416 } catch ( err ) {
@@ -3417,7 +3421,6 @@ export class Cline {
34173421
34183422 service . on ( "checkpoint" , ( { isFirst, fromHash : from , toHash : to } ) => {
34193423 try {
3420- log ( `[Cline#initializeCheckpoints] ${ isFirst ? "initial" : "incremental" } checkpoint saved: ${ to } ` )
34213424 this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : to } )
34223425
34233426 this . say ( "checkpoint_saved" , to , undefined , undefined , { isFirst, from, to } ) . catch ( ( e ) =>
@@ -3430,9 +3433,35 @@ export class Cline {
34303433 } )
34313434
34323435 service . initShadowGit ( )
3436+ return service
34333437 } catch ( err ) {
3434- log ( "[Cline#initializeCheckpoints] caught error in initializeCheckpoints() , disabling checkpoints" )
3438+ log ( "[Cline#initializeCheckpoints] caught unexpected error , disabling checkpoints" )
34353439 this . enableCheckpoints = false
3440+ return undefined
3441+ }
3442+ }
3443+
3444+ private async getInitializedCheckpointService ( {
3445+ interval = 250 ,
3446+ timeout = 15_000 ,
3447+ } : { interval ?: number ; timeout ?: number } = { } ) {
3448+ const service = this . getCheckpointService ( )
3449+
3450+ if ( ! service || service . isInitialized ) {
3451+ return service
3452+ }
3453+
3454+ try {
3455+ await pWaitFor (
3456+ ( ) => {
3457+ console . log ( "[Cline#getCheckpointService] waiting for service to initialize" )
3458+ return service . isInitialized
3459+ } ,
3460+ { interval, timeout } ,
3461+ )
3462+ return service
3463+ } catch ( err ) {
3464+ return undefined
34363465 }
34373466 }
34383467
@@ -3445,7 +3474,9 @@ export class Cline {
34453474 commitHash : string
34463475 mode : "full" | "checkpoint"
34473476 } ) {
3448- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3477+ const service = await this . getInitializedCheckpointService ( )
3478+
3479+ if ( ! service ) {
34493480 return
34503481 }
34513482
@@ -3461,7 +3492,7 @@ export class Cline {
34613492 }
34623493
34633494 try {
3464- const changes = await this . checkpointService . getDiff ( { from : previousCommitHash , to : commitHash } )
3495+ const changes = await service . getDiff ( { from : previousCommitHash , to : commitHash } )
34653496
34663497 if ( ! changes ?. length ) {
34673498 vscode . window . showInformationMessage ( "No changes found." )
@@ -3488,12 +3519,22 @@ export class Cline {
34883519 }
34893520
34903521 public checkpointSave ( ) {
3491- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3522+ const service = this . getCheckpointService ( )
3523+
3524+ if ( ! service ) {
3525+ return
3526+ }
3527+
3528+ if ( ! service . isInitialized ) {
3529+ this . providerRef
3530+ . deref ( )
3531+ ?. log ( "[checkpointSave] checkpoints didn't initialize in time, disabling checkpoints for this task" )
3532+ this . enableCheckpoints = false
34923533 return
34933534 }
34943535
34953536 // Start the checkpoint process in the background.
3496- this . checkpointService . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
3537+ service . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
34973538 }
34983539
34993540 public async checkpointRestore ( {
@@ -3505,7 +3546,9 @@ export class Cline {
35053546 commitHash : string
35063547 mode : "preview" | "restore"
35073548 } ) {
3508- if ( ! this . checkpointService || ! this . enableCheckpoints ) {
3549+ const service = await this . getInitializedCheckpointService ( )
3550+
3551+ if ( ! service ) {
35093552 return
35103553 }
35113554
@@ -3516,7 +3559,7 @@ export class Cline {
35163559 }
35173560
35183561 try {
3519- await this . checkpointService . restoreCheckpoint ( commitHash )
3562+ await service . restoreCheckpoint ( commitHash )
35203563
35213564 await this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : commitHash } )
35223565
0 commit comments