Skip to content

Commit bd0033d

Browse files
committed
Merge branch 'main' into cte/deep-research
2 parents 8c49ed5 + 97ed646 commit bd0033d

25 files changed

+1401
-633
lines changed

.changeset/orange-geese-provide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Allow users to clear out custom instructions for the built-in modes

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@
328328
"diff-match-patch": "^1.0.5",
329329
"fast-deep-equal": "^3.1.3",
330330
"fastest-levenshtein": "^1.0.16",
331+
"get-folder-size": "^5.0.0",
331332
"globby": "^14.0.2",
332333
"isbinaryfile": "^5.0.2",
333334
"js-tiktoken": "^1.0.18",
@@ -338,6 +339,7 @@
338339
"p-limit": "^6.2.0",
339340
"p-wait-for": "^5.0.2",
340341
"pdf-parse": "^1.1.1",
342+
"pretty-bytes": "^6.1.1",
341343
"puppeteer-chromium-resolver": "^23.0.0",
342344
"puppeteer-core": "^23.4.0",
343345
"react-hook-form": "^7.54.2",

src/__mocks__/get-folder-size.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = async function getFolderSize() {
2+
return {
3+
size: 1000,
4+
errors: [],
5+
}
6+
}

src/core/Cline.ts

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import delay from "delay"
66
import fs from "fs/promises"
77
import os from "os"
88
import pWaitFor from "p-wait-for"
9+
import getFolderSize from "get-folder-size"
910
import * as path from "path"
1011
import { serializeError } from "serialize-error"
1112
import * as vscode from "vscode"
1213
import { ApiHandler, SingleCompletionHandler, buildApiHandler } from "../api"
1314
import { ApiStream } from "../api/transform/stream"
1415
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
15-
import { CheckpointService } from "../services/checkpoints/CheckpointService"
16+
import { CheckpointService, CheckpointServiceFactory } from "../services/checkpoints"
1617
import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
1718
import {
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
@@ -3255,11 +3269,32 @@ export class Cline {
32553269
// Checkpoints
32563270

32573271
private async getCheckpointService() {
3272+
if (!this.checkpointsEnabled) {
3273+
throw new Error("Checkpoints are disabled")
3274+
}
3275+
32583276
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),
3277+
const workspaceDir = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
3278+
const shadowDir = this.providerRef.deref()?.context.globalStorageUri.fsPath
3279+
3280+
if (!workspaceDir) {
3281+
this.providerRef.deref()?.log("[getCheckpointService] workspace folder not found")
3282+
throw new Error("Workspace directory not found")
3283+
}
3284+
3285+
if (!shadowDir) {
3286+
this.providerRef.deref()?.log("[getCheckpointService] shadowDir not found")
3287+
throw new Error("Global storage directory not found")
3288+
}
3289+
3290+
this.checkpointService = await CheckpointServiceFactory.create({
3291+
strategy: "shadow",
3292+
options: {
3293+
taskId: this.taskId,
3294+
workspaceDir,
3295+
shadowDir,
3296+
log: (message) => this.providerRef.deref()?.log(message),
3297+
},
32633298
})
32643299
}
32653300

@@ -3318,29 +3353,25 @@ export class Cline {
33183353
}
33193354
}
33203355

3321-
public async checkpointSave() {
3356+
public async checkpointSave({ isFirst }: { isFirst: boolean }) {
33223357
if (!this.checkpointsEnabled) {
33233358
return
33243359
}
33253360

33263361
try {
3327-
const isFirst = !this.checkpointService
33283362
const service = await this.getCheckpointService()
3329-
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
3363+
const strategy = service.strategy
3364+
const version = service.version
33303365

3331-
if (commit?.commit) {
3332-
await this.providerRef
3333-
.deref()
3334-
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
3366+
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
3367+
const fromHash = service.baseHash
3368+
const toHash = isFirst ? commit?.commit || fromHash : commit?.commit
33353369

3336-
// Checkpoint metadata required by the UI.
3337-
const checkpoint = {
3338-
isFirst,
3339-
from: service.baseCommitHash,
3340-
to: commit.commit,
3341-
}
3370+
if (toHash) {
3371+
await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: toHash })
33423372

3343-
await this.say("checkpoint_saved", commit.commit, undefined, undefined, checkpoint)
3373+
const checkpoint = { isFirst, from: fromHash, to: toHash, strategy, version }
3374+
await this.say("checkpoint_saved", toHash, undefined, undefined, checkpoint)
33443375
}
33453376
} catch (err) {
33463377
this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task")
@@ -3371,9 +3402,7 @@ export class Cline {
33713402
const service = await this.getCheckpointService()
33723403
await service.restoreCheckpoint(commitHash)
33733404

3374-
await this.providerRef
3375-
.deref()
3376-
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
3405+
await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash })
33773406

33783407
if (mode === "restore") {
33793408
await this.overwriteApiConversationHistory(

src/core/webview/ClineProvider.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2321,35 +2321,55 @@ export class ClineProvider implements vscode.WebviewViewProvider {
23212321

23222322
await this.deleteTaskFromState(id)
23232323

2324-
// Delete the task files
2324+
// Delete the task files.
23252325
const apiConversationHistoryFileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
2326+
23262327
if (apiConversationHistoryFileExists) {
23272328
await fs.unlink(apiConversationHistoryFilePath)
23282329
}
2330+
23292331
const uiMessagesFileExists = await fileExistsAtPath(uiMessagesFilePath)
2332+
23302333
if (uiMessagesFileExists) {
23312334
await fs.unlink(uiMessagesFilePath)
23322335
}
2336+
23332337
const legacyMessagesFilePath = path.join(taskDirPath, "claude_messages.json")
2338+
23342339
if (await fileExistsAtPath(legacyMessagesFilePath)) {
23352340
await fs.unlink(legacyMessagesFilePath)
23362341
}
2337-
await fs.rmdir(taskDirPath) // succeeds if the dir is empty
23382342

23392343
const { checkpointsEnabled } = await this.getState()
23402344
const baseDir = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
2341-
const branch = `roo-code-checkpoints-${id}`
23422345

2346+
// Delete checkpoints branch.
23432347
if (checkpointsEnabled && baseDir) {
2348+
const branchSummary = await simpleGit(baseDir)
2349+
.branch(["-D", `roo-code-checkpoints-${id}`])
2350+
.catch(() => undefined)
2351+
2352+
if (branchSummary) {
2353+
console.log(`[deleteTaskWithId${id}] deleted checkpoints branch`)
2354+
}
2355+
}
2356+
2357+
// Delete checkpoints directory
2358+
const checkpointsDir = path.join(taskDirPath, "checkpoints")
2359+
2360+
if (await fileExistsAtPath(checkpointsDir)) {
23442361
try {
2345-
await simpleGit(baseDir).branch(["-D", branch])
2346-
console.log(`[deleteTaskWithId] Deleted branch ${branch}`)
2347-
} catch (err) {
2362+
await fs.rm(checkpointsDir, { recursive: true, force: true })
2363+
console.log(`[deleteTaskWithId${id}] removed checkpoints repo`)
2364+
} catch (error) {
23482365
console.error(
2349-
`[deleteTaskWithId] Error deleting branch ${branch}: ${err instanceof Error ? err.message : String(err)}`,
2366+
`[deleteTaskWithId${id}] failed to remove checkpoints repo: ${error instanceof Error ? error.message : String(error)}`,
23502367
)
23512368
}
23522369
}
2370+
2371+
// Succeeds if the dir is empty.
2372+
await fs.rmdir(taskDirPath)
23532373
}
23542374

23552375
async deleteTaskFromState(id: string) {
@@ -2417,6 +2437,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
24172437
alwaysAllowMcp: alwaysAllowMcp ?? false,
24182438
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
24192439
uriScheme: vscode.env.uriScheme,
2440+
currentTaskItem: this.cline?.taskId
2441+
? (taskHistory || []).find((item) => item.id === this.cline?.taskId)
2442+
: undefined,
24202443
clineMessages: this.cline?.clineMessages || [],
24212444
taskHistory: (taskHistory || [])
24222445
.filter((item: HistoryItem) => item.ts && item.task)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { LocalCheckpointService, LocalCheckpointServiceOptions } from "./LocalCheckpointService"
2+
import { ShadowCheckpointService, ShadowCheckpointServiceOptions } from "./ShadowCheckpointService"
3+
4+
export type CreateCheckpointServiceFactoryOptions =
5+
| {
6+
strategy: "local"
7+
options: LocalCheckpointServiceOptions
8+
}
9+
| {
10+
strategy: "shadow"
11+
options: ShadowCheckpointServiceOptions
12+
}
13+
14+
type CheckpointServiceType<T extends CreateCheckpointServiceFactoryOptions> = T extends { strategy: "local" }
15+
? LocalCheckpointService
16+
: T extends { strategy: "shadow" }
17+
? ShadowCheckpointService
18+
: never
19+
20+
export class CheckpointServiceFactory {
21+
public static create<T extends CreateCheckpointServiceFactoryOptions>(options: T): CheckpointServiceType<T> {
22+
switch (options.strategy) {
23+
case "local":
24+
return LocalCheckpointService.create(options.options) as any
25+
case "shadow":
26+
return ShadowCheckpointService.create(options.options) as any
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)