From 2766839dcc4f4b08b5b01bcce23b7cef4413a63e 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 7ae89e8777d..e3bb00cba66 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -517,6 +517,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 6a0224b42f8..9029586f3be 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1207,6 +1207,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 9f8f961e73e..1110198b515 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 086372dda85..06c901b0380 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 636d26f76cb..1b8aeb667e4 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 bc22040c6a8..ec57ab4979d 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 f7a76a53c12..45990a25aa0 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 e51d177d946..7c8fb826416 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 cfb165979d3..f16d15a1df6 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 e5fa6d68db3..af39d5ade3e 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 7ebe0de597d..2010e990c42 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 0c1ed5ba518..7e052ca7254 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 0bbf5695364..ec61dc19afe 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 23bc09e4d78..98705dc5844 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 737b322f78a..5a0d8e0ba8c 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 7ac53199ba8..50146ac152e 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 fca268c0ff6..fafb91100c1 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 bd9bb72b474..8e0c639c456 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 494c246d658..3cbbfb83a9a 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 572cdb46519..7fb6b22a374 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)}>