Skip to content

Commit 9b2f4ab

Browse files
committed
feat: add custom checkpoint functionality
- Add saveCustomCheckpoint command to VSCode command system - Update checkpoint system to accept custom messages - Add UI button in TaskActions for creating custom checkpoints - Add webview message handler for custom checkpoint requests - Add translation strings for custom checkpoint UI - Update Task and checkpoint services to support custom messages Fixes #8350
1 parent a57528d commit 9b2f4ab

File tree

10 files changed

+109
-9
lines changed

10 files changed

+109
-9
lines changed

packages/types/src/message.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export const clineSays = [
160160
"mcp_server_response",
161161
"subtask_result",
162162
"checkpoint_saved",
163+
"checkpoint_saved_custom",
163164
"rooignore_error",
164165
"diff_error",
165166
"condense_context",

packages/types/src/vscode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const commandIds = [
5454
"acceptInput",
5555
"focusPanel",
5656
"toggleAutoApprove",
57+
"saveCustomCheckpoint",
5758
] as const
5859

5960
export type CommandId = (typeof commandIds)[number]

src/activate/registerCommands.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,42 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
233233
action: "toggleAutoApprove",
234234
})
235235
},
236+
saveCustomCheckpoint: async () => {
237+
const visibleProvider = getVisibleProviderOrLog(outputChannel)
238+
239+
if (!visibleProvider) {
240+
return
241+
}
242+
243+
// Get the current task
244+
const currentTask = visibleProvider.getCurrentTask()
245+
if (!currentTask) {
246+
vscode.window.showInformationMessage(t("common:errors.no_active_task"))
247+
return
248+
}
249+
250+
// Check if checkpoints are enabled
251+
if (!currentTask.enableCheckpoints) {
252+
vscode.window.showInformationMessage(t("common:errors.checkpoints_disabled"))
253+
return
254+
}
255+
256+
// Save a custom checkpoint with a user-provided message
257+
const message = await vscode.window.showInputBox({
258+
prompt: t("common:checkpoint.custom_prompt"),
259+
placeHolder: t("common:checkpoint.custom_placeholder"),
260+
value: t("common:checkpoint.custom_default"),
261+
})
262+
263+
if (message !== undefined) {
264+
// Force save checkpoint even if no file changes with custom message
265+
await currentTask.checkpointSave(true, false, message)
266+
await currentTask.say("checkpoint_saved_custom", message, undefined, false, undefined, undefined, {
267+
isNonInteractive: true,
268+
})
269+
vscode.window.showInformationMessage(t("common:checkpoint.custom_saved"))
270+
}
271+
},
236272
})
237273

238274
export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {

src/core/checkpoints/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ async function checkGitInstallation(
177177
}
178178
}
179179

180-
export async function checkpointSave(task: Task, force = false, suppressMessage = false) {
180+
export async function checkpointSave(task: Task, force = false, suppressMessage = false, customMessage?: string) {
181181
const service = await getCheckpointService(task)
182182

183183
if (!service) {
@@ -186,13 +186,14 @@ export async function checkpointSave(task: Task, force = false, suppressMessage
186186

187187
TelemetryService.instance.captureCheckpointCreated(task.taskId)
188188

189+
// Use custom message if provided, otherwise use default format
190+
const message = customMessage || `Task: ${task.taskId}, Time: ${Date.now()}`
191+
189192
// Start the checkpoint process in the background.
190-
return service
191-
.saveCheckpoint(`Task: ${task.taskId}, Time: ${Date.now()}`, { allowEmpty: force, suppressMessage })
192-
.catch((err) => {
193-
console.error("[Task#checkpointSave] caught unexpected error, disabling checkpoints", err)
194-
task.enableCheckpoints = false
195-
})
193+
return service.saveCheckpoint(message, { allowEmpty: force, suppressMessage }).catch((err) => {
194+
console.error("[Task#checkpointSave] caught unexpected error, disabling checkpoints", err)
195+
task.enableCheckpoints = false
196+
})
196197
}
197198

198199
export type CheckpointRestoreOptions = {

src/core/task/Task.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2795,8 +2795,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27952795

27962796
// Checkpoints
27972797

2798-
public async checkpointSave(force: boolean = false, suppressMessage: boolean = false) {
2799-
return checkpointSave(this, force, suppressMessage)
2798+
public async checkpointSave(force: boolean = false, suppressMessage: boolean = false, customMessage?: string) {
2799+
return checkpointSave(this, force, suppressMessage, customMessage)
28002800
}
28012801

28022802
public async checkpointRestore(options: CheckpointRestoreOptions) {

src/core/webview/webviewMessageHandler.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,35 @@ export const webviewMessageHandler = async (
10001000

10011001
break
10021002
}
1003+
case "saveCustomCheckpoint": {
1004+
const currentTask = provider.getCurrentTask()
1005+
if (!currentTask) {
1006+
vscode.window.showErrorMessage(t("common:checkpoint.no_active_task"))
1007+
break
1008+
}
1009+
1010+
if (!getGlobalState("enableCheckpoints")) {
1011+
vscode.window.showErrorMessage(t("common:checkpoint.checkpoints_disabled"))
1012+
break
1013+
}
1014+
1015+
// Use the message text as the custom checkpoint message
1016+
const customMessage = message.text || t("common:checkpoint.custom_checkpoint_default")
1017+
1018+
try {
1019+
// Force checkpoint creation with custom message
1020+
// Parameters: force=true (create even without changes), suppressMessage=false (show in UI), customMessage
1021+
await currentTask.checkpointSave(true, false, customMessage)
1022+
vscode.window.showInformationMessage(t("common:checkpoint.custom_checkpoint_saved"))
1023+
} catch (error) {
1024+
vscode.window.showErrorMessage(
1025+
t("common:checkpoint.custom_checkpoint_failed", {
1026+
error: error instanceof Error ? error.message : String(error),
1027+
}),
1028+
)
1029+
}
1030+
break
1031+
}
10031032
case "cancelTask":
10041033
await provider.cancelTask()
10051034
break

src/i18n/locales/en/common.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
"checkpoint_failed": "Failed to restore checkpoint.",
3131
"git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.",
3232
"nested_git_repos_warning": "Checkpoints are disabled because a nested git repository was detected at: {{path}}. To use checkpoints, please remove or relocate this nested git repository.",
33+
"no_active_task": "No active task. Please start a task first.",
34+
"checkpoints_disabled": "Checkpoints are disabled for this task. Please enable checkpoints in settings.",
3335
"no_workspace": "Please open a project folder first",
3436
"update_support_prompt": "Failed to update support prompt",
3537
"reset_support_prompt": "Failed to reset support prompt",
@@ -223,5 +225,14 @@
223225
"docsLink": {
224226
"label": "Docs",
225227
"url": "https://docs.roocode.com"
228+
},
229+
"checkpoint": {
230+
"custom_prompt": "Enter a description for this checkpoint",
231+
"custom_placeholder": "e.g., 'Before refactoring authentication'",
232+
"custom_default": "Manual checkpoint",
233+
"custom_saved": "Custom checkpoint saved successfully",
234+
"custom_checkpoint_default": "Manual checkpoint",
235+
"custom_checkpoint_saved": "Custom checkpoint saved successfully",
236+
"custom_checkpoint_failed": "Failed to save custom checkpoint: {{error}}"
226237
}
227238
}

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export interface WebviewMessage {
156156
| "openCustomModesSettings"
157157
| "checkpointDiff"
158158
| "checkpointRestore"
159+
| "saveCustomCheckpoint"
159160
| "deleteMcpServer"
160161
| "maxOpenTabsContext"
161162
| "maxWorkspaceFiles"

webview-ui/src/components/chat/TaskActions.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { HistoryItem } from "@roo-code/types"
55

66
import { vscode } from "@/utils/vscode"
77
import { useCopyToClipboard } from "@/utils/clipboard"
8+
import { useExtensionState } from "@/context/ExtensionStateContext"
89

910
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
1011
import { IconButton } from "./IconButton"
@@ -20,9 +21,25 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
2021
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
2122
const { t } = useTranslation()
2223
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
24+
const { enableCheckpoints } = useExtensionState()
25+
26+
const handleCustomCheckpoint = () => {
27+
const message = prompt(t("chat:checkpoint.custom_prompt"), t("chat:checkpoint.custom_default"))
28+
if (message !== null) {
29+
vscode.postMessage({ type: "saveCustomCheckpoint", text: message })
30+
}
31+
}
2332

2433
return (
2534
<div className="flex flex-row items-center">
35+
{enableCheckpoints && (
36+
<IconButton
37+
iconClass="codicon-save"
38+
title={t("chat:checkpoint.save_custom")}
39+
disabled={buttonsDisabled}
40+
onClick={handleCustomCheckpoint}
41+
/>
42+
)}
2643
<IconButton
2744
iconClass="codicon-desktop-download"
2845
title={t("chat:task.export")}

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@
152152
"checkpoint": {
153153
"regular": "Checkpoint",
154154
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> and restart your task.",
155+
"save_custom": "Save Custom Checkpoint",
156+
"custom_prompt": "Enter a description for this checkpoint",
157+
"custom_default": "Manual checkpoint",
155158
"menu": {
156159
"viewDiff": "View Diff",
157160
"restore": "Restore Checkpoint",

0 commit comments

Comments
 (0)