1- import fs from "fs/promises"
21import * as path from "path"
32import os from "os"
43import crypto from "crypto"
@@ -8,7 +7,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
87import cloneDeep from "clone-deep"
98import delay from "delay"
109import pWaitFor from "p-wait-for"
11- import getFolderSize from "get-folder-size"
1210import { serializeError } from "serialize-error"
1311import * as vscode from "vscode"
1412
@@ -58,7 +56,6 @@ import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
5856
5957// utils
6058import { calculateApiCostAnthropic } from "../utils/cost"
61- import { fileExistsAtPath } from "../utils/fs"
6259import { arePathsEqual , getWorkspacePath } from "../utils/path"
6360
6461// tools
@@ -93,6 +90,7 @@ import { truncateConversationIfNeeded } from "./sliding-window"
9390import { ClineProvider } from "./webview/ClineProvider"
9491import { validateToolUse } from "./mode-validator"
9592import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace"
93+ import { readApiMessages , saveApiMessages , readTaskMessages , saveTaskMessages } from "./task-persistence"
9694
9795type UserContent = Array < Anthropic . Messages . ContentBlockParam >
9896
@@ -164,6 +162,7 @@ export class Cline extends EventEmitter<ClineEvents> {
164162 consecutiveMistakeCountForApplyDiff : Map < string , number > = new Map ( )
165163 // Not private since it needs to be accessible by tools.
166164 providerRef : WeakRef < ClineProvider >
165+ private readonly globalStoragePath : string
167166 private abort : boolean = false
168167 didFinishAbortingStream = false
169168 abandoned = false
@@ -203,7 +202,6 @@ export class Cline extends EventEmitter<ClineEvents> {
203202 task,
204203 images,
205204 historyItem,
206- experiments,
207205 startTask = true ,
208206 rootTask,
209207 parentTask,
@@ -222,9 +220,11 @@ export class Cline extends EventEmitter<ClineEvents> {
222220
223221 this . rooIgnoreController = new RooIgnoreController ( this . cwd )
224222 this . fileContextTracker = new FileContextTracker ( provider , this . taskId )
223+
225224 this . rooIgnoreController . initialize ( ) . catch ( ( error ) => {
226225 console . error ( "Failed to initialize RooIgnoreController:" , error )
227226 } )
227+
228228 this . apiConfiguration = apiConfiguration
229229 this . api = buildApiHandler ( apiConfiguration )
230230 this . urlContentFetcher = new UrlContentFetcher ( provider . context )
@@ -234,6 +234,7 @@ export class Cline extends EventEmitter<ClineEvents> {
234234 this . fuzzyMatchThreshold = fuzzyMatchThreshold
235235 this . consecutiveMistakeLimit = consecutiveMistakeLimit
236236 this . providerRef = new WeakRef ( provider )
237+ this . globalStoragePath = provider . context . globalStorageUri . fsPath
237238 this . diffViewProvider = new DiffViewProvider ( this . cwd )
238239 this . enableCheckpoints = enableCheckpoints
239240
@@ -284,26 +285,8 @@ export class Cline extends EventEmitter<ClineEvents> {
284285
285286 // Storing task to disk for history
286287
287- private async ensureTaskDirectoryExists ( ) : Promise < string > {
288- const globalStoragePath = this . providerRef . deref ( ) ?. context . globalStorageUri . fsPath
289- if ( ! globalStoragePath ) {
290- throw new Error ( "Global storage uri is invalid" )
291- }
292-
293- // Use storagePathManager to retrieve the task storage directory
294- const { getTaskDirectoryPath } = await import ( "../shared/storagePathManager" )
295- return getTaskDirectoryPath ( globalStoragePath , this . taskId )
296- }
297-
298288 private async getSavedApiConversationHistory ( ) : Promise < Anthropic . MessageParam [ ] > {
299- const filePath = path . join ( await this . ensureTaskDirectoryExists ( ) , GlobalFileNames . apiConversationHistory )
300- const fileExists = await fileExistsAtPath ( filePath )
301-
302- if ( fileExists ) {
303- return JSON . parse ( await fs . readFile ( filePath , "utf8" ) )
304- }
305-
306- return [ ]
289+ return readApiMessages ( { taskId : this . taskId , globalStoragePath : this . globalStoragePath } )
307290 }
308291
309292 private async addToApiConversationHistory ( message : Anthropic . MessageParam ) {
@@ -319,30 +302,19 @@ export class Cline extends EventEmitter<ClineEvents> {
319302
320303 private async saveApiConversationHistory ( ) {
321304 try {
322- const filePath = path . join ( await this . ensureTaskDirectoryExists ( ) , GlobalFileNames . apiConversationHistory )
323- await fs . writeFile ( filePath , JSON . stringify ( this . apiConversationHistory ) )
305+ await saveApiMessages ( {
306+ messages : this . apiConversationHistory ,
307+ taskId : this . taskId ,
308+ globalStoragePath : this . globalStoragePath ,
309+ } )
324310 } catch ( error ) {
325311 // in the off chance this fails, we don't want to stop the task
326312 console . error ( "Failed to save API conversation history:" , error )
327313 }
328314 }
329315
330316 private async getSavedClineMessages ( ) : Promise < ClineMessage [ ] > {
331- const filePath = path . join ( await this . ensureTaskDirectoryExists ( ) , GlobalFileNames . uiMessages )
332-
333- if ( await fileExistsAtPath ( filePath ) ) {
334- return JSON . parse ( await fs . readFile ( filePath , "utf8" ) )
335- } else {
336- // check old location
337- const oldPath = path . join ( await this . ensureTaskDirectoryExists ( ) , "claude_messages.json" )
338- if ( await fileExistsAtPath ( oldPath ) ) {
339- const data = JSON . parse ( await fs . readFile ( oldPath , "utf8" ) )
340- await fs . unlink ( oldPath ) // remove old file
341- return data
342- }
343- }
344-
345- return [ ]
317+ return readTaskMessages ( { taskId : this . taskId , globalStoragePath : this . globalStoragePath } )
346318 }
347319
348320 private async addToClineMessages ( message : ClineMessage ) {
@@ -364,46 +336,17 @@ export class Cline extends EventEmitter<ClineEvents> {
364336
365337 private async saveClineMessages ( ) {
366338 try {
367- const taskDir = await this . ensureTaskDirectoryExists ( )
368- const filePath = path . join ( taskDir , GlobalFileNames . uiMessages )
369- await fs . writeFile ( filePath , JSON . stringify ( this . clineMessages ) )
339+ const { historyItem, tokenUsage } = await saveTaskMessages ( {
340+ messages : this . clineMessages ,
341+ taskId : this . taskId ,
342+ taskNumber : this . taskNumber ,
343+ globalStoragePath : this . globalStoragePath ,
344+ workspace : this . cwd ,
345+ } )
370346
371- const tokenUsage = this . getTokenUsage ( )
372347 this . emit ( "taskTokenUsageUpdated" , this . taskId , tokenUsage )
373348
374- const taskMessage = this . clineMessages [ 0 ] // First message is always the task say
375-
376- const lastRelevantMessage =
377- this . clineMessages [
378- findLastIndex (
379- this . clineMessages ,
380- ( m ) => ! ( m . ask === "resume_task" || m . ask === "resume_completed_task" ) ,
381- )
382- ]
383-
384- let taskDirSize = 0
385-
386- try {
387- taskDirSize = await getFolderSize . loose ( taskDir )
388- } catch ( err ) {
389- console . error (
390- `[saveClineMessages] failed to get task directory size (${ taskDir } ): ${ err instanceof Error ? err . message : String ( err ) } ` ,
391- )
392- }
393-
394- await this . providerRef . deref ( ) ?. updateTaskHistory ( {
395- id : this . taskId ,
396- number : this . taskNumber ,
397- ts : lastRelevantMessage . ts ,
398- task : taskMessage . text ?? "" ,
399- tokensIn : tokenUsage . totalTokensIn ,
400- tokensOut : tokenUsage . totalTokensOut ,
401- cacheWrites : tokenUsage . totalCacheWrites ,
402- cacheReads : tokenUsage . totalCacheReads ,
403- totalCost : tokenUsage . totalCost ,
404- size : taskDirSize ,
405- workspace : this . cwd ,
406- } )
349+ await this . providerRef . deref ( ) ?. updateTaskHistory ( historyItem )
407350 } catch ( error ) {
408351 console . error ( "Failed to save cline messages:" , error )
409352 }
@@ -853,7 +796,7 @@ export class Cline extends EventEmitter<ClineEvents> {
853796 }
854797
855798 const wasRecent = lastClineMessage ?. ts && Date . now ( ) - lastClineMessage . ts < 30_000
856-
799+
857800 newUserContent . push ( {
858801 type : "text" ,
859802 text :
0 commit comments