Skip to content
Draft
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/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ export interface WebviewMessage {
| "shareCurrentTask"
| "showTaskWithId"
| "deleteTaskWithId"
| "deleteTaskCheckpointsWithId"
| "exportTaskWithId"
| "importSettings"
| "exportSettings"
Expand Down
68 changes: 68 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,74 @@ export class ClineProvider
}
}

// this function deletes only the checkpoints for a task, but keeps the task history and messages
async deleteTaskCheckpointsWithId(id: string) {
try {
// get the task info
const { historyItem, taskDirPath } = await this.getTaskWithId(id)

// Delete associated shadow repository or branch
const globalStorageDir = this.contextProxy.globalStorageUri.fsPath
const workspaceDir = historyItem.workspace || this.cwd

try {
await ShadowCheckpointService.deleteTask({ taskId: id, globalStorageDir, workspaceDir })
console.log(`[deleteTaskCheckpointsWithId${id}] deleted shadow repository/branch`)
} catch (error) {
console.error(
`[deleteTaskCheckpointsWithId${id}] failed to delete shadow repository or branch: ${error instanceof Error ? error.message : String(error)}`,
)
}

// Delete the checkpoints directory within the task folder
const checkpointsDir = path.join(taskDirPath, "checkpoints")
try {
const checkpointsDirExists = await fileExistsAtPath(checkpointsDir)
if (checkpointsDirExists) {
await fs.rm(checkpointsDir, { recursive: true, force: true })
console.log(`[deleteTaskCheckpointsWithId${id}] removed checkpoints directory`)
}
} catch (error) {
console.error(
`[deleteTaskCheckpointsWithId${id}] failed to remove checkpoints directory: ${error instanceof Error ? error.message : String(error)}`,
)
}

// Update ui_messages.json to remove or mark checkpoint_saved entries as deleted
try {
const uiMessagesPath = path.join(taskDirPath, GlobalFileNames.uiMessages)
const uiMessagesExists = await fileExistsAtPath(uiMessagesPath)
if (uiMessagesExists) {
const uiMessages = JSON.parse(await fs.readFile(uiMessagesPath, "utf8")) as ClineMessage[]
// Filter out checkpoint_saved messages
const filteredMessages = uiMessages.filter(
(msg) => !(msg.type === "say" && msg.say === "checkpoint_saved"),
)
await saveTaskMessages({
messages: filteredMessages,
taskId: id,
globalStoragePath: globalStorageDir,
})
console.log(`[deleteTaskCheckpointsWithId${id}] updated ui_messages.json`)
}
} catch (error) {
console.error(
`[deleteTaskCheckpointsWithId${id}] failed to update ui_messages.json: ${error instanceof Error ? error.message : String(error)}`,
)
}

// Update webview state to reflect changes
await this.postStateToWebview()
} catch (error) {
// If task is not found, just log the error
if (error instanceof Error && error.message === "Task not found") {
console.log(`[deleteTaskCheckpointsWithId${id}] task not found`)
return
}
throw error
}
}

async deleteTaskFromState(id: string) {
const taskHistory = this.getGlobalState("taskHistory") ?? []
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,9 @@ export const webviewMessageHandler = async (
case "deleteTaskWithId":
provider.deleteTaskWithId(message.text!)
break
case "deleteTaskCheckpointsWithId":
provider.deleteTaskCheckpointsWithId(message.text!)
break
case "deleteMultipleTasksWithIds": {
const ids = message.ids

Expand Down
39 changes: 39 additions & 0 deletions webview-ui/src/components/history/DeleteCheckpointsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useCallback } from "react"

import { Button, StandardTooltip } from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { vscode } from "@/utils/vscode"

type DeleteCheckpointsButtonProps = {
itemId: string
onDeleteCheckpoints?: (taskId: string) => void
}

export const DeleteCheckpointsButton = ({ itemId, onDeleteCheckpoints }: DeleteCheckpointsButtonProps) => {
const { t } = useAppTranslation()

const handleDeleteCheckpointsClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation()
if (e.shiftKey) {
vscode.postMessage({ type: "deleteTaskCheckpointsWithId", text: itemId })
} else if (onDeleteCheckpoints) {
onDeleteCheckpoints(itemId)
}
},
[itemId, onDeleteCheckpoints],
)

return (
<StandardTooltip content={t("history:deleteCheckpointsTitle")}>
<Button
variant="ghost"
size="icon"
data-testid="delete-checkpoints-button"
onClick={handleDeleteCheckpointsClick}
className="opacity-70">
<span className="codicon codicon-discard size-4 align-middle text-vscode-descriptionForeground" />
</Button>
</StandardTooltip>
)
}
63 changes: 63 additions & 0 deletions webview-ui/src/components/history/DeleteCheckpointsDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useCallback, useEffect } from "react"
import { useKeyPress } from "react-use"
import { AlertDialogProps } from "@radix-ui/react-alert-dialog"

import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Button,
} from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"

import { vscode } from "@/utils/vscode"

interface DeleteCheckpointsDialogProps extends AlertDialogProps {
taskId: string
}

export const DeleteCheckpointsDialog = ({ taskId, ...props }: DeleteCheckpointsDialogProps) => {
const { t } = useAppTranslation()
const [isEnterPressed] = useKeyPress("Enter")

const { onOpenChange } = props

const onDeleteCheckpoints = useCallback(() => {
if (taskId) {
vscode.postMessage({ type: "deleteTaskCheckpointsWithId", text: taskId })
onOpenChange?.(false)
}
}, [taskId, onOpenChange])

useEffect(() => {
if (taskId && isEnterPressed) {
onDeleteCheckpoints()
}
}, [taskId, isEnterPressed, onDeleteCheckpoints])

return (
<AlertDialog {...props}>
<AlertDialogContent onEscapeKeyDown={() => onOpenChange?.(false)}>
<AlertDialogHeader>
<AlertDialogTitle>{t("history:deleteCheckpoints")}</AlertDialogTitle>
<AlertDialogDescription>{t("history:deleteCheckpointsMessage")}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="secondary">{t("history:cancel")}</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDeleteCheckpoints}>
{t("history:deleteCheckpointsConfirm")}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The translation key history:deleteCheckpointsConfirm is not defined in any of the locale files. This will cause the button to display the raw key or be empty. The existing DeleteTaskDialog uses t("history:delete") for its confirm button, which is already defined. Consider using the same key for consistency, or add deleteCheckpointsConfirm to all 18 locale files.

Suggested change
{t("history:deleteCheckpointsConfirm")}
{t("history:delete")}

Fix it with Roo Code or mention @roomote and request a fix.

</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
12 changes: 12 additions & 0 deletions webview-ui/src/components/history/HistoryView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { memo, useState } from "react"
import { ArrowLeft } from "lucide-react"
import { DeleteTaskDialog } from "./DeleteTaskDialog"
import { DeleteCheckpointsDialog } from "./DeleteCheckpointsDialog"
import { BatchDeleteTaskDialog } from "./BatchDeleteTaskDialog"
import { Virtuoso } from "react-virtuoso"

Expand Down Expand Up @@ -42,6 +43,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
const { t } = useAppTranslation()

const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
const [deleteCheckpointsTaskId, setDeleteCheckpointsTaskId] = useState<string | null>(null)
const [isSelectionMode, setIsSelectionMode] = useState(false)
const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([])
const [showBatchDeleteDialog, setShowBatchDeleteDialog] = useState<boolean>(false)
Expand Down Expand Up @@ -249,6 +251,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
isSelectionMode={isSelectionMode}
isSelected={selectedTaskIds.includes(item.id)}
onToggleSelection={toggleTaskSelection}
onDeleteCheckpoints={setDeleteCheckpointsTaskId}
onDelete={setDeleteTaskId}
className="m-2"
/>
Expand Down Expand Up @@ -278,6 +281,15 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
<DeleteTaskDialog taskId={deleteTaskId} onOpenChange={(open) => !open && setDeleteTaskId(null)} open />
)}

{/* Delete checkpoints dialog */}
{deleteCheckpointsTaskId && (
<DeleteCheckpointsDialog
taskId={deleteCheckpointsTaskId}
onOpenChange={(open) => !open && setDeleteCheckpointsTaskId(null)}
open
/>
)}

{/* Batch delete dialog */}
{showBatchDeleteDialog && (
<BatchDeleteTaskDialog
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/components/history/TaskItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface TaskItemProps {
isSelectionMode?: boolean
isSelected?: boolean
onToggleSelection?: (taskId: string, isSelected: boolean) => void
onDeleteCheckpoints?: (taskId: string) => void
onDelete?: (taskId: string) => void
className?: string
}
Expand All @@ -29,6 +30,7 @@ const TaskItem = ({
isSelectionMode = false,
isSelected = false,
onToggleSelection,
onDeleteCheckpoints,
onDelete,
className,
}: TaskItemProps) => {
Expand Down Expand Up @@ -86,6 +88,7 @@ const TaskItem = ({
item={item}
variant={variant}
isSelectionMode={isSelectionMode}
onDeleteCheckpoints={onDeleteCheckpoints}
onDelete={onDelete}
/>

Expand Down
13 changes: 12 additions & 1 deletion webview-ui/src/components/history/TaskItemFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ import type { HistoryItem } from "@roo-code/types"
import { formatTimeAgo } from "@/utils/format"
import { CopyButton } from "./CopyButton"
import { ExportButton } from "./ExportButton"
import { DeleteCheckpointsButton } from "./DeleteCheckpointsButton"
import { DeleteButton } from "./DeleteButton"
import { StandardTooltip } from "../ui/standard-tooltip"

export interface TaskItemFooterProps {
item: HistoryItem
variant: "compact" | "full"
isSelectionMode?: boolean
onDeleteCheckpoints?: (taskId: string) => void
onDelete?: (taskId: string) => void
}

const TaskItemFooter: React.FC<TaskItemFooterProps> = ({ item, variant, isSelectionMode = false, onDelete }) => {
const TaskItemFooter: React.FC<TaskItemFooterProps> = ({
item,
variant,
isSelectionMode = false,
onDeleteCheckpoints,
onDelete,
}) => {
return (
<div className="text-xs text-vscode-descriptionForeground flex justify-between items-center">
<div className="flex gap-1 items-center text-vscode-descriptionForeground/60">
Expand All @@ -35,6 +43,9 @@ const TaskItemFooter: React.FC<TaskItemFooterProps> = ({ item, variant, isSelect
<div className="flex flex-row gap-0 -mx-2 items-center text-vscode-descriptionForeground/60 hover:text-vscode-descriptionForeground">
<CopyButton itemTask={item.task} />
{variant === "full" && <ExportButton itemId={item.id} />}
{onDeleteCheckpoints && (
<DeleteCheckpointsButton itemId={item.id} onDeleteCheckpoints={onDeleteCheckpoints} />
)}
{onDelete && <DeleteButton itemId={item.id} onDelete={onDelete} />}
</div>
)}
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/ca/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/de/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/en/history.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
"mostTokens": "Most Tokens",
"mostRelevant": "Most Relevant",
"deleteTaskTitle": "Delete Task (Shift + Click to skip confirmation)",
"deleteCheckpointsTitle": "Delete Checkpoints (Shift + Click to skip confirmation)",
"copyPrompt": "Copy Prompt",
"exportTask": "Export Task",
"deleteTask": "Delete Task",
"deleteTaskMessage": "Are you sure you want to delete this task? This action cannot be undone.",
"deleteCheckpoints": "Delete Checkpoints",
"deleteCheckpointsMessage": "Are you sure you want to delete all checkpoints for this task? This will remove the ability to restore previous states but keep your task history.",
"cancel": "Cancel",
"delete": "Delete",
"exitSelection": "Exit Selection",
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/es/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/fr/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/hi/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/id/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/it/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/ja/history.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading