From abfbe7f789c00a8084901158cedb9e1ca1ac4982 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Thu, 27 Aug 2026 19:59:46 +0800 Subject: [PATCH 1/4] feat(checkpoints): per-write checkpoints, task-start baseline, and perWriteCheckpoints setting (B1, #1375) --- packages/types/src/global-settings.ts | 13 + packages/types/src/vscode-extension-host.ts | 1 + src/core/task/Task.ts | 14 + src/core/task/__tests__/Task.spec.ts | 92 +++++ src/core/tools/ApplyPatchTool.ts | 67 +++- src/core/tools/EditFileTool.ts | 6 + src/core/tools/WriteToFileTool.ts | 8 + .../__tests__/applyPatchTool.execute.spec.ts | 331 ++++++++++++++++++ src/core/tools/__tests__/editFileTool.spec.ts | 42 +++ .../tools/__tests__/writeToFileTool.spec.ts | 77 ++++ src/core/webview/ClineProvider.ts | 4 + .../webview/__tests__/ClineProvider.spec.ts | 43 +++ .../settings/CheckpointSettings.tsx | 21 +- .../src/components/settings/SettingsView.tsx | 4 + .../__tests__/CheckpointSettings.spec.tsx | 125 +++++++ .../src/context/ExtensionStateContext.tsx | 1 + .../__tests__/ExtensionStateContext.spec.tsx | 2 + webview-ui/src/i18n/locales/ca/settings.json | 4 + webview-ui/src/i18n/locales/de/settings.json | 4 + webview-ui/src/i18n/locales/en/settings.json | 4 + webview-ui/src/i18n/locales/es/settings.json | 4 + webview-ui/src/i18n/locales/fr/settings.json | 4 + webview-ui/src/i18n/locales/hi/settings.json | 4 + webview-ui/src/i18n/locales/id/settings.json | 4 + webview-ui/src/i18n/locales/it/settings.json | 4 + webview-ui/src/i18n/locales/ja/settings.json | 4 + webview-ui/src/i18n/locales/ko/settings.json | 4 + webview-ui/src/i18n/locales/nl/settings.json | 4 + webview-ui/src/i18n/locales/pl/settings.json | 4 + .../src/i18n/locales/pt-BR/settings.json | 4 + webview-ui/src/i18n/locales/ru/settings.json | 4 + webview-ui/src/i18n/locales/tr/settings.json | 4 + webview-ui/src/i18n/locales/vi/settings.json | 4 + .../src/i18n/locales/zh-CN/settings.json | 4 + .../src/i18n/locales/zh-TW/settings.json | 4 + 35 files changed, 904 insertions(+), 19 deletions(-) create mode 100644 webview-ui/src/components/settings/__tests__/CheckpointSettings.spec.tsx diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 95f246dbe7..4f21ca9607 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -99,6 +99,13 @@ export const MAX_CHECKPOINT_TIMEOUT_SECONDS = 60 */ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 +/** + * Whether per-write checkpoints and task-start baseline are enabled by default. + * Master switch for the B cluster of checkpoint features. + * @default true + */ +export const DEFAULT_PER_WRITE_CHECKPOINTS = true + /** * GlobalSettings */ @@ -200,6 +207,12 @@ export const globalSettingsSchema = z.object({ .min(MIN_CHECKPOINT_TIMEOUT_SECONDS) .max(MAX_CHECKPOINT_TIMEOUT_SECONDS) .optional(), + /** + * Whether to record a shadow-git checkpoint after every successful write_to_file, + * edit_file, and apply_patch (per-write checkpoints), plus a task-start baseline. + * @default true + */ + perWriteCheckpoints: z.boolean().optional(), ttsEnabled: z.boolean().optional(), ttsSpeed: z.number().optional(), diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 5f6b579779..20756d7a68 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -348,6 +348,7 @@ export type ExtensionState = Pick< enableCheckpoints: boolean checkpointTimeout: number // Timeout for checkpoint initialization in seconds (default: 15) + perWriteCheckpoints: boolean maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500) maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500) showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 349d9c51d3..61203a3046 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -314,6 +314,9 @@ export class Task extends EventEmitter implements TaskLike { public lastMessageTs?: number private autoApprovalTimeoutRef?: NodeJS.Timeout + // B1: task-start baseline, recorded at most once (initiateTaskLoop also runs on resume). + private taskStartBaselineDone = false + // Tool Use consecutiveMistakeCount: number = 0 consecutiveMistakeLimit: number @@ -2492,6 +2495,17 @@ export class Task extends EventEmitter implements TaskLike { // arm needed. void getCheckpointService(this) + // B1 task-start baseline: a suppressed pre-task root commit (default-on). + if (!this.taskStartBaselineDone) { + this.taskStartBaselineDone = true + const baselineEnabled = (await this.providerRef.deref()?.getState())?.perWriteCheckpoints + if (baselineEnabled !== false) { + // allowEmpty=true so a clean workspace still produces the baseline + // commit; awaited so the first per-write checkpoint cannot interleave. + await this.checkpointSave(true, true) + } + } + let nextUserContent = userContent let includeFileDetails = true diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 37e228f887..4ca5d7495c 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -3289,6 +3289,98 @@ describe("Cline", () => { }) }) + describe("task-start baseline (B1 perWriteCheckpoints)", () => { + it("records one suppressed baseline checkpoint per Task instance at loop start", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "baseline task", + startTask: false, + }) + const taskAccess = getTaskTestAccess(task) + const saveSpy = vi.spyOn(task, "checkpointSave").mockResolvedValue(undefined) + const state = await mockProvider.getState() + vi.spyOn(mockProvider, "getState").mockResolvedValue(state) + + task.abort = true + + await taskAccess.initiateTaskLoop([]) + await taskAccess.initiateTaskLoop([]) + + expect(saveSpy).toHaveBeenCalledOnce() + expect(saveSpy).toHaveBeenCalledWith(true, true) + }) + + it("records the baseline checkpoint when the setting is unset (default-on)", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "baseline unset task", + startTask: false, + }) + const taskAccess = getTaskTestAccess(task) + const saveSpy = vi.spyOn(task, "checkpointSave").mockResolvedValue(undefined) + const state = await mockProvider.getState() + // Unset: the property is absent from the state, so default-on applies. + const unsetState = { ...state } + Reflect.deleteProperty(unsetState, "perWriteCheckpoints") + vi.spyOn(mockProvider, "getState").mockResolvedValue(unsetState as typeof state) + + task.abort = true + + await taskAccess.initiateTaskLoop([]) + + expect(saveSpy).toHaveBeenCalledOnce() + expect(saveSpy).toHaveBeenCalledWith(true, true) + }) + + it("does not record a baseline checkpoint when perWriteCheckpoints is disabled", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "baseline disabled task", + startTask: false, + }) + const taskAccess = getTaskTestAccess(task) + const saveSpy = vi.spyOn(task, "checkpointSave").mockResolvedValue(undefined) + const state = await mockProvider.getState() + vi.spyOn(mockProvider, "getState").mockResolvedValue({ ...state, perWriteCheckpoints: false }) + + task.abort = true + + await taskAccess.initiateTaskLoop([]) + + expect(saveSpy).not.toHaveBeenCalled() + }) + + it("awaits the baseline checkpoint before entering the request loop", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "baseline await task", + startTask: false, + }) + const taskAccess = getTaskTestAccess(task) + type SaveResult = Awaited> + let resolveSave: (value: SaveResult | PromiseLike) => void = () => {} + const saveSpy = vi + .spyOn(task, "checkpointSave") + .mockImplementation(() => new Promise((resolve) => (resolveSave = resolve))) + const requestSpy = vi.spyOn(task, "recursivelyMakeClineRequests").mockResolvedValue(true) + vi.spyOn(mockProvider, "getState").mockResolvedValue({ ...(await mockProvider.getState()) }) + const loopPromise = taskAccess.initiateTaskLoop([]) + + // The loop must not enter while the baseline checkpoint is still in flight. + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(saveSpy).toHaveBeenCalledOnce() + expect(requestSpy).not.toHaveBeenCalled() + + resolveSave() + await loopPromise + expect(requestSpy).toHaveBeenCalled() + }) + }) + describe("start()", () => { it("should be a no-op if the task was already started in the constructor", () => { const task = new Task({ diff --git a/src/core/tools/ApplyPatchTool.ts b/src/core/tools/ApplyPatchTool.ts index 56b2bf8909..f42a4ebf03 100644 --- a/src/core/tools/ApplyPatchTool.ts +++ b/src/core/tools/ApplyPatchTool.ts @@ -6,6 +6,7 @@ import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { Task } from "../task/Task" +import { checkpointSave } from "../checkpoints" import { formatResponse } from "../prompts/responses" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" @@ -102,7 +103,10 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { return } - // Process each file change + // Process each file change. The handlers report whether their file + // operation succeeded, so a rejected approval or a failed local write + // does not get checkpointed as if the patch had succeeded. + let patchSucceeded = true for (const change of changes) { const relPath = change.path const absolutePath = path.resolve(task.cwd, relPath) @@ -120,17 +124,39 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { if (change.type === "add") { // Create new file - await this.handleAddFile(change, absolutePath, relPath, task, callbacks, isWriteProtected) + patchSucceeded = + (await this.handleAddFile(change, absolutePath, relPath, task, callbacks, isWriteProtected)) && + patchSucceeded } else if (change.type === "delete") { // Delete file - await this.handleDeleteFile(absolutePath, relPath, task, callbacks, isWriteProtected) + patchSucceeded = + (await this.handleDeleteFile(absolutePath, relPath, task, callbacks, isWriteProtected)) && + patchSucceeded } else if (change.type === "update") { // Update file - await this.handleUpdateFile(change, absolutePath, relPath, task, callbacks, isWriteProtected) + patchSucceeded = + (await this.handleUpdateFile( + change, + absolutePath, + relPath, + task, + callbacks, + isWriteProtected, + )) && patchSucceeded } } task.consecutiveMistakeCount = 0 + + // B1: one checkpoint for the whole patch (not per file), and only when + // every file operation succeeded. Live setting with default-on + // semantics: skip only when explicitly false. + if (patchSucceeded) { + const perWriteCheckpoints = (await task.providerRef?.deref()?.getState())?.perWriteCheckpoints + if (perWriteCheckpoints !== false) { + void checkpointSave(task, false, true).catch(() => {}) + } + } } catch (error) { await handleError("apply patch", error as Error) await task.diffViewProvider.reset() @@ -144,7 +170,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { task: Task, callbacks: ToolCallbacks, isWriteProtected: boolean, - ): Promise { + ): Promise { const { askApproval, pushToolResult } = callbacks // Check if file already exists @@ -155,7 +181,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { const errorMessage = `File already exists: ${relPath}. Use Update File instead.` await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) - return + return false } const newContent = change.newContent || "" @@ -209,7 +235,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { } pushToolResult("Changes were rejected by the user.") await task.diffViewProvider.reset() - return + return false } // Save the changes @@ -227,6 +253,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { pushToolResult(message) await task.diffViewProvider.reset() task.processQueuedMessages() + return true } private async handleDeleteFile( @@ -235,7 +262,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { task: Task, callbacks: ToolCallbacks, isWriteProtected: boolean, - ): Promise { + ): Promise { const { askApproval, pushToolResult } = callbacks // Check if file exists @@ -246,7 +273,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { const errorMessage = `File not found: ${relPath}. Cannot delete a non-existent file.` await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) - return + return false } const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) @@ -268,7 +295,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { if (!didApprove) { pushToolResult("Delete operation was rejected by the user.") - return + return false } // Delete the file @@ -278,12 +305,13 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { const errorMessage = `Failed to delete file '${relPath}': ${error instanceof Error ? error.message : String(error)}` await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) - return + return false } task.didEditFile = true pushToolResult(`Successfully deleted ${relPath}`) task.processQueuedMessages() + return true } private async handleUpdateFile( @@ -293,7 +321,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { task: Task, callbacks: ToolCallbacks, isWriteProtected: boolean, - ): Promise { + ): Promise { const { askApproval, pushToolResult } = callbacks // Check if file exists @@ -304,7 +332,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { const errorMessage = `File not found: ${relPath}. Cannot update a non-existent file.` await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) - return + return false } const originalContent = change.originalContent || "" @@ -318,9 +346,11 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { // Generate and validate diff const diff = formatResponse.createPrettyPatch(relPath, originalContent, newContent) if (!diff) { + // A no-op change is not a failure: the patch processed cleanly and + // nothing was written, so the whole-patch success state is kept. pushToolResult(`No changes needed for '${relPath}'`) await task.diffViewProvider.reset() - return + return true } // Check experiment settings @@ -366,7 +396,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { } pushToolResult("Changes were rejected by the user.") await task.diffViewProvider.reset() - return + return false } // Handle file move if specified @@ -379,7 +409,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { await task.say("rooignore_error", change.movePath) pushToolResult(formatResponse.rooIgnoreError(change.movePath)) await task.diffViewProvider.reset() - return + return false } // Check if destination path is write-protected @@ -391,7 +421,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) await task.diffViewProvider.reset() - return + return false } // Check if destination path is outside workspace @@ -403,7 +433,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { await task.say("error", errorMessage) pushToolResult(formatResponse.toolError(errorMessage)) await task.diffViewProvider.reset() - return + return false } // Save new content to the new path @@ -447,6 +477,7 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { pushToolResult(message) await task.diffViewProvider.reset() task.processQueuedMessages() + return true } override async handlePartial(task: Task, block: ToolUse<"apply_patch">): Promise { diff --git a/src/core/tools/EditFileTool.ts b/src/core/tools/EditFileTool.ts index a7301e2ac9..0a3cb5d2e8 100644 --- a/src/core/tools/EditFileTool.ts +++ b/src/core/tools/EditFileTool.ts @@ -11,6 +11,7 @@ import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { sanitizeUnifiedDiff, computeDiffStats } from "../diff/stats" +import { checkpointSave } from "../../core/checkpoints" import type { ToolUse } from "../../shared/tools" import { BaseTool, ToolCallbacks } from "./BaseTool" @@ -392,6 +393,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { const state = await provider?.getState() const diagnosticsEnabled = state?.diagnosticsEnabled ?? true const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const perWriteCheckpoints = state?.perWriteCheckpoints ?? true const isPreventFocusDisruptionEnabled = experiments.isEnabled( state?.experiments ?? {}, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, @@ -463,6 +465,10 @@ export class EditFileTool extends BaseTool<"edit_file"> { pushToolResult(message + replacementInfo) + if (perWriteCheckpoints) { + void checkpointSave(task, false, true).catch(() => {}) + } + await task.diffViewProvider.reset() this.resetPartialState() diff --git a/src/core/tools/WriteToFileTool.ts b/src/core/tools/WriteToFileTool.ts index ae026b4b86..cf1500a510 100644 --- a/src/core/tools/WriteToFileTool.ts +++ b/src/core/tools/WriteToFileTool.ts @@ -14,6 +14,7 @@ import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { convertNewFileToUnifiedDiff, computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" +import { checkpointSave } from "../checkpoints" import type { ToolUse } from "../../shared/tools" import { BaseTool, ToolCallbacks } from "./BaseTool" @@ -103,6 +104,7 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { const state = await provider?.getState() const diagnosticsEnabled = state?.diagnosticsEnabled ?? true const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS + const perWriteCheckpoints = state?.perWriteCheckpoints ?? true const isPreventFocusDisruptionEnabled = experiments.isEnabled( state?.experiments ?? {}, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, @@ -179,6 +181,12 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { pushToolResult(message) + if (perWriteCheckpoints) { + // Await so the checkpoint (staging + commit) finishes before the next + // queued write starts; otherwise two writes can collapse into one commit. + await checkpointSave(task, false, true).catch(() => {}) + } + await task.diffViewProvider.reset() this.resetPartialState() diff --git a/src/core/tools/__tests__/applyPatchTool.execute.spec.ts b/src/core/tools/__tests__/applyPatchTool.execute.spec.ts index 72ffb112bc..f5f64c939e 100644 --- a/src/core/tools/__tests__/applyPatchTool.execute.spec.ts +++ b/src/core/tools/__tests__/applyPatchTool.execute.spec.ts @@ -4,13 +4,29 @@ import type { MockedFunction } from "vitest" import { fileExistsAtPath } from "../../../utils/fs" import { isPathOutsideWorkspace } from "../../../utils/pathUtils" +import * as fsPromises from "fs/promises" import type { Task } from "../../task/Task" +import { checkpointSave } from "../../checkpoints" import { ApplyPatchTool } from "../ApplyPatchTool" +// The vi.mock factory exposes the fs/promises functions under a `default` +// property (matching the SUT's default import), which the static module type +// does not declare; cast once at this boundary rather than at each call site. +const mockedFsPromises = vi.mocked( + fsPromises as unknown as { + default: { + unlink: MockedFunction + writeFile: MockedFunction + } + }, +) + vi.mock("fs/promises", () => ({ default: { readFile: vi.fn().mockResolvedValue("original file content\n"), unlink: vi.fn().mockResolvedValue(undefined), + mkdir: vi.fn().mockResolvedValue(undefined), + writeFile: vi.fn().mockResolvedValue(undefined), }, })) @@ -22,6 +38,10 @@ vi.mock("../../../utils/pathUtils", () => ({ isPathOutsideWorkspace: vi.fn().mockReturnValue(false), })) +vi.mock("../../checkpoints", () => ({ + checkpointSave: vi.fn().mockResolvedValue(undefined), +})) + describe("ApplyPatchTool.execute - delete file success path", () => { const mockedFileExistsAtPath = fileExistsAtPath as MockedFunction const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction @@ -38,6 +58,9 @@ describe("ApplyPatchTool.execute - delete file success path", () => { | "say" | "processQueuedMessages" | "didEditFile" + | "providerRef" + | "diffViewProvider" + | "fileContextTracker" > let mockAskApproval: MockedFunction<(...args: unknown[]) => Promise> let mockHandleError: MockedFunction<(...args: unknown[]) => Promise> @@ -52,6 +75,11 @@ describe("ApplyPatchTool.execute - delete file success path", () => { mockTask = { cwd: "/workspace/project", consecutiveMistakeCount: 0, + providerRef: { + deref: vi.fn().mockReturnValue({ + getState: vi.fn().mockResolvedValue({}), + }), + } as unknown as Task["providerRef"], recordToolUsage: vi.fn(), recordToolError: vi.fn(), rooIgnoreController: { @@ -63,6 +91,21 @@ describe("ApplyPatchTool.execute - delete file success path", () => { say: vi.fn().mockResolvedValue(undefined), processQueuedMessages: vi.fn(), didEditFile: false, + diffViewProvider: { + editType: "modify", + originalContent: undefined, + open: vi.fn().mockResolvedValue(undefined), + update: vi.fn().mockResolvedValue(undefined), + scrollToFirstDiff: vi.fn(), + revertChanges: vi.fn().mockResolvedValue(undefined), + reset: vi.fn().mockResolvedValue(undefined), + saveDirectly: vi.fn().mockResolvedValue({ finalContent: "saved" }), + saveChanges: vi.fn().mockResolvedValue(undefined), + pushToolWriteResult: vi.fn().mockResolvedValue("File saved successfully"), + } as unknown as Task["diffViewProvider"], + fileContextTracker: { + trackFileContext: vi.fn().mockResolvedValue(undefined), + } as unknown as Task["fileContextTracker"], } mockAskApproval = vi.fn().mockResolvedValue(true) @@ -93,4 +136,292 @@ describe("ApplyPatchTool.execute - delete file success path", () => { expect(mockTask.recordToolUsage).not.toHaveBeenCalled() expect(mockTask.recordToolError).not.toHaveBeenCalled() }) + + describe("per-write checkpoints (B1)", () => { + const deletePatch = `*** Begin Patch +*** Delete File: src/obsolete.ts +*** End Patch` + const mockedCheckpointSave = checkpointSave as MockedFunction + + it("records one suppressed checkpoint for the whole patch (default-on)", async () => { + await tool.execute({ patch: deletePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully deleted")) + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + expect(mockedCheckpointSave).toHaveBeenCalledWith(mockTask as Task, false, true) + }) + + it("does not record a checkpoint when perWriteCheckpoints is disabled", async () => { + // Structural cast for the test double (matches the mock style used for the controllers above). + const ref = (mockTask["providerRef"] as unknown as { deref: MockedFunction<() => unknown> }).deref + ref.mockReturnValue({ + getState: vi.fn().mockResolvedValue({ perWriteCheckpoints: false }), + }) + + await tool.execute({ patch: deletePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully deleted")) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when patch processing fails", async () => { + // A malformed patch fails at parse time, before the change loop and + // the post-loop checkpoint hook. + const badPatch = `*** Begin Patch +*** This is not a valid hunk +*** End Patch` + + await tool.execute({ patch: badPatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockTask.recordToolError).toHaveBeenCalledWith("apply_patch") + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + }) + + describe("checkpoint only for fully successful patches (B1)", () => { + const mockedCheckpointSave = checkpointSave as MockedFunction + const deletePatch = `*** Begin Patch +*** Delete File: src/obsolete.ts +*** End Patch` + const addPatch = `*** Begin Patch +*** Add File: src/new.ts ++hello ++world +*** End Patch` + const updatePatch = `*** Begin Patch +*** Update File: src/test.ts +@@ +-original file content ++modified content +*** End Patch` + const updateNoDiffPatch = `*** Begin Patch +*** Update File: src/test.ts +@@ +-original file content ++original file content +*** End Patch` + const movePatch = `*** Begin Patch +*** Update File: src/test.ts +*** Move to: src/moved.ts +@@ +-original file content ++modified content +*** End Patch` + + it("does not record a checkpoint when the user rejects the patch", async () => { + // Rejected approval: the handler early-returns without recording a + // tool error, so the success flag must come from the handler itself. + mockAskApproval.mockResolvedValue(false) + + await tool.execute({ patch: deletePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith("Delete operation was rejected by the user.") + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the file to delete does not exist", async () => { + mockedFileExistsAtPath.mockResolvedValue(false) + + await tool.execute({ patch: deletePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("File not found")) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the delete write fails", async () => { + mockedFsPromises.default.unlink.mockRejectedValueOnce(new Error("EBUSY")) + + await tool.execute({ patch: deletePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Failed to delete file")) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the added file already exists", async () => { + // fileExistsAtPath resolves true by default in beforeEach. + + await tool.execute({ patch: addPatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("File already exists")) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the user rejects the add", async () => { + mockedFileExistsAtPath.mockResolvedValue(false) + mockAskApproval.mockResolvedValue(false) + + await tool.execute({ patch: addPatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith("Changes were rejected by the user.") + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("records a checkpoint when the add succeeds", async () => { + mockedFileExistsAtPath.mockResolvedValue(false) + + await tool.execute({ patch: addPatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith("File saved successfully") + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + }) + + it("does not record a checkpoint when the file to update does not exist", async () => { + mockedFileExistsAtPath.mockResolvedValue(false) + + await tool.execute({ patch: updatePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("File not found")) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("records a checkpoint when the update is a no-op (no changes needed)", async () => { + // A no-op change is not a failure, so the whole-patch checkpoint still runs. + + await tool.execute({ patch: updateNoDiffPatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("No changes needed")) + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + }) + + it("does not record a checkpoint when the user rejects the update", async () => { + mockAskApproval.mockResolvedValue(false) + + await tool.execute({ patch: updatePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith("Changes were rejected by the user.") + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the move destination is not allowed", async () => { + // First validateAccess call (source path, in the execute loop) passes; + // the move destination check inside the handler fails. + const validateAccess = ( + mockTask["rooIgnoreController"] as unknown as { validateAccess: MockedFunction<() => boolean> } + ).validateAccess + validateAccess.mockReturnValueOnce(true).mockReturnValue(false) + + await tool.execute({ patch: movePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockTask.say).toHaveBeenCalledWith("rooignore_error", "src/moved.ts") + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the move destination is write-protected", async () => { + // Source path check (execute loop) passes; the move destination fails. + const isWriteProtected = ( + mockTask["rooProtectedController"] as unknown as { + isWriteProtected: MockedFunction<(p: string) => boolean> + } + ).isWriteProtected + isWriteProtected.mockReturnValueOnce(false).mockReturnValue(true) + + await tool.execute({ patch: movePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith( + expect.stringContaining("Cannot move file to write-protected path"), + ) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the move destination is outside the workspace", async () => { + // Source path (first call) is inside; the move destination (second) + // call is outside the workspace. + mockedIsPathOutsideWorkspace.mockReturnValueOnce(false).mockReturnValue(true) + + await tool.execute({ patch: movePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith( + expect.stringContaining("Cannot move file to path outside workspace"), + ) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("records a checkpoint when the move succeeds", async () => { + await tool.execute({ patch: movePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + // path is platform-dependent (Windows resolves cwd to a drive path); + // assert on the written content instead. + expect(mockedFsPromises.default.writeFile).toHaveBeenCalledWith( + expect.any(String), + "modified content\n", + "utf8", + ) + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + }) + + it("records a checkpoint when the in-place update succeeds", async () => { + await tool.execute({ patch: updatePatch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockPushToolResult).toHaveBeenCalledWith("File saved successfully") + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + }) + }) }) diff --git a/src/core/tools/__tests__/editFileTool.spec.ts b/src/core/tools/__tests__/editFileTool.spec.ts index 1ff8d52a8d..5b645a0074 100644 --- a/src/core/tools/__tests__/editFileTool.spec.ts +++ b/src/core/tools/__tests__/editFileTool.spec.ts @@ -7,6 +7,7 @@ import { fileExistsAtPath } from "../../../utils/fs" import { isPathOutsideWorkspace } from "../../../utils/pathUtils" import { getReadablePath } from "../../../utils/path" import { ToolUse, ToolResponse, AskApproval, HandleError, PushToolResult } from "../../../shared/tools" +import { checkpointSave } from "../../checkpoints" import { editFileTool } from "../EditFileTool" vi.mock("fs/promises", () => ({ @@ -59,6 +60,10 @@ vi.mock("../../diff/stats", () => ({ computeDiffStats: vi.fn(() => ({ additions: 1, deletions: 1 })), })) +vi.mock("../../checkpoints", () => ({ + checkpointSave: vi.fn().mockResolvedValue(undefined), +})) + vi.mock("vscode", () => ({ window: { showWarningMessage: vi.fn().mockResolvedValue(undefined), @@ -774,4 +779,41 @@ describe("editFileTool", () => { expect(mockAskApproval).toHaveBeenCalled() }) }) + + describe("per-write checkpoints (B1)", () => { + const mockedCheckpointSave = checkpointSave as MockedFunction + + it("records one suppressed checkpoint after a successful edit (default-on)", async () => { + await executeEditFileTool({}) + + expect(mockTask.consecutiveMistakeCount).toBe(0) + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + expect(mockedCheckpointSave).toHaveBeenCalledWith(mockTask, false, true) + }) + + it("does not record a checkpoint when perWriteCheckpoints is disabled", async () => { + mockTask.providerRef.deref = vi.fn().mockReturnValue({ + getState: vi.fn().mockResolvedValue({ + diagnosticsEnabled: true, + writeDelayMs: 1000, + experiments: {}, + perWriteCheckpoints: false, + }), + }) + + await executeEditFileTool({}) + + expect(mockTask.consecutiveMistakeCount).toBe(0) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the edit fails", async () => { + mockTask.diffViewProvider.saveChanges.mockRejectedValue(new Error("save failed")) + + await executeEditFileTool({}) + + expect(mockHandleError).toHaveBeenCalledWith("edit_file", expect.any(Error)) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + }) }) diff --git a/src/core/tools/__tests__/writeToFileTool.spec.ts b/src/core/tools/__tests__/writeToFileTool.spec.ts index 52a7e3c052..f9286af36d 100644 --- a/src/core/tools/__tests__/writeToFileTool.spec.ts +++ b/src/core/tools/__tests__/writeToFileTool.spec.ts @@ -8,6 +8,7 @@ import { getReadablePath } from "../../../utils/path" import { unescapeHtmlEntities } from "../../../utils/text-normalization" import { everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" import { ToolUse, ToolResponse, AskApproval, HandleError, PushToolResult } from "../../../shared/tools" +import { checkpointSave } from "../../checkpoints" import { writeToFileTool } from "../WriteToFileTool" vi.mock("path", async () => { @@ -89,6 +90,10 @@ vi.mock("../../ignore/RooIgnoreController", () => ({ }, })) +vi.mock("../../checkpoints", () => ({ + checkpointSave: vi.fn().mockResolvedValue(undefined), +})) + describe("writeToFileTool", () => { // Test data const testFilePath = "test/file.txt" @@ -472,4 +477,76 @@ describe("writeToFileTool", () => { expect(mockHandleError).toHaveBeenCalledWith("handling partial write_to_file", expect.any(Error)) }) }) + + describe("per-write checkpoints (B1)", () => { + const mockedCheckpointSave = checkpointSave as MockedFunction + + it("records one suppressed checkpoint after a successful write (default-on)", async () => { + await executeWriteFileTool({}) + + expect(mockedCheckpointSave).toHaveBeenCalledOnce() + expect(mockedCheckpointSave).toHaveBeenCalledWith(mockCline, false, true) + }) + + it("does not record a checkpoint when perWriteCheckpoints is disabled", async () => { + mockCline.providerRef.deref = vi.fn().mockReturnValue({ + getState: vi + .fn() + .mockResolvedValue({ diagnosticsEnabled: true, writeDelayMs: 1000, perWriteCheckpoints: false }), + }) + + await executeWriteFileTool({}) + + expect(mockCline.consecutiveMistakeCount).toBe(0) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("does not record a checkpoint when the write fails", async () => { + mockCline.diffViewProvider.open.mockRejectedValue(new Error("write failed")) + + await executeWriteFileTool({}) + + expect(mockHandleError).toHaveBeenCalledWith("writing file", expect.any(Error)) + expect(mockedCheckpointSave).not.toHaveBeenCalled() + }) + + it("waits for the per-write checkpoint before the tool completes", async () => { + let checkpointStarted = false + let releaseCheckpoint: () => void = () => {} + mockedCheckpointSave.mockImplementationOnce(() => { + checkpointStarted = true + return new Promise((resolve) => { + releaseCheckpoint = () => resolve(undefined) + }) + }) + const processQueuedSpy = vi.fn() + mockCline.processQueuedMessages = processQueuedSpy + + const toolPromise = executeWriteFileTool({}) + + // Advance microtasks until the tool reaches the checkpoint call (all + // preceding awaits are mocked resolutions, no real timers involved). + for (let i = 0; i < 50 && !checkpointStarted; i++) { + await Promise.resolve() + } + expect(checkpointStarted).toBe(true) + + let settled = false + void toolPromise.then(() => { + settled = true + }) + + // The tool must not complete while the checkpoint is still + // staging/committing: a later write started by the task loop would + // otherwise collapse into the same (or a missing) commit. + await new Promise((resolve) => setTimeout(resolve, 20)) + expect(settled).toBe(false) + expect(processQueuedSpy).not.toHaveBeenCalled() + + releaseCheckpoint() + await toolPromise + expect(settled).toBe(true) + expect(processQueuedSpy).toHaveBeenCalledOnce() + }) + }) }) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 4621cb3fc4..481904ed57 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -51,6 +51,7 @@ import { ORGANIZATION_ALLOW_ALL, DEFAULT_MODES, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, + DEFAULT_PER_WRITE_CHECKPOINTS, getModelId, isRetiredProvider, providerIdentifiers, @@ -2556,6 +2557,7 @@ export class ClineProvider ttsSpeed, enableCheckpoints, checkpointTimeout, + perWriteCheckpoints, soundVolume, writeDelayMs, diffFuzzyThreshold, @@ -2715,6 +2717,7 @@ export class ClineProvider ttsSpeed: ttsSpeed ?? 1.0, enableCheckpoints: enableCheckpoints ?? true, checkpointTimeout: checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, + perWriteCheckpoints: perWriteCheckpoints ?? DEFAULT_PER_WRITE_CHECKPOINTS, shouldShowAnnouncement: telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId, allowedCommands: mergedAllowedCommands, @@ -2951,6 +2954,7 @@ export class ClineProvider ttsSpeed: stateValues.ttsSpeed ?? 1.0, enableCheckpoints: stateValues.enableCheckpoints ?? true, checkpointTimeout: stateValues.checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, + perWriteCheckpoints: stateValues.perWriteCheckpoints ?? DEFAULT_PER_WRITE_CHECKPOINTS, soundVolume: stateValues.soundVolume, writeDelayMs: stateValues.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS, diffFuzzyThreshold: stateValues.diffFuzzyThreshold ?? DEFAULT_DIFF_FUZZY_THRESHOLD, diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 731124cccc..7ec6bb782b 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -720,6 +720,7 @@ describe("ClineProvider", () => { soundEnabled: false, ttsEnabled: false, enableCheckpoints: false, + perWriteCheckpoints: false, writeDelayMs: 1000, mcpEnabled: true, mode: defaultModeSlug, @@ -1401,6 +1402,48 @@ describe("ClineProvider", () => { expect(state.destructiveCommandGuardEnabled).toBe(false) }) + test("getState returns the saved per-write checkpoints setting", async () => { + await provider.contextProxy.setValue("perWriteCheckpoints", false) + + const state = await provider.getState() + + expect(state.perWriteCheckpoints).toBe(false) + }) + + test("getState defaults per-write checkpoints to true when unset", async () => { + const state = await provider.getState() + + expect(state.perWriteCheckpoints).toBe(true) + }) + + test("getStateToPostToWebview returns the saved per-write checkpoints setting", async () => { + await provider.resolveWebviewView(mockWebviewView) + await provider.contextProxy.setValue("perWriteCheckpoints", true) + + const state = await provider.getStateToPostToWebview() + + expect(state.perWriteCheckpoints).toBe(true) + }) + + test("getStateToPostToWebview returns false when per-write checkpoints is saved as false", async () => { + // The default is also true, so only an explicit false proves that the + // stored value (rather than the default) reaches the webview state. + await provider.resolveWebviewView(mockWebviewView) + await provider.contextProxy.setValue("perWriteCheckpoints", false) + + const state = await provider.getStateToPostToWebview() + + expect(state.perWriteCheckpoints).toBe(false) + }) + + test("getStateToPostToWebview defaults per-write checkpoints to true when unset", async () => { + await provider.resolveWebviewView(mockWebviewView) + + const state = await provider.getStateToPostToWebview() + + expect(state.perWriteCheckpoints).toBe(true) + }) + test("language is set to VSCode language", async () => { // Mock VSCode language as Spanish ;(vscode.env as any).language = "pt-BR" diff --git a/webview-ui/src/components/settings/CheckpointSettings.tsx b/webview-ui/src/components/settings/CheckpointSettings.tsx index dd28f6615f..7ea12ef873 100644 --- a/webview-ui/src/components/settings/CheckpointSettings.tsx +++ b/webview-ui/src/components/settings/CheckpointSettings.tsx @@ -13,17 +13,20 @@ import { DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, MAX_CHECKPOINT_TIMEOUT_SECONDS, MIN_CHECKPOINT_TIMEOUT_SECONDS, + DEFAULT_PER_WRITE_CHECKPOINTS, } from "@roo-code/types" type CheckpointSettingsProps = HTMLAttributes & { enableCheckpoints?: boolean checkpointTimeout?: number - setCachedStateField: SetCachedStateField<"enableCheckpoints" | "checkpointTimeout"> + perWriteCheckpoints?: boolean + setCachedStateField: SetCachedStateField<"enableCheckpoints" | "checkpointTimeout" | "perWriteCheckpoints"> } export const CheckpointSettings = ({ enableCheckpoints, checkpointTimeout, + perWriteCheckpoints, setCachedStateField, ...props }: CheckpointSettingsProps) => { @@ -33,6 +36,22 @@ export const CheckpointSettings = ({ {t("settings:sections.checkpoints")}
+ + { + setCachedStateField("perWriteCheckpoints", e.target.checked) + }}> + {t("settings:checkpoints.perWrite.label")} + +
+ {t("settings:checkpoints.perWrite.description")} +
+
+ (({ onDone, t autoCondenseContextPercent, enableCheckpoints, checkpointTimeout, + perWriteCheckpoints, experiments, maxOpenTabsContext, maxWorkspaceFiles, @@ -410,6 +412,7 @@ const SettingsView = forwardRef(({ onDone, t ttsSpeed, enableCheckpoints: enableCheckpoints ?? false, checkpointTimeout: checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, + perWriteCheckpoints: perWriteCheckpoints ?? DEFAULT_PER_WRITE_CHECKPOINTS, writeDelayMs, diffFuzzyThreshold, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? 30_000, @@ -847,6 +850,7 @@ const SettingsView = forwardRef(({ onDone, t )} diff --git a/webview-ui/src/components/settings/__tests__/CheckpointSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/CheckpointSettings.spec.tsx new file mode 100644 index 0000000000..b08c15ace7 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/CheckpointSettings.spec.tsx @@ -0,0 +1,125 @@ +// npx vitest src/components/settings/__tests__/CheckpointSettings.spec.tsx + +import { render, screen, fireEvent } from "@/utils/test-utils" +import { CheckpointSettings } from "../CheckpointSettings" + +// Mock the translation hook +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => { + if (key === "settings:checkpoints.perWrite.label") { + return "Checkpoint after each file write" + } + if (key === "settings:checkpoints.perWrite.description") { + return "Record a checkpoint snapshot after every successful file write by the agent" + } + return key + }, + }), +})) + +// Mock the UI components (async factory: vi.importActual resolves asynchronously). +vi.mock("@/components/ui", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + Slider: ({ defaultValue, onValueChange, "data-testid": dataTestId }: any) => ( + onValueChange?.([100])} + data-testid={dataTestId} + role="slider" + /> + ), + } +}) + +// Mock vscode utilities +vi.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +// Mock VSCode components to behave like standard HTML elements +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeCheckbox: ({ checked, onChange, children, ...props }: any) => ( + + ), + VSCodeLink: ({ children, ...props }: any) => {children}, +})) + +describe("CheckpointSettings", () => { + const setCachedStateField = vi.fn() + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("renders the per-write checkpoints checkbox checked by default when the value is unset", () => { + render() + + const checkbox = screen.getByRole("checkbox", { name: "Checkpoint after each file write" }) + expect(checkbox).toBeChecked() + }) + + it("unchecks the per-write checkpoints checkbox when the saved value is false", () => { + render( + , + ) + + const checkbox = screen.getByRole("checkbox", { name: "Checkpoint after each file write" }) + expect(checkbox).not.toBeChecked() + }) + + it("keeps the per-write checkpoints checkbox checked when the saved value is true", () => { + render( + , + ) + + const checkbox = screen.getByRole("checkbox", { name: "Checkpoint after each file write" }) + expect(checkbox).toBeChecked() + }) + + it("caches a toggle to enable per-write checkpoints when the user checks the box", () => { + render( + , + ) + + const checkbox = screen.getByRole("checkbox", { name: "Checkpoint after each file write" }) + fireEvent.click(checkbox) + + expect(setCachedStateField).toHaveBeenCalledWith("perWriteCheckpoints", true) + }) + + it("caches a toggle to disable per-write checkpoints when the user unchecks the box", () => { + render() + + const checkbox = screen.getByRole("checkbox", { name: "Checkpoint after each file write" }) + fireEvent.click(checkbox) + + expect(setCachedStateField).toHaveBeenCalledWith("perWriteCheckpoints", false) + }) +}) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 377c8eb721..203594275d 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -211,6 +211,7 @@ const createInitialExtensionState = (): ExtensionState => ({ ttsEnabled: false, ttsSpeed: 1.0, enableCheckpoints: true, + perWriteCheckpoints: true, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Default to 15 seconds language: "en", // Default language code writeDelayMs: 1000, diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 23ac911585..f0655b55e0 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -407,6 +407,7 @@ describe("mergeExtensionState", () => { taskHistory: [], shouldShowAnnouncement: false, enableCheckpoints: true, + perWriteCheckpoints: true, writeDelayMs: 1000, mode: "default", experiments: {} as Record, @@ -477,6 +478,7 @@ describe("mergeExtensionState", () => { taskHistory: [], shouldShowAnnouncement: false, enableCheckpoints: true, + perWriteCheckpoints: true, writeDelayMs: 1000, mode: "default", experiments: {} as Record, diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 52805e74e8..dca198cc7b 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Habilitar punts de control automàtics", "description": "Quan està habilitat, Zoo crearà automàticament punts de control durant l'execució de tasques, facilitant la revisió de canvis o la reversió a estats anteriors. <0>Més informació" + }, + "perWrite": { + "label": "Punt de control després de cada escriptura de fitxer", + "description": "Registra una instantània de punt de control després de cada escriptura de fitxer reeixida de l’agent" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index b895717422..530c799733 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Automatische Kontrollpunkte aktivieren", "description": "Wenn aktiviert, erstellt Zoo automatisch Kontrollpunkte während der Aufgabenausführung, was die Überprüfung von Änderungen oder die Rückkehr zu früheren Zuständen erleichtert. <0>Mehr erfahren" + }, + "perWrite": { + "label": "Kontrollpunkt nach jedem Dateischreibvorgang", + "description": "Ein Kontrollpunkt-Snapshot wird nach jedem erfolgreichen Dateischreibvorgang des Agents erfasst" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index eaa37b7034..5f8b40ade2 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -781,6 +781,10 @@ "enable": { "label": "Enable automatic checkpoints", "description": "When enabled, Zoo will automatically create checkpoints during task execution, making it easy to review changes or revert to earlier states. <0>Learn more" + }, + "perWrite": { + "label": "Checkpoint after each file write", + "description": "Record a checkpoint snapshot after every successful file write by the agent" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index abb8a60609..704f8d1d31 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Habilitar puntos de control automáticos", "description": "Cuando está habilitado, Zoo creará automáticamente puntos de control durante la ejecución de tareas, facilitando la revisión de cambios o la reversión a estados anteriores. <0>Más información" + }, + "perWrite": { + "label": "Punto de control después de cada escritura de archivo", + "description": "Registra una instantánea de punto de control después de cada escritura de archivo exitosa del agente" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 272f21a6ee..5db4e0dfe7 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Activer les points de contrôle automatiques", "description": "Lorsque cette option est activée, Zoo créera automatiquement des points de contrôle pendant l'exécution des tâches, facilitant la révision des modifications ou le retour à des états antérieurs. <0>En savoir plus" + }, + "perWrite": { + "label": "Point de contrôle après chaque écriture de fichier", + "description": "Enregistre un instantané de point de contrôle après chaque écriture de fichier réussie par l’agent" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0a4152b17a..4570f85117 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "स्वचालित चेकपॉइंट सक्षम करें", "description": "जब सक्षम होता है, तो Zoo कार्य निष्पादन के दौरान स्वचालित रूप से चेकपॉइंट बनाएगा, जिससे परिवर्तनों की समीक्षा करना या पहले की स्थितियों पर वापस जाना आसान हो जाएगा। <0>अधिक जानें" + }, + "perWrite": { + "label": "हर फ़ाइल लिखने के बाद चेकपॉइंट", + "description": "एजेंट द्वारा हर सफल फ़ाइल लिखने के बाद एक चेकपॉइंट स्नैपशॉट दर्ज किया जाता है" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index b8abe9ab25..2918f14870 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Aktifkan checkpoint otomatis", "description": "Ketika diaktifkan, Zoo akan secara otomatis membuat checkpoint selama eksekusi tugas, memudahkan untuk meninjau perubahan atau kembali ke state sebelumnya. <0>Pelajari lebih lanjut" + }, + "perWrite": { + "label": "Checkpoint setelah setiap penulisan file", + "description": "Merekam snapshot checkpoint setelah setiap penulisan file yang berhasil oleh agen" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index e49ede9cec..6909e6a1a2 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Abilita punti di controllo automatici", "description": "Quando abilitato, Zoo creerà automaticamente punti di controllo durante l'esecuzione dei compiti, facilitando la revisione delle modifiche o il ritorno a stati precedenti. <0>Scopri di più" + }, + "perWrite": { + "label": "Punto di controllo dopo ogni scrittura del file", + "description": "Registra uno snapshot di punto di controllo dopo ogni scrittura del file riuscita dell’agente" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index d58c86c95d..6deaeca4d6 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "自動チェックポイントを有効化", "description": "有効にすると、Zooはタスク実行中に自動的にチェックポイントを作成し、変更の確認や以前の状態への復帰を容易にします。 <0>詳細情報" + }, + "perWrite": { + "label": "ファイルの書き込みごとにチェックポイント", + "description": "エージェントによる各ファイルの書き込み成功後にチェックポイントのスナップショットを記録します" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 68ce8b2523..a6c6583347 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "자동 체크포인트 활성화", "description": "활성화되면 Zoo는 작업 실행 중에 자동으로 체크포인트를 생성하여 변경 사항을 검토하거나 이전 상태로 되돌리기 쉽게 합니다. <0>더 알아보기" + }, + "perWrite": { + "label": "파일을 쓸 때마다 체크포인트", + "description": "에이전트가 파일 쓰기에 성공할 때마다 체크포인트 스냅샷을 기록합니다" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 8d90d7747e..77f6f5797b 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Automatische checkpoints inschakelen", "description": "Indien ingeschakeld, maakt Zoo automatisch checkpoints tijdens het uitvoeren van taken, zodat je eenvoudig wijzigingen kunt bekijken of terugzetten. <0>Meer informatie" + }, + "perWrite": { + "label": "Checkpoint na elke bestandsschrijving", + "description": "Neemt een checkpoint-snapshot op na elke succesvolle bestandsschrijving door de agent" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index ffc1cdf1a4..49b341cb9f 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Włącz automatyczne punkty kontrolne", "description": "Gdy włączone, Zoo automatycznie utworzy punkty kontrolne podczas wykonywania zadań, ułatwiając przeglądanie zmian lub powrót do wcześniejszych stanów. <0>Dowiedz się więcej" + }, + "perWrite": { + "label": "Punkt kontrolny po każdym zapisaniu pliku", + "description": "Rejestruje migawkę punktu kontrolnego po każdym udanym zapisaniu pliku przez agenta" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index cf92b76ac7..71694fe203 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Ativar pontos de verificação automáticos", "description": "Quando ativado, o Zoo criará automaticamente pontos de verificação durante a execução de tarefas, facilitando a revisão de alterações ou o retorno a estados anteriores. <0>Saiba mais" + }, + "perWrite": { + "label": "Ponto de verificação após cada gravação de arquivo", + "description": "Registra um snapshot de ponto de verificação após cada gravação de arquivo bem-sucedida pelo agente" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 23ff32faa9..e9a3f5938b 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Включить автоматические контрольные точки", "description": "Если включено, Zoo будет автоматически создавать контрольные точки во время выполнения задач, что упрощает просмотр изменений или возврат к предыдущим состояниям. <0>Подробнее" + }, + "perWrite": { + "label": "Контрольная точка после каждой записи файла", + "description": "Записывает снимок контрольной точки после каждой успешной записи файла агентом" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index f674e116d2..aff7402428 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Otomatik kontrol noktalarını etkinleştir", "description": "Etkinleştirildiğinde, Zoo görev yürütme sırasında otomatik olarak kontrol noktaları oluşturarak değişiklikleri gözden geçirmeyi veya önceki durumlara dönmeyi kolaylaştırır. <0>Daha fazla bilgi" + }, + "perWrite": { + "label": "Her dosya yazımından sonra kontrol noktası", + "description": "Ajanın her başarılı dosya yazımından sonra bir kontrol noktası görüntüsü kaydeder" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 4b908ca658..e944a56780 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "Bật điểm kiểm tra tự động", "description": "Khi được bật, Zoo sẽ tự động tạo các điểm kiểm tra trong quá trình thực hiện nhiệm vụ, giúp dễ dàng xem lại các thay đổi hoặc quay lại trạng thái trước đó. <0>Tìm hiểu thêm" + }, + "perWrite": { + "label": "Điểm kiểm tra sau mỗi lần ghi file", + "description": "Ghi lại ảnh chụp nhanh điểm kiểm tra sau mỗi lần ghi file thành công của agent" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index d79edca302..05d04463e1 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -701,6 +701,10 @@ "enable": { "label": "启用自动存档点", "description": "开启后自动创建任务存档点,方便回溯修改。 <0>了解更多" + }, + "perWrite": { + "label": "每次文件写入后创建存档点", + "description": "智能体每次成功写入文件后都会记录一个存档点快照" } }, "notifications": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 250cc2111b..a2ef47b841 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -728,6 +728,10 @@ "enable": { "label": "啟用自動檢查點", "description": "啟用後,Zoo 將在工作執行期間自動建立檢查點,方便檢視變更或回到較早的狀態。 <0>了解更多" + }, + "perWrite": { + "label": "每次檔案寫入後建立檢查點", + "description": "代理每次成功寫入檔案後都會記錄一個檢查點快照" } }, "notifications": { From b98f15903e1e026f2a6d004dc44ec7a7b25a63e2 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Fri, 28 Aug 2026 10:10:47 +0800 Subject: [PATCH 2/4] fix(fws): export the webview initializer and test the pre-hydration checkpoint default createInitialExtensionState is exported and a focused test asserts it initializes perWriteCheckpoints to true: a regression that dropped the key from the initializer (rather than from a merge fixture) would otherwise stay hidden (CodeRabbit finding on trial #1413). --- .../src/context/ExtensionStateContext.tsx | 2 +- .../__tests__/ExtensionStateContext.spec.tsx | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 203594275d..f1cddc3cf7 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -196,7 +196,7 @@ export const mergeExtensionState = (prevState: ExtensionState, newState: Partial } } -const createInitialExtensionState = (): ExtensionState => ({ +export const createInitialExtensionState = (): ExtensionState => ({ apiConfiguration: {}, version: "", clineMessages: [], diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index f0655b55e0..9bc77489bc 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -13,7 +13,12 @@ import { DEFAULT_DIFF_FUZZY_THRESHOLD, } from "@roo-code/types" -import { ExtensionStateContextProvider, useExtensionState, mergeExtensionState } from "../ExtensionStateContext" +import { + ExtensionStateContextProvider, + useExtensionState, + mergeExtensionState, + createInitialExtensionState, +} from "../ExtensionStateContext" const TestComponent = () => { const { allowedCommands, setAllowedCommands, soundEnabled, showRooIgnoredFiles, setShowRooIgnoredFiles } = @@ -396,6 +401,16 @@ describe("ExtensionStateContext", () => { }), ) }) + + it("initializes the per-write checkpoint default before hydration", () => { + // The initializer itself (not a merge fixture) must carry the per-write + // checkpoint default: a regression that dropped it from + // createInitialExtensionState would otherwise stay hidden because the + // merge tests supply the key manually. + const state = createInitialExtensionState() + + expect(state.perWriteCheckpoints).toBe(true) + }) }) describe("mergeExtensionState", () => { From abc10b43e9483e8b8b878e8737d2e7726e7c8537 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Sun, 30 Aug 2026 12:33:23 +0800 Subject: [PATCH 3/4] =?UTF-8?q?chore(ci):=20empty=20commit=20=E2=80=94=20r?= =?UTF-8?q?e-trigger=20CI=20and=20the=20CodeRabbit=20current-head=20review?= =?UTF-8?q?=20gate=20(no=20code=20change)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 23e945144ccb1724ac7d1001138ca0825853ba6e Mon Sep 17 00:00:00 2001 From: easonLiangWorldedtech Date: Tue, 29 Sep 2026 08:35:26 +0800 Subject: [PATCH 4/4] test(visual): regenerate electron chat-dark sidebar reference for #1404 The extension-host visual smoke scenario now renders the fws per-step change-card/rollback buttons ("See New Changes" / "Restore Changes") below the Task Completed card. The committed reference predates the feature (main-era render) and fails with a deterministic 432px diff (stable across repeats, same render on B1 and B2 heads). Reference regenerated from this head's CI render (visual job artifact); the diff is exactly the two-button region and nothing else. Main's reference will need the same regeneration once the fws units land. --- .../electron-chat-dark-sidebar.png | Bin 30321 -> 33176 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/vscode-e2e/src/visual/__screenshots__/electron-chat-dark-sidebar.png b/apps/vscode-e2e/src/visual/__screenshots__/electron-chat-dark-sidebar.png index e8497070ef3440fe4a3bbb3ef49f9a1de9d4c4d9..a27af05b17ee1787a3a13f48cf2a39fc7fa9011e 100644 GIT binary patch literal 33176 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 ACjbBd literal 30321 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