Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 2766839

Browse files
committed
feat: add restore to task start button
Adds a button in the task header that allows users to restore the workspace to its initial state when the task was created. This uses the baseHash checkpoint from the shadow git repository. - Add checkpointRestoreToBase() function in checkpoints/index.ts - Add restoreToTaskStart message handler in webviewMessageHandler.ts - Add RestoreTaskDialog component with confirmation dialog - Add restore button to TaskActions (visible when checkpoints enabled) - Add translations for all 17 supported locales - Add 4 unit tests for the new checkpoint restore function
1 parent d7fa963 commit 2766839

42 files changed

Lines changed: 332 additions & 21 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/types/src/vscode-extension-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ export interface WebviewMessage {
517517
| "openCustomModesSettings"
518518
| "checkpointDiff"
519519
| "checkpointRestore"
520+
| "restoreToTaskStart"
520521
| "deleteMcpServer"
521522
| "codebaseIndexEnabled"
522523
| "telemetrySetting"

src/core/checkpoints/__tests__/checkpoint.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest"
22
import { Task } from "../../task/Task"
33
import { ClineProvider } from "../../webview/ClineProvider"
4-
import { checkpointSave, checkpointRestore, checkpointDiff, getCheckpointService } from "../index"
4+
import {
5+
checkpointSave,
6+
checkpointRestore,
7+
checkpointRestoreToBase,
8+
checkpointDiff,
9+
getCheckpointService,
10+
} from "../index"
511
import { MessageManager } from "../../message-manager"
612
import * as vscode from "vscode"
713

@@ -296,6 +302,56 @@ describe("Checkpoint functionality", () => {
296302
})
297303
})
298304

305+
describe("checkpointRestoreToBase", () => {
306+
beforeEach(() => {
307+
mockCheckpointService.baseHash = "initial-commit-hash"
308+
})
309+
310+
it("should restore to base hash successfully", async () => {
311+
const result = await checkpointRestoreToBase(mockTask)
312+
313+
expect(result).toBe(true)
314+
expect(mockCheckpointService.restoreCheckpoint).toHaveBeenCalledWith("initial-commit-hash")
315+
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
316+
type: "currentCheckpointUpdated",
317+
text: "initial-commit-hash",
318+
})
319+
expect(mockProvider.cancelTask).toHaveBeenCalled()
320+
})
321+
322+
it("should return false if no checkpoint service available", async () => {
323+
mockTask.checkpointService = undefined
324+
mockTask.enableCheckpoints = false
325+
326+
const result = await checkpointRestoreToBase(mockTask)
327+
328+
expect(result).toBe(false)
329+
expect(mockCheckpointService.restoreCheckpoint).not.toHaveBeenCalled()
330+
})
331+
332+
it("should return false if no baseHash available", async () => {
333+
mockCheckpointService.baseHash = undefined
334+
335+
const result = await checkpointRestoreToBase(mockTask)
336+
337+
expect(result).toBe(false)
338+
expect(mockCheckpointService.restoreCheckpoint).not.toHaveBeenCalled()
339+
expect(mockProvider.log).toHaveBeenCalledWith("[checkpointRestoreToBase] no baseHash available")
340+
})
341+
342+
it("should disable checkpoints on error", async () => {
343+
mockCheckpointService.restoreCheckpoint.mockRejectedValue(new Error("Restore failed"))
344+
345+
const result = await checkpointRestoreToBase(mockTask)
346+
347+
expect(result).toBe(false)
348+
expect(mockTask.enableCheckpoints).toBe(false)
349+
expect(mockProvider.log).toHaveBeenCalledWith(
350+
"[checkpointRestoreToBase] disabling checkpoints for this task",
351+
)
352+
})
353+
})
354+
299355
describe("checkpointDiff", () => {
300356
beforeEach(() => {
301357
mockTask.clineMessages = [

src/core/checkpoints/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,46 @@ export async function checkpointRestore(
301301
}
302302
}
303303

304+
/**
305+
* Restore the workspace to its initial state (baseHash) - the state when the shadow git repo was initialized.
306+
* This is a simpler version of checkpointRestore that doesn't need to rewind messages since we're
307+
* restoring to the very beginning of the task.
308+
* @returns true if restoration was successful, false otherwise
309+
*/
310+
export async function checkpointRestoreToBase(task: Task): Promise<boolean> {
311+
const service = await getCheckpointService(task)
312+
313+
if (!service) {
314+
return false
315+
}
316+
317+
const baseHash = service.baseHash
318+
319+
if (!baseHash) {
320+
const provider = task.providerRef.deref()
321+
provider?.log("[checkpointRestoreToBase] no baseHash available")
322+
return false
323+
}
324+
325+
const provider = task.providerRef.deref()
326+
327+
try {
328+
await service.restoreCheckpoint(baseHash)
329+
TelemetryService.instance.captureCheckpointRestored(task.taskId)
330+
await provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: baseHash })
331+
332+
// Cancel the task to reinitialize with the restored state
333+
// This follows the same pattern as checkpointRestore
334+
provider?.cancelTask()
335+
336+
return true
337+
} catch (err) {
338+
provider?.log("[checkpointRestoreToBase] disabling checkpoints for this task")
339+
task.enableCheckpoints = false
340+
return false
341+
}
342+
}
343+
304344
export type CheckpointDiffOptions = {
305345
ts?: number
306346
previousCommitHash?: string

src/core/webview/webviewMessageHandler.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,42 @@ export const webviewMessageHandler = async (
12071207

12081208
break
12091209
}
1210+
case "restoreToTaskStart": {
1211+
const currentTask = provider.getCurrentTask()
1212+
1213+
if (!currentTask) {
1214+
vscode.window.showErrorMessage(t("common:errors.checkpoint_no_active_task"))
1215+
break
1216+
}
1217+
1218+
if (!currentTask.enableCheckpoints) {
1219+
vscode.window.showErrorMessage(t("common:errors.checkpoint_not_enabled"))
1220+
break
1221+
}
1222+
1223+
// Cancel the current task first
1224+
await provider.cancelTask()
1225+
1226+
try {
1227+
await pWaitFor(() => provider.getCurrentTask()?.isInitialized === true, { timeout: 3_000 })
1228+
} catch (error) {
1229+
vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
1230+
break
1231+
}
1232+
1233+
try {
1234+
const { checkpointRestoreToBase } = await import("../checkpoints")
1235+
const success = await checkpointRestoreToBase(provider.getCurrentTask()!)
1236+
1237+
if (!success) {
1238+
vscode.window.showErrorMessage(t("common:errors.checkpoint_restore_base_failed"))
1239+
}
1240+
} catch (error) {
1241+
vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
1242+
}
1243+
1244+
break
1245+
}
12101246
case "cancelTask":
12111247
await provider.cancelTask()
12121248
break

src/i18n/locales/ca/common.json

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

src/i18n/locales/de/common.json

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

src/i18n/locales/en/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
"could_not_open_file_generic": "Could not open file!",
2929
"checkpoint_timeout": "Timed out when attempting to restore checkpoint.",
3030
"checkpoint_failed": "Failed to restore checkpoint.",
31+
"checkpoint_no_active_task": "No active task to restore.",
32+
"checkpoint_not_enabled": "Checkpoints are not enabled for this task.",
33+
"checkpoint_restore_base_failed": "Failed to restore workspace to initial state.",
3134
"git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.",
3235
"checkpoint_no_first": "No first checkpoint to compare.",
3336
"checkpoint_no_previous": "No previous checkpoint to compare.",

src/i18n/locales/es/common.json

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

src/i18n/locales/fr/common.json

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

src/i18n/locales/hi/common.json

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

0 commit comments

Comments
 (0)