Skip to content

Commit 939e4fc

Browse files
authored
Merge pull request #1346 from RooVetGit/cte/async-checkpoints
Async checkpoints
2 parents 464d440 + e227a9e commit 939e4fc

File tree

10 files changed

+387
-961
lines changed

10 files changed

+387
-961
lines changed

src/core/Cline.ts

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import getFolderSize from "get-folder-size"
1010
import * as path from "path"
1111
import { serializeError } from "serialize-error"
1212
import * as vscode from "vscode"
13-
import { ApiHandler, SingleCompletionHandler, buildApiHandler } from "../api"
13+
import { ApiHandler, buildApiHandler } from "../api"
1414
import { ApiStream } from "../api/transform/stream"
1515
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
16-
import { CheckpointService, CheckpointServiceFactory } from "../services/checkpoints"
16+
import { ShadowCheckpointService } from "../services/checkpoints/ShadowCheckpointService"
1717
import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
1818
import {
1919
extractTextFromFile,
@@ -116,7 +116,7 @@ export class Cline {
116116

117117
// checkpoints
118118
enableCheckpoints: boolean = false
119-
private checkpointService?: CheckpointService
119+
private checkpointService?: ShadowCheckpointService
120120

121121
// streaming
122122
isWaitingForFirstChunk = false
@@ -747,8 +747,11 @@ export class Cline {
747747
}
748748

749749
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
750+
this.initializeCheckpoints()
751+
750752
let nextUserContent = userContent
751753
let includeFileDetails = true
754+
752755
while (!this.abort) {
753756
const didEndLoop = await this.recursivelyMakeClineRequests(nextUserContent, includeFileDetails)
754757
includeFileDetails = false // we only need file details the first time
@@ -2773,7 +2776,7 @@ export class Cline {
27732776
}
27742777

27752778
if (isCheckpointPossible) {
2776-
await this.checkpointSave({ isFirst: false })
2779+
this.checkpointSave()
27772780
}
27782781

27792782
/*
@@ -2839,13 +2842,6 @@ export class Cline {
28392842
// get previous api req's index to check token usage and determine if we need to truncate conversation history
28402843
const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
28412844

2842-
// Save checkpoint if this is the first API request.
2843-
const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
2844-
2845-
if (isFirstRequest) {
2846-
await this.checkpointSave({ isFirst: true })
2847-
}
2848-
28492845
// 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
28502846
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
28512847
await this.say(
@@ -3356,37 +3352,88 @@ export class Cline {
33563352

33573353
// Checkpoints
33583354

3359-
private async getCheckpointService() {
3355+
private async initializeCheckpoints() {
33603356
if (!this.enableCheckpoints) {
3361-
throw new Error("Checkpoints are disabled")
3357+
return
33623358
}
33633359

3364-
if (!this.checkpointService) {
3360+
const log = (message: string) => {
3361+
console.log(message)
3362+
3363+
try {
3364+
this.providerRef.deref()?.log(message)
3365+
} catch (err) {
3366+
// NO-OP
3367+
}
3368+
}
3369+
3370+
try {
3371+
if (this.checkpointService) {
3372+
log("[Cline#initializeCheckpoints] checkpointService already initialized")
3373+
return
3374+
}
3375+
33653376
const workspaceDir = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
3366-
const shadowDir = this.providerRef.deref()?.context.globalStorageUri.fsPath
33673377

33683378
if (!workspaceDir) {
3369-
this.providerRef.deref()?.log("[getCheckpointService] workspace folder not found")
3370-
throw new Error("Workspace directory not found")
3379+
log("[Cline#initializeCheckpoints] workspace folder not found, disabling checkpoints")
3380+
this.enableCheckpoints = false
3381+
return
33713382
}
33723383

3384+
const shadowDir = this.providerRef.deref()?.context.globalStorageUri.fsPath
3385+
33733386
if (!shadowDir) {
3374-
this.providerRef.deref()?.log("[getCheckpointService] shadowDir not found")
3375-
throw new Error("Global storage directory not found")
3387+
log("[Cline#initializeCheckpoints] shadowDir not found, disabling checkpoints")
3388+
this.enableCheckpoints = false
3389+
return
3390+
}
3391+
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
33763398
}
33773399

3378-
this.checkpointService = await CheckpointServiceFactory.create({
3379-
strategy: "shadow",
3380-
options: {
3381-
taskId: this.taskId,
3382-
workspaceDir,
3383-
shadowDir,
3384-
log: (message) => this.providerRef.deref()?.log(message),
3385-
},
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+
}
3409+
3410+
this.checkpointService = service
3411+
this.checkpointSave()
3412+
} catch (err) {
3413+
log("[Cline#initializeCheckpoints] caught error in on('initialize'), disabling checkpoints")
3414+
this.enableCheckpoints = false
3415+
}
33863416
})
3387-
}
33883417

3389-
return this.checkpointService
3418+
service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to }) => {
3419+
try {
3420+
log(`[Cline#initializeCheckpoints] ${isFirst ? "initial" : "incremental"} checkpoint saved: ${to}`)
3421+
this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: to })
3422+
3423+
this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((e) =>
3424+
console.error("Error saving checkpoint message:", e),
3425+
)
3426+
} catch (err) {
3427+
log("[Cline#initializeCheckpoints] caught error in on('checkpoint'), disabling checkpoints")
3428+
this.enableCheckpoints = false
3429+
}
3430+
})
3431+
3432+
service.initShadowGit()
3433+
} catch (err) {
3434+
log("[Cline#initializeCheckpoints] caught error in initializeCheckpoints(), disabling checkpoints")
3435+
this.enableCheckpoints = false
3436+
}
33903437
}
33913438

33923439
public async checkpointDiff({
@@ -3398,7 +3445,7 @@ export class Cline {
33983445
commitHash: string
33993446
mode: "full" | "checkpoint"
34003447
}) {
3401-
if (!this.enableCheckpoints) {
3448+
if (!this.checkpointService || !this.enableCheckpoints) {
34023449
return
34033450
}
34043451

@@ -3414,8 +3461,7 @@ export class Cline {
34143461
}
34153462

34163463
try {
3417-
const service = await this.getCheckpointService()
3418-
const changes = await service.getDiff({ from: previousCommitHash, to: commitHash })
3464+
const changes = await this.checkpointService.getDiff({ from: previousCommitHash, to: commitHash })
34193465

34203466
if (!changes?.length) {
34213467
vscode.window.showInformationMessage("No changes found.")
@@ -3441,30 +3487,13 @@ export class Cline {
34413487
}
34423488
}
34433489

3444-
public async checkpointSave({ isFirst }: { isFirst: boolean }) {
3445-
if (!this.enableCheckpoints) {
3490+
public checkpointSave() {
3491+
if (!this.checkpointService || !this.enableCheckpoints) {
34463492
return
34473493
}
34483494

3449-
try {
3450-
const service = await this.getCheckpointService()
3451-
const strategy = service.strategy
3452-
const version = service.version
3453-
3454-
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
3455-
const fromHash = service.baseHash
3456-
const toHash = isFirst ? commit?.commit || fromHash : commit?.commit
3457-
3458-
if (toHash) {
3459-
await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: toHash })
3460-
3461-
const checkpoint = { isFirst, from: fromHash, to: toHash, strategy, version }
3462-
await this.say("checkpoint_saved", toHash, undefined, undefined, checkpoint)
3463-
}
3464-
} catch (err) {
3465-
this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task")
3466-
this.enableCheckpoints = false
3467-
}
3495+
// Start the checkpoint process in the background.
3496+
this.checkpointService.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
34683497
}
34693498

34703499
public async checkpointRestore({
@@ -3476,7 +3505,7 @@ export class Cline {
34763505
commitHash: string
34773506
mode: "preview" | "restore"
34783507
}) {
3479-
if (!this.enableCheckpoints) {
3508+
if (!this.checkpointService || !this.enableCheckpoints) {
34803509
return
34813510
}
34823511

@@ -3487,8 +3516,7 @@ export class Cline {
34873516
}
34883517

34893518
try {
3490-
const service = await this.getCheckpointService()
3491-
await service.restoreCheckpoint(commitHash)
3519+
await this.checkpointService.restoreCheckpoint(commitHash)
34923520

34933521
await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash })
34943522

src/services/checkpoints/CheckpointServiceFactory.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)