diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 77c055c6e1..1cdfce7b77 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -160,6 +160,7 @@ export const clineSays = [ "mcp_server_response", "subtask_result", "checkpoint_saved", + "checkpoint_saved_custom", "rooignore_error", "diff_error", "condense_context", diff --git a/packages/types/src/vscode.ts b/packages/types/src/vscode.ts index d22ebdab22..7fc40ffcb1 100644 --- a/packages/types/src/vscode.ts +++ b/packages/types/src/vscode.ts @@ -54,6 +54,7 @@ export const commandIds = [ "acceptInput", "focusPanel", "toggleAutoApprove", + "saveCustomCheckpoint", ] as const export type CommandId = (typeof commandIds)[number] diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index 41c127333d..1ea4403849 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -233,6 +233,42 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt action: "toggleAutoApprove", }) }, + saveCustomCheckpoint: async () => { + const visibleProvider = getVisibleProviderOrLog(outputChannel) + + if (!visibleProvider) { + return + } + + // Get the current task + const currentTask = visibleProvider.getCurrentTask() + if (!currentTask) { + vscode.window.showInformationMessage(t("common:errors.no_active_task")) + return + } + + // Check if checkpoints are enabled + if (!currentTask.enableCheckpoints) { + vscode.window.showInformationMessage(t("common:errors.checkpoints_disabled")) + return + } + + // Save a custom checkpoint with a user-provided message + const message = await vscode.window.showInputBox({ + prompt: t("common:checkpoint.custom_prompt"), + placeHolder: t("common:checkpoint.custom_placeholder"), + value: t("common:checkpoint.custom_default"), + }) + + if (message !== undefined) { + // Force save checkpoint even if no file changes with custom message + await currentTask.checkpointSave(true, false, message) + await currentTask.say("checkpoint_saved_custom", message, undefined, false, undefined, undefined, { + isNonInteractive: true, + }) + vscode.window.showInformationMessage(t("common:checkpoint.custom_saved")) + } + }, }) export const openClineInNewTab = async ({ context, outputChannel }: Omit) => { diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts index bc842c9f18..89a48c3316 100644 --- a/src/core/checkpoints/index.ts +++ b/src/core/checkpoints/index.ts @@ -177,7 +177,7 @@ async function checkGitInstallation( } } -export async function checkpointSave(task: Task, force = false, suppressMessage = false) { +export async function checkpointSave(task: Task, force = false, suppressMessage = false, customMessage?: string) { const service = await getCheckpointService(task) if (!service) { @@ -186,13 +186,14 @@ export async function checkpointSave(task: Task, force = false, suppressMessage TelemetryService.instance.captureCheckpointCreated(task.taskId) + // Use custom message if provided, otherwise use default format + const message = customMessage || `Task: ${task.taskId}, Time: ${Date.now()}` + // Start the checkpoint process in the background. - return service - .saveCheckpoint(`Task: ${task.taskId}, Time: ${Date.now()}`, { allowEmpty: force, suppressMessage }) - .catch((err) => { - console.error("[Task#checkpointSave] caught unexpected error, disabling checkpoints", err) - task.enableCheckpoints = false - }) + return service.saveCheckpoint(message, { allowEmpty: force, suppressMessage }).catch((err) => { + console.error("[Task#checkpointSave] caught unexpected error, disabling checkpoints", err) + task.enableCheckpoints = false + }) } export type CheckpointRestoreOptions = { diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 2dd9e55c0b..24bdb4a666 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2795,8 +2795,8 @@ export class Task extends EventEmitter implements TaskLike { // Checkpoints - public async checkpointSave(force: boolean = false, suppressMessage: boolean = false) { - return checkpointSave(this, force, suppressMessage) + public async checkpointSave(force: boolean = false, suppressMessage: boolean = false, customMessage?: string) { + return checkpointSave(this, force, suppressMessage, customMessage) } public async checkpointRestore(options: CheckpointRestoreOptions) { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index af5f9925c3..fb4d97eb6b 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1000,6 +1000,35 @@ export const webviewMessageHandler = async ( break } + case "saveCustomCheckpoint": { + const currentTask = provider.getCurrentTask() + if (!currentTask) { + vscode.window.showErrorMessage(t("common:checkpoint.no_active_task")) + break + } + + if (!getGlobalState("enableCheckpoints")) { + vscode.window.showErrorMessage(t("common:checkpoint.checkpoints_disabled")) + break + } + + // Use the message text as the custom checkpoint message + const customMessage = message.text || t("common:checkpoint.custom_checkpoint_default") + + try { + // Force checkpoint creation with custom message + // Parameters: force=true (create even without changes), suppressMessage=false (show in UI), customMessage + await currentTask.checkpointSave(true, false, customMessage) + vscode.window.showInformationMessage(t("common:checkpoint.custom_checkpoint_saved")) + } catch (error) { + vscode.window.showErrorMessage( + t("common:checkpoint.custom_checkpoint_failed", { + error: error instanceof Error ? error.message : String(error), + }), + ) + } + break + } case "cancelTask": await provider.cancelTask() break diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 3a613cc1c2..970ccd05e6 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -30,6 +30,8 @@ "checkpoint_failed": "Failed to restore checkpoint.", "git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.", "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.", + "no_active_task": "No active task. Please start a task first.", + "checkpoints_disabled": "Checkpoints are disabled for this task. Please enable checkpoints in settings.", "no_workspace": "Please open a project folder first", "update_support_prompt": "Failed to update support prompt", "reset_support_prompt": "Failed to reset support prompt", @@ -223,5 +225,14 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "checkpoint": { + "custom_prompt": "Enter a description for this checkpoint", + "custom_placeholder": "e.g., 'Before refactoring authentication'", + "custom_default": "Manual checkpoint", + "custom_saved": "Custom checkpoint saved successfully", + "custom_checkpoint_default": "Manual checkpoint", + "custom_checkpoint_saved": "Custom checkpoint saved successfully", + "custom_checkpoint_failed": "Failed to save custom checkpoint: {{error}}" } } diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d43a2fce04..feee84b9d2 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -156,6 +156,7 @@ export interface WebviewMessage { | "openCustomModesSettings" | "checkpointDiff" | "checkpointRestore" + | "saveCustomCheckpoint" | "deleteMcpServer" | "maxOpenTabsContext" | "maxWorkspaceFiles" diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index a6954c5ef3..1cab0bae33 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -5,6 +5,7 @@ import type { HistoryItem } from "@roo-code/types" import { vscode } from "@/utils/vscode" import { useCopyToClipboard } from "@/utils/clipboard" +import { useExtensionState } from "@/context/ExtensionStateContext" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" import { IconButton } from "./IconButton" @@ -20,9 +21,25 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) const { t } = useTranslation() const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard() + const { enableCheckpoints } = useExtensionState() + + const handleCustomCheckpoint = () => { + const message = prompt(t("chat:checkpoint.custom_prompt"), t("chat:checkpoint.custom_default")) + if (message !== null) { + vscode.postMessage({ type: "saveCustomCheckpoint", text: message }) + } + } return (
+ {enableCheckpoints && ( + + )} settings and restart your task.", + "save_custom": "Save Custom Checkpoint", + "custom_prompt": "Enter a description for this checkpoint", + "custom_default": "Manual checkpoint", "menu": { "viewDiff": "View Diff", "restore": "Restore Checkpoint",