From 9142841ca7df03219395ed452ef91304b6ebaa32 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 22 Jan 2026 21:08:02 -0700 Subject: [PATCH 1/2] 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 --- packages/types/src/vscode-extension-host.ts | 1 + .../checkpoints/__tests__/checkpoint.test.ts | 58 ++++++++++++++++++- src/core/checkpoints/index.ts | 40 +++++++++++++ src/core/webview/webviewMessageHandler.ts | 36 ++++++++++++ src/i18n/locales/ca/common.json | 3 + src/i18n/locales/de/common.json | 3 + src/i18n/locales/en/common.json | 3 + src/i18n/locales/es/common.json | 3 + src/i18n/locales/fr/common.json | 3 + src/i18n/locales/hi/common.json | 3 + src/i18n/locales/id/common.json | 3 + src/i18n/locales/it/common.json | 3 + src/i18n/locales/ja/common.json | 3 + src/i18n/locales/ko/common.json | 3 + src/i18n/locales/nl/common.json | 3 + src/i18n/locales/pl/common.json | 3 + src/i18n/locales/pt-BR/common.json | 3 + src/i18n/locales/ru/common.json | 3 + src/i18n/locales/tr/common.json | 3 + src/i18n/locales/vi/common.json | 3 + src/i18n/locales/zh-CN/common.json | 3 + src/i18n/locales/zh-TW/common.json | 3 + .../src/components/chat/RestoreTaskDialog.tsx | 57 ++++++++++++++++++ .../src/components/chat/TaskActions.tsx | 17 +++++- webview-ui/src/i18n/locales/ca/chat.json | 5 +- webview-ui/src/i18n/locales/de/chat.json | 5 +- webview-ui/src/i18n/locales/en/chat.json | 5 +- webview-ui/src/i18n/locales/es/chat.json | 5 +- webview-ui/src/i18n/locales/fr/chat.json | 5 +- webview-ui/src/i18n/locales/hi/chat.json | 5 +- webview-ui/src/i18n/locales/id/chat.json | 5 +- webview-ui/src/i18n/locales/it/chat.json | 5 +- webview-ui/src/i18n/locales/ja/chat.json | 5 +- webview-ui/src/i18n/locales/ko/chat.json | 5 +- webview-ui/src/i18n/locales/nl/chat.json | 5 +- webview-ui/src/i18n/locales/pl/chat.json | 5 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 5 +- webview-ui/src/i18n/locales/ru/chat.json | 5 +- webview-ui/src/i18n/locales/tr/chat.json | 5 +- webview-ui/src/i18n/locales/vi/chat.json | 5 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 5 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 5 +- 42 files changed, 332 insertions(+), 21 deletions(-) create mode 100644 webview-ui/src/components/chat/RestoreTaskDialog.tsx diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 03cad1bc96d..d53e3cfd65f 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -524,6 +524,7 @@ export interface WebviewMessage { | "openCustomModesSettings" | "checkpointDiff" | "checkpointRestore" + | "restoreToTaskStart" | "deleteMcpServer" | "codebaseIndexEnabled" | "telemetrySetting" diff --git a/src/core/checkpoints/__tests__/checkpoint.test.ts b/src/core/checkpoints/__tests__/checkpoint.test.ts index 299ff823b87..2398dcbf43e 100644 --- a/src/core/checkpoints/__tests__/checkpoint.test.ts +++ b/src/core/checkpoints/__tests__/checkpoint.test.ts @@ -1,7 +1,13 @@ import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest" import { Task } from "../../task/Task" import { ClineProvider } from "../../webview/ClineProvider" -import { checkpointSave, checkpointRestore, checkpointDiff, getCheckpointService } from "../index" +import { + checkpointSave, + checkpointRestore, + checkpointRestoreToBase, + checkpointDiff, + getCheckpointService, +} from "../index" import { MessageManager } from "../../message-manager" import * as vscode from "vscode" @@ -296,6 +302,56 @@ describe("Checkpoint functionality", () => { }) }) + describe("checkpointRestoreToBase", () => { + beforeEach(() => { + mockCheckpointService.baseHash = "initial-commit-hash" + }) + + it("should restore to base hash successfully", async () => { + const result = await checkpointRestoreToBase(mockTask) + + expect(result).toBe(true) + expect(mockCheckpointService.restoreCheckpoint).toHaveBeenCalledWith("initial-commit-hash") + expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "currentCheckpointUpdated", + text: "initial-commit-hash", + }) + expect(mockProvider.cancelTask).toHaveBeenCalled() + }) + + it("should return false if no checkpoint service available", async () => { + mockTask.checkpointService = undefined + mockTask.enableCheckpoints = false + + const result = await checkpointRestoreToBase(mockTask) + + expect(result).toBe(false) + expect(mockCheckpointService.restoreCheckpoint).not.toHaveBeenCalled() + }) + + it("should return false if no baseHash available", async () => { + mockCheckpointService.baseHash = undefined + + const result = await checkpointRestoreToBase(mockTask) + + expect(result).toBe(false) + expect(mockCheckpointService.restoreCheckpoint).not.toHaveBeenCalled() + expect(mockProvider.log).toHaveBeenCalledWith("[checkpointRestoreToBase] no baseHash available") + }) + + it("should disable checkpoints on error", async () => { + mockCheckpointService.restoreCheckpoint.mockRejectedValue(new Error("Restore failed")) + + const result = await checkpointRestoreToBase(mockTask) + + expect(result).toBe(false) + expect(mockTask.enableCheckpoints).toBe(false) + expect(mockProvider.log).toHaveBeenCalledWith( + "[checkpointRestoreToBase] disabling checkpoints for this task", + ) + }) + }) + describe("checkpointDiff", () => { beforeEach(() => { mockTask.clineMessages = [ diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts index 26a137b939c..01b6ce21717 100644 --- a/src/core/checkpoints/index.ts +++ b/src/core/checkpoints/index.ts @@ -301,6 +301,46 @@ export async function checkpointRestore( } } +/** + * Restore the workspace to its initial state (baseHash) - the state when the shadow git repo was initialized. + * This is a simpler version of checkpointRestore that doesn't need to rewind messages since we're + * restoring to the very beginning of the task. + * @returns true if restoration was successful, false otherwise + */ +export async function checkpointRestoreToBase(task: Task): Promise { + const service = await getCheckpointService(task) + + if (!service) { + return false + } + + const baseHash = service.baseHash + + if (!baseHash) { + const provider = task.providerRef.deref() + provider?.log("[checkpointRestoreToBase] no baseHash available") + return false + } + + const provider = task.providerRef.deref() + + try { + await service.restoreCheckpoint(baseHash) + TelemetryService.instance.captureCheckpointRestored(task.taskId) + await provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: baseHash }) + + // Cancel the task to reinitialize with the restored state + // This follows the same pattern as checkpointRestore + provider?.cancelTask() + + return true + } catch (err) { + provider?.log("[checkpointRestoreToBase] disabling checkpoints for this task") + task.enableCheckpoints = false + return false + } +} + export type CheckpointDiffOptions = { ts?: number previousCommitHash?: string diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 4a8b7520078..e22674c2065 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1212,6 +1212,42 @@ export const webviewMessageHandler = async ( break } + case "restoreToTaskStart": { + const currentTask = provider.getCurrentTask() + + if (!currentTask) { + vscode.window.showErrorMessage(t("common:errors.checkpoint_no_active_task")) + break + } + + if (!currentTask.enableCheckpoints) { + vscode.window.showErrorMessage(t("common:errors.checkpoint_not_enabled")) + break + } + + // Cancel the current task first + await provider.cancelTask() + + try { + await pWaitFor(() => provider.getCurrentTask()?.isInitialized === true, { timeout: 3_000 }) + } catch (error) { + vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout")) + break + } + + try { + const { checkpointRestoreToBase } = await import("../checkpoints") + const success = await checkpointRestoreToBase(provider.getCurrentTask()!) + + if (!success) { + vscode.window.showErrorMessage(t("common:errors.checkpoint_restore_base_failed")) + } + } catch (error) { + vscode.window.showErrorMessage(t("common:errors.checkpoint_failed")) + } + + break + } case "cancelTask": await provider.cancelTask() break diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 321a1aa3a06..235c6b489ae 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -32,6 +32,9 @@ "could_not_open_file_generic": "No s'ha pogut obrir el fitxer!", "checkpoint_timeout": "S'ha esgotat el temps en intentar restaurar el punt de control.", "checkpoint_failed": "Ha fallat la restauració del punt de control.", + "checkpoint_no_active_task": "No hi ha cap tasca activa per restaurar.", + "checkpoint_not_enabled": "Els punts de control no estan activats per a aquesta tasca.", + "checkpoint_restore_base_failed": "Ha fallat la restauració de l'espai de treball a l'estat inicial.", "git_not_installed": "Git és necessari per a la funció de punts de control. Si us plau, instal·la Git per activar els punts de control.", "checkpoint_no_first": "No hi ha un primer punt de control per comparar.", "checkpoint_no_previous": "No hi ha un punt de control anterior per comparar.", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 0611cf889af..487075c85f5 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Datei konnte nicht geöffnet werden!", "checkpoint_timeout": "Zeitüberschreitung beim Versuch, den Checkpoint wiederherzustellen.", "checkpoint_failed": "Fehler beim Wiederherstellen des Checkpoints.", + "checkpoint_no_active_task": "Keine aktive Aufgabe zum Wiederherstellen.", + "checkpoint_not_enabled": "Checkpoints sind für diese Aufgabe nicht aktiviert.", + "checkpoint_restore_base_failed": "Wiederherstellung des Arbeitsbereichs in den Ausgangszustand fehlgeschlagen.", "git_not_installed": "Git ist für die Checkpoint-Funktion erforderlich. Bitte installiere Git, um Checkpoints zu aktivieren.", "checkpoint_no_first": "Kein erster Checkpoint zum Vergleich vorhanden.", "checkpoint_no_previous": "Kein vorheriger Checkpoint zum Vergleich vorhanden.", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 90c409feb76..4cc9be68ba7 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Could not open file!", "checkpoint_timeout": "Timed out when attempting to restore checkpoint.", "checkpoint_failed": "Failed to restore checkpoint.", + "checkpoint_no_active_task": "No active task to restore.", + "checkpoint_not_enabled": "Checkpoints are not enabled for this task.", + "checkpoint_restore_base_failed": "Failed to restore workspace to initial state.", "git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.", "checkpoint_no_first": "No first checkpoint to compare.", "checkpoint_no_previous": "No previous checkpoint to compare.", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d0a086173e9..23164c9f89e 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "¡No se pudo abrir el archivo!", "checkpoint_timeout": "Se agotó el tiempo al intentar restaurar el punto de control.", "checkpoint_failed": "Error al restaurar el punto de control.", + "checkpoint_no_active_task": "No hay ninguna tarea activa para restaurar.", + "checkpoint_not_enabled": "Los puntos de control no están habilitados para esta tarea.", + "checkpoint_restore_base_failed": "Error al restaurar el espacio de trabajo al estado inicial.", "git_not_installed": "Git es necesario para la función de puntos de control. Por favor, instala Git para activar los puntos de control.", "checkpoint_no_first": "No hay primer punto de control para comparar.", "checkpoint_no_previous": "No hay punto de control anterior para comparar.", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 58350ef02ba..698cccd527d 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Impossible d'ouvrir le fichier !", "checkpoint_timeout": "Expiration du délai lors de la tentative de rétablissement du checkpoint.", "checkpoint_failed": "Échec du rétablissement du checkpoint.", + "checkpoint_no_active_task": "Aucune tâche active à restaurer.", + "checkpoint_not_enabled": "Les points de contrôle ne sont pas activés pour cette tâche.", + "checkpoint_restore_base_failed": "Échec de la restauration de l'espace de travail à l'état initial.", "git_not_installed": "Git est requis pour la fonctionnalité des points de contrôle. Veuillez installer Git pour activer les points de contrôle.", "checkpoint_no_first": "Aucun premier point de contrôle à comparer.", "checkpoint_no_previous": "Aucun point de contrôle précédent à comparer.", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 33277c71623..89fbc37b985 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "फ़ाइल नहीं खोली जा सकी!", "checkpoint_timeout": "चेकपॉइंट को पुनर्स्थापित करने का प्रयास करते समय टाइमआउट हो गया।", "checkpoint_failed": "चेकपॉइंट पुनर्स्थापित करने में विफल।", + "checkpoint_no_active_task": "पुनर्स्थापित करने के लिए कोई सक्रिय कार्य नहीं।", + "checkpoint_not_enabled": "इस कार्य के लिए चेकपॉइंट सक्षम नहीं हैं।", + "checkpoint_restore_base_failed": "वर्कस्पेस को प्रारंभिक स्थिति में पुनर्स्थापित करने में विफल।", "git_not_installed": "चेकपॉइंट सुविधा के लिए Git आवश्यक है। कृपया चेकपॉइंट सक्षम करने के लिए Git इंस्टॉल करें।", "checkpoint_no_first": "तुलना करने के लिए कोई पहला चेकपॉइंट नहीं है।", "checkpoint_no_previous": "तुलना करने के लिए कोई पिछला चेकपॉइंट नहीं है।", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index c10532beef9..4115c290710 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Tidak dapat membuka file!", "checkpoint_timeout": "Timeout saat mencoba memulihkan checkpoint.", "checkpoint_failed": "Gagal memulihkan checkpoint.", + "checkpoint_no_active_task": "Tidak ada tugas aktif untuk dipulihkan.", + "checkpoint_not_enabled": "Checkpoint tidak diaktifkan untuk tugas ini.", + "checkpoint_restore_base_failed": "Gagal memulihkan workspace ke keadaan awal.", "git_not_installed": "Git diperlukan untuk fitur checkpoint. Silakan instal Git untuk mengaktifkan checkpoint.", "checkpoint_no_first": "Tidak ada checkpoint pertama untuk dibandingkan.", "checkpoint_no_previous": "Tidak ada checkpoint sebelumnya untuk dibandingkan.", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index a75dccd3875..291dd43ce5a 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Impossibile aprire il file!", "checkpoint_timeout": "Timeout durante il tentativo di ripristinare il checkpoint.", "checkpoint_failed": "Impossibile ripristinare il checkpoint.", + "checkpoint_no_active_task": "Nessuna attività attiva da ripristinare.", + "checkpoint_not_enabled": "I checkpoint non sono abilitati per questa attività.", + "checkpoint_restore_base_failed": "Impossibile ripristinare lo spazio di lavoro allo stato iniziale.", "git_not_installed": "Git è richiesto per la funzione di checkpoint. Per favore, installa Git per abilitare i checkpoint.", "checkpoint_no_first": "Nessun primo checkpoint da confrontare.", "checkpoint_no_previous": "Nessun checkpoint precedente da confrontare.", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index b378f00b03f..f32a69804d9 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "ファイルを開けませんでした!", "checkpoint_timeout": "チェックポイントの復元を試みる際にタイムアウトしました。", "checkpoint_failed": "チェックポイントの復元に失敗しました。", + "checkpoint_no_active_task": "復元するアクティブなタスクがありません。", + "checkpoint_not_enabled": "このタスクではチェックポイントが有効になっていません。", + "checkpoint_restore_base_failed": "ワークスペースを初期状態に復元できませんでした。", "git_not_installed": "チェックポイント機能にはGitが必要です。チェックポイントを有効にするにはGitをインストールしてください。", "checkpoint_no_first": "比較する最初のチェックポイントがありません。", "checkpoint_no_previous": "比較する前のチェックポイントがありません。", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index e7afdceabce..b9ad1c90c40 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "파일을 열 수 없습니다!", "checkpoint_timeout": "체크포인트 복원을 시도하는 중 시간 초과되었습니다.", "checkpoint_failed": "체크포인트 복원에 실패했습니다.", + "checkpoint_no_active_task": "복원할 활성 작업이 없습니다.", + "checkpoint_not_enabled": "이 작업에는 체크포인트가 활성화되어 있지 않습니다.", + "checkpoint_restore_base_failed": "워크스페이스를 초기 상태로 복원하는 데 실패했습니다.", "git_not_installed": "체크포인트 기능을 사용하려면 Git이 필요합니다. 체크포인트를 활성화하려면 Git을 설치하세요.", "checkpoint_no_first": "비교할 첫 번째 체크포인트가 없습니다.", "checkpoint_no_previous": "비교할 이전 체크포인트가 없습니다.", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 889cd4b3ab6..ed5e0c99bdf 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Kon bestand niet openen!", "checkpoint_timeout": "Time-out bij het herstellen van checkpoint.", "checkpoint_failed": "Herstellen van checkpoint mislukt.", + "checkpoint_no_active_task": "Geen actieve taak om te herstellen.", + "checkpoint_not_enabled": "Checkpoints zijn niet ingeschakeld voor deze taak.", + "checkpoint_restore_base_failed": "Herstellen van workspace naar begintoestand mislukt.", "git_not_installed": "Git is vereist voor de checkpoint-functie. Installeer Git om checkpoints in te schakelen.", "checkpoint_no_first": "Geen eerste checkpoint om mee te vergelijken.", "checkpoint_no_previous": "Geen vorig checkpoint om mee te vergelijken.", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index faa4e9ed3a5..af3e5d8fd9d 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Nie można otworzyć pliku!", "checkpoint_timeout": "Upłynął limit czasu podczas próby przywrócenia punktu kontrolnego.", "checkpoint_failed": "Nie udało się przywrócić punktu kontrolnego.", + "checkpoint_no_active_task": "Brak aktywnego zadania do przywrócenia.", + "checkpoint_not_enabled": "Punkty kontrolne nie są włączone dla tego zadania.", + "checkpoint_restore_base_failed": "Nie udało się przywrócić obszaru roboczego do stanu początkowego.", "git_not_installed": "Funkcja punktów kontrolnych wymaga oprogramowania Git. Zainstaluj Git, aby włączyć punkty kontrolne.", "checkpoint_no_first": "Brak pierwszego punktu kontrolnego do porównania.", "checkpoint_no_previous": "Brak poprzedniego punktu kontrolnego do porównania.", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index f41a379acbc..4d7ff569b87 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -32,6 +32,9 @@ "could_not_open_file_generic": "Não foi possível abrir o arquivo!", "checkpoint_timeout": "Tempo esgotado ao tentar restaurar o ponto de verificação.", "checkpoint_failed": "Falha ao restaurar o ponto de verificação.", + "checkpoint_no_active_task": "Nenhuma tarefa ativa para restaurar.", + "checkpoint_not_enabled": "Os pontos de verificação não estão habilitados para esta tarefa.", + "checkpoint_restore_base_failed": "Falha ao restaurar o espaço de trabalho ao estado inicial.", "git_not_installed": "O Git é necessário para o recurso de checkpoints. Por favor, instale o Git para habilitar os checkpoints.", "checkpoint_no_first": "Nenhum primeiro ponto de verificação para comparar.", "checkpoint_no_previous": "Nenhum ponto de verificação anterior para comparar.", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 751637f19e0..32bfc6a9344 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Не удалось открыть файл!", "checkpoint_timeout": "Превышено время ожидания при попытке восстановления контрольной точки.", "checkpoint_failed": "Не удалось восстановить контрольную точку.", + "checkpoint_no_active_task": "Нет активной задачи для восстановления.", + "checkpoint_not_enabled": "Контрольные точки не включены для этой задачи.", + "checkpoint_restore_base_failed": "Не удалось восстановить рабочее пространство до исходного состояния.", "git_not_installed": "Для функции контрольных точек требуется Git. Пожалуйста, установите Git, чтобы включить контрольные точки.", "checkpoint_no_first": "Нет первой контрольной точки для сравнения.", "checkpoint_no_previous": "Нет предыдущей контрольной точки для сравнения.", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 7b2ac152a9b..e345b6d9e7e 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Dosya açılamadı!", "checkpoint_timeout": "Kontrol noktasını geri yüklemeye çalışırken zaman aşımına uğradı.", "checkpoint_failed": "Kontrol noktası geri yüklenemedi.", + "checkpoint_no_active_task": "Geri yüklenecek aktif görev yok.", + "checkpoint_not_enabled": "Bu görev için kontrol noktaları etkinleştirilmemiş.", + "checkpoint_restore_base_failed": "Çalışma alanı başlangıç durumuna geri yüklenemedi.", "git_not_installed": "Kontrol noktaları özelliği için Git gereklidir. Kontrol noktalarını etkinleştirmek için lütfen Git'i yükleyin.", "checkpoint_no_first": "Karşılaştırılacak ilk kontrol noktası yok.", "checkpoint_no_previous": "Karşılaştırılacak önceki kontrol noktası yok.", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 0d88ba07808..959df4d189f 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "Không thể mở tệp!", "checkpoint_timeout": "Đã hết thời gian khi cố gắng khôi phục điểm kiểm tra.", "checkpoint_failed": "Không thể khôi phục điểm kiểm tra.", + "checkpoint_no_active_task": "Không có nhiệm vụ đang hoạt động để khôi phục.", + "checkpoint_not_enabled": "Các điểm kiểm tra chưa được bật cho nhiệm vụ này.", + "checkpoint_restore_base_failed": "Không thể khôi phục không gian làm việc về trạng thái ban đầu.", "git_not_installed": "Yêu cầu Git cho tính năng điểm kiểm tra. Vui lòng cài đặt Git để bật điểm kiểm tra.", "checkpoint_no_first": "Không có điểm kiểm tra đầu tiên để so sánh.", "checkpoint_no_previous": "Không có điểm kiểm tra trước đó để so sánh.", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 133b3de0794..0a26c0e17c7 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -33,6 +33,9 @@ "could_not_open_file_generic": "无法打开文件!", "checkpoint_timeout": "尝试恢复检查点时超时。", "checkpoint_failed": "恢复检查点失败。", + "checkpoint_no_active_task": "没有活动任务可恢复。", + "checkpoint_not_enabled": "此任务未启用检查点。", + "checkpoint_restore_base_failed": "无法将工作区恢复到初始状态。", "git_not_installed": "检查点功能需要 Git。请安装 Git 以启用检查点。", "checkpoint_no_first": "没有第一个存档点可供比较。", "checkpoint_no_previous": "没有上一个存档点可供比较。", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 8039f203b62..47d665139b7 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -28,6 +28,9 @@ "could_not_open_file_generic": "無法開啟檔案!", "checkpoint_timeout": "嘗試恢復檢查點時超時。", "checkpoint_failed": "恢復檢查點失敗。", + "checkpoint_no_active_task": "沒有活動任務可還原。", + "checkpoint_not_enabled": "此任務未啟用存檔點。", + "checkpoint_restore_base_failed": "無法將工作區還原至初始狀態。", "git_not_installed": "存檔點功能需要 Git。請安裝 Git 以啟用存檔點。", "checkpoint_no_first": "沒有第一個存檔點可供比較。", "checkpoint_no_previous": "沒有上一個存檔點可供比較。", diff --git a/webview-ui/src/components/chat/RestoreTaskDialog.tsx b/webview-ui/src/components/chat/RestoreTaskDialog.tsx new file mode 100644 index 00000000000..385d9067b51 --- /dev/null +++ b/webview-ui/src/components/chat/RestoreTaskDialog.tsx @@ -0,0 +1,57 @@ +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" + +export const RestoreTaskDialog = ({ ...props }: AlertDialogProps) => { + const { t } = useAppTranslation() + const [isEnterPressed] = useKeyPress("Enter") + + const { onOpenChange } = props + + const onRestore = useCallback(() => { + vscode.postMessage({ type: "restoreToTaskStart" }) + onOpenChange?.(false) + }, [onOpenChange]) + + useEffect(() => { + if (props.open && isEnterPressed) { + onRestore() + } + }, [props.open, isEnterPressed, onRestore]) + + return ( + + onOpenChange?.(false)}> + + {t("chat:task.restoreToStart")} + {t("chat:task.restoreToStartConfirm")} + + + + + + + + + + + + ) +} diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index 74575ddc28f..1e8fe27e817 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -8,9 +8,10 @@ import { useCopyToClipboard } from "@/utils/clipboard" import { useExtensionState } from "@/context/ExtensionStateContext" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" +import { RestoreTaskDialog } from "./RestoreTaskDialog" import { ShareButton } from "./ShareButton" import { CloudTaskButton } from "./CloudTaskButton" -import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react" +import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon, RotateCcw } from "lucide-react" import { LucideIconButton } from "./LucideIconButton" interface TaskActionsProps { @@ -20,9 +21,10 @@ interface TaskActionsProps { export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) + const [showRestoreDialog, setShowRestoreDialog] = useState(false) const { t } = useTranslation() const { copyWithFeedback } = useCopyToClipboard() - const { debug } = useExtensionState() + const { debug, enableCheckpoints } = useExtensionState() return (
@@ -65,6 +67,17 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { )} + {enableCheckpoints && ( + <> + setShowRestoreDialog(true)} + /> + + + )} {debug && item?.id && ( <> Date: Fri, 23 Jan 2026 15:00:51 +0000 Subject: [PATCH 2/2] fix: remove global Enter key listener from RestoreTaskDialog to prevent accidental restores --- webview-ui/src/components/chat/RestoreTaskDialog.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/webview-ui/src/components/chat/RestoreTaskDialog.tsx b/webview-ui/src/components/chat/RestoreTaskDialog.tsx index 385d9067b51..77bb7b0a27d 100644 --- a/webview-ui/src/components/chat/RestoreTaskDialog.tsx +++ b/webview-ui/src/components/chat/RestoreTaskDialog.tsx @@ -1,5 +1,4 @@ -import { useCallback, useEffect } from "react" -import { useKeyPress } from "react-use" +import { useCallback } from "react" import { AlertDialogProps } from "@radix-ui/react-alert-dialog" import { @@ -19,7 +18,6 @@ import { vscode } from "@/utils/vscode" export const RestoreTaskDialog = ({ ...props }: AlertDialogProps) => { const { t } = useAppTranslation() - const [isEnterPressed] = useKeyPress("Enter") const { onOpenChange } = props @@ -28,12 +26,6 @@ export const RestoreTaskDialog = ({ ...props }: AlertDialogProps) => { onOpenChange?.(false) }, [onOpenChange]) - useEffect(() => { - if (props.open && isEnterPressed) { - onRestore() - } - }, [props.open, isEnterPressed, onRestore]) - return ( onOpenChange?.(false)}>