@@ -6,13 +6,14 @@ import delay from "delay"
66import fs from "fs/promises"
77import os from "os"
88import pWaitFor from "p-wait-for"
9+ import getFolderSize from "get-folder-size"
910import * as path from "path"
1011import { serializeError } from "serialize-error"
1112import * as vscode from "vscode"
1213import { ApiHandler , SingleCompletionHandler , buildApiHandler } from "../api"
1314import { ApiStream } from "../api/transform/stream"
1415import { DIFF_VIEW_URI_SCHEME , DiffViewProvider } from "../integrations/editor/DiffViewProvider"
15- import { CheckpointService } from "../services/checkpoints/CheckpointService "
16+ import { CheckpointService , CheckpointServiceFactory } from "../services/checkpoints"
1617import { findToolName , formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
1718import {
1819 extractTextFromFile ,
@@ -239,7 +240,8 @@ export class Cline {
239240
240241 private async saveClineMessages ( ) {
241242 try {
242- const filePath = path . join ( await this . ensureTaskDirectoryExists ( ) , GlobalFileNames . uiMessages )
243+ const taskDir = await this . ensureTaskDirectoryExists ( )
244+ const filePath = path . join ( taskDir , GlobalFileNames . uiMessages )
243245 await fs . writeFile ( filePath , JSON . stringify ( this . clineMessages ) )
244246 // combined as they are in ChatView
245247 const apiMetrics = getApiMetrics ( combineApiRequests ( combineCommandSequences ( this . clineMessages . slice ( 1 ) ) ) )
@@ -251,6 +253,17 @@ export class Cline {
251253 ( m ) => ! ( m . ask === "resume_task" || m . ask === "resume_completed_task" ) ,
252254 )
253255 ]
256+
257+ let taskDirSize = 0
258+
259+ try {
260+ taskDirSize = await getFolderSize . loose ( taskDir )
261+ } catch ( err ) {
262+ console . error (
263+ `[saveClineMessages] failed to get task directory size (${ taskDir } ): ${ err instanceof Error ? err . message : String ( err ) } ` ,
264+ )
265+ }
266+
254267 await this . providerRef . deref ( ) ?. updateTaskHistory ( {
255268 id : this . taskId ,
256269 ts : lastRelevantMessage . ts ,
@@ -260,6 +273,7 @@ export class Cline {
260273 cacheWrites : apiMetrics . totalCacheWrites ,
261274 cacheReads : apiMetrics . totalCacheReads ,
262275 totalCost : apiMetrics . totalCost ,
276+ size : taskDirSize ,
263277 } )
264278 } catch ( error ) {
265279 console . error ( "Failed to save cline messages:" , error )
@@ -2692,7 +2706,7 @@ export class Cline {
26922706 }
26932707
26942708 if ( isCheckpointPossible ) {
2695- await this . checkpointSave ( )
2709+ await this . checkpointSave ( { isFirst : false } )
26962710 }
26972711
26982712 /*
@@ -2762,7 +2776,7 @@ export class Cline {
27622776 const isFirstRequest = this . clineMessages . filter ( ( m ) => m . say === "api_req_started" ) . length === 0
27632777
27642778 if ( isFirstRequest ) {
2765- await this . checkpointSave ( )
2779+ await this . checkpointSave ( { isFirst : true } )
27662780 }
27672781
27682782 // 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
@@ -3098,11 +3112,14 @@ export class Cline {
30983112 }
30993113
31003114 details += "\n\n# VSCode Open Tabs"
3115+ const { maxOpenTabsContext } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
3116+ const maxTabs = maxOpenTabsContext ?? 20
31013117 const openTabs = vscode . window . tabGroups . all
31023118 . flatMap ( ( group ) => group . tabs )
31033119 . map ( ( tab ) => ( tab . input as vscode . TabInputText ) ?. uri ?. fsPath )
31043120 . filter ( Boolean )
31053121 . map ( ( absolutePath ) => path . relative ( cwd , absolutePath ) . toPosix ( ) )
3122+ . slice ( 0 , maxTabs )
31063123 . join ( "\n" )
31073124 if ( openTabs ) {
31083125 details += `\n${ openTabs } `
@@ -3255,11 +3272,32 @@ export class Cline {
32553272 // Checkpoints
32563273
32573274 private async getCheckpointService ( ) {
3275+ if ( ! this . checkpointsEnabled ) {
3276+ throw new Error ( "Checkpoints are disabled" )
3277+ }
3278+
32583279 if ( ! this . checkpointService ) {
3259- this . checkpointService = await CheckpointService . create ( {
3260- taskId : this . taskId ,
3261- baseDir : vscode . workspace . workspaceFolders ?. map ( ( folder ) => folder . uri . fsPath ) . at ( 0 ) ?? "" ,
3262- log : ( message ) => this . providerRef . deref ( ) ?. log ( message ) ,
3280+ const workspaceDir = vscode . workspace . workspaceFolders ?. map ( ( folder ) => folder . uri . fsPath ) . at ( 0 )
3281+ const shadowDir = this . providerRef . deref ( ) ?. context . globalStorageUri . fsPath
3282+
3283+ if ( ! workspaceDir ) {
3284+ this . providerRef . deref ( ) ?. log ( "[getCheckpointService] workspace folder not found" )
3285+ throw new Error ( "Workspace directory not found" )
3286+ }
3287+
3288+ if ( ! shadowDir ) {
3289+ this . providerRef . deref ( ) ?. log ( "[getCheckpointService] shadowDir not found" )
3290+ throw new Error ( "Global storage directory not found" )
3291+ }
3292+
3293+ this . checkpointService = await CheckpointServiceFactory . create ( {
3294+ strategy : "shadow" ,
3295+ options : {
3296+ taskId : this . taskId ,
3297+ workspaceDir,
3298+ shadowDir,
3299+ log : ( message ) => this . providerRef . deref ( ) ?. log ( message ) ,
3300+ } ,
32633301 } )
32643302 }
32653303
@@ -3318,29 +3356,25 @@ export class Cline {
33183356 }
33193357 }
33203358
3321- public async checkpointSave ( ) {
3359+ public async checkpointSave ( { isFirst } : { isFirst : boolean } ) {
33223360 if ( ! this . checkpointsEnabled ) {
33233361 return
33243362 }
33253363
33263364 try {
3327- const isFirst = ! this . checkpointService
33283365 const service = await this . getCheckpointService ( )
3329- const commit = await service . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
3366+ const strategy = service . strategy
3367+ const version = service . version
33303368
3331- if ( commit ?. commit ) {
3332- await this . providerRef
3333- . deref ( )
3334- ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : service . currentCheckpoint } )
3369+ const commit = await service . saveCheckpoint ( `Task: ${ this . taskId } , Time: ${ Date . now ( ) } ` )
3370+ const fromHash = service . baseHash
3371+ const toHash = isFirst ? commit ?. commit || fromHash : commit ?. commit
33353372
3336- // Checkpoint metadata required by the UI.
3337- const checkpoint = {
3338- isFirst,
3339- from : service . baseCommitHash ,
3340- to : commit . commit ,
3341- }
3373+ if ( toHash ) {
3374+ await this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : toHash } )
33423375
3343- await this . say ( "checkpoint_saved" , commit . commit , undefined , undefined , checkpoint )
3376+ const checkpoint = { isFirst, from : fromHash , to : toHash , strategy, version }
3377+ await this . say ( "checkpoint_saved" , toHash , undefined , undefined , checkpoint )
33443378 }
33453379 } catch ( err ) {
33463380 this . providerRef . deref ( ) ?. log ( "[checkpointSave] disabling checkpoints for this task" )
@@ -3371,9 +3405,7 @@ export class Cline {
33713405 const service = await this . getCheckpointService ( )
33723406 await service . restoreCheckpoint ( commitHash )
33733407
3374- await this . providerRef
3375- . deref ( )
3376- ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : service . currentCheckpoint } )
3408+ await this . providerRef . deref ( ) ?. postMessageToWebview ( { type : "currentCheckpointUpdated" , text : commitHash } )
33773409
33783410 if ( mode === "restore" ) {
33793411 await this . overwriteApiConversationHistory (
0 commit comments