Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export const clineSays = [
"mcp_server_response",
"subtask_result",
"checkpoint_saved",
"checkpoint_saved_custom",
"rooignore_error",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] New say type 'checkpoint_saved_custom' added. Verify all renderers that map say types handle this with an appropriate label/icon to avoid generic presentation.

"diff_error",
"condense_context",
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const commandIds = [
"acceptInput",
"focusPanel",
"toggleAutoApprove",
"saveCustomCheckpoint",
] as const
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] New command id 'saveCustomCheckpoint' added. Ensure it is contributed in the extension package.json (contributes.commands) so users can invoke it from the Command Palette.


export type CommandId = (typeof commandIds)[number]
Expand Down
36 changes: 36 additions & 0 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] i18n key mismatch: t("common:errors.no_active_task"). The en/common.json addition places 'no_active_task' at the common root, not under errors. Either switch to t("common:no_active_task") (and t("common:checkpoints_disabled")) or move these keys under common.errors in the locale file.

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)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Avoid blank custom checkpoint messages by defaulting on empty input.

Suggested change
await currentTask.checkpointSave(true, false, message)
const trimmed = (message ?? '').trim()
const finalMessage = trimmed || t('common:checkpoint.custom_default')
await currentTask.checkpointSave(true, false, finalMessage)

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<RegisterCommandOptions, "provider">) => {
Expand Down
15 changes: 8 additions & 7 deletions src/core/checkpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 = {
Expand Down
4 changes: 2 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2795,8 +2795,8 @@ export class Task extends EventEmitter<TaskEvents> 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) {
Expand Down
29 changes: 29 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] i18n key mismatch: uses t("common:checkpoint.no_active_task") but en/common.json defines this key at the common root (not under checkpoint). Align to t("common:no_active_task") (same for checkpoints_disabled) or move keys under common.checkpoint consistently.

break
}

if (!getGlobalState("enableCheckpoints")) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Mixed enablement checks: here we check getGlobalState("enableCheckpoints") while the command path checks currentTask.enableCheckpoints. Pick a single source of truth (prefer task-scoped) to avoid divergence.

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)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] UX consistency: the command path also emits a chat event via currentTask.say("checkpoint_saved_custom", …). Consider emitting the same here so the checkpoint appears in chat history, not just a toast.

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
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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}}"
}
}
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface WebviewMessage {
| "openCustomModesSettings"
| "checkpointDiff"
| "checkpointRestore"
| "saveCustomCheckpoint"
| "deleteMcpServer"
| "maxOpenTabsContext"
| "maxWorkspaceFiles"
Expand Down
17 changes: 17 additions & 0 deletions webview-ui/src/components/chat/TaskActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -20,9 +21,25 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(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"))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Using window.prompt() blocks the UI and is inconsistent with the rest of the UI patterns. Consider using a non-blocking Dialog component with proper i18n and focus handling.

if (message !== null) {
vscode.postMessage({ type: "saveCustomCheckpoint", text: message })
}
}

return (
<div className="flex flex-row items-center">
{enableCheckpoints && (
<IconButton
iconClass="codicon-save"
title={t("chat:checkpoint.save_custom")}
disabled={buttonsDisabled}
onClick={handleCustomCheckpoint}
/>
)}
<IconButton
iconClass="codicon-desktop-download"
title={t("chat:task.export")}
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@
"checkpoint": {
"regular": "Checkpoint",
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> 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",
Expand Down
Loading