From 7baefefec6cc7ff6fa413aa0001d05166fe15369 Mon Sep 17 00:00:00 2001 From: Will Li Date: Wed, 23 Jul 2025 01:20:46 -0700 Subject: [PATCH] reduce checkpoint changes --- src/core/task/Task.ts | 7 +- src/core/webview/ClineProvider.ts | 7 - .../checkpointRestoreHandler.spec.ts | 1 - .../webviewMessageHandler.checkpoint.spec.ts | 19 +- src/core/webview/checkpointRestoreHandler.ts | 1 - src/core/webview/webviewMessageHandler.ts | 166 ++++++++---------- 6 files changed, 85 insertions(+), 116 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6c6e950613..0b4ca93d7c 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -181,7 +181,6 @@ export class Task extends EventEmitter { // LLM Messages & Chat Messages apiConversationHistory: ApiMessage[] = [] clineMessages: ClineMessage[] = [] - public pendingUserMessageCheckpoint?: Record // Ask private askResponse?: ClineAskResponse @@ -718,17 +717,13 @@ export class Task extends EventEmitter { } if (type === "user_feedback") { - // Automatically use and clear the pending checkpoint for user_feedback messages - const feedbackCheckpoint = checkpoint || this.pendingUserMessageCheckpoint - this.pendingUserMessageCheckpoint = undefined // Clear it after use - await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, - checkpoint: feedbackCheckpoint, + checkpoint, contextCondense, }) } else { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 094539e5df..5d8b70a28b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -87,7 +87,6 @@ interface PendingEditOperation { images?: string[] messageIndex: number apiConversationHistoryIndex: number - originalCheckpoint: { hash: string } timeoutId: NodeJS.Timeout createdAt: number } @@ -268,7 +267,6 @@ export class ClineProvider images?: string[] messageIndex: number apiConversationHistoryIndex: number - originalCheckpoint: { hash: string } }, ): void { // Clear any existing operation with the same ID @@ -720,11 +718,6 @@ export class ClineProvider ) } - // If there was an original checkpoint, preserve it for the new message - if (pendingEdit.originalCheckpoint) { - cline.pendingUserMessageCheckpoint = pendingEdit.originalCheckpoint - } - // Process the edited message await cline.handleWebviewAskResponse( "messageResponse", diff --git a/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts b/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts index a1454272c3..d321449f20 100644 --- a/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts +++ b/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts @@ -134,7 +134,6 @@ describe("checkpointRestoreHandler", () => { images: ["image1.png"], messageIndex: 2, apiConversationHistoryIndex: 2, - originalCheckpoint: { hash: "abc123" }, }) // Verify checkpoint restore was called with edit operation diff --git a/src/core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts index 5db10da16f..6a22632136 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts @@ -27,21 +27,14 @@ describe("webviewMessageHandler - checkpoint operations", () => { taskId: "test-task-123", clineMessages: [ { ts: 1, type: "user", say: "user", text: "First message" }, - { ts: 2, type: "assistant", say: "assistant", text: "Response" }, - { - ts: 3, - type: "user", - say: "user", - text: "Checkpoint message", - checkpoint: { hash: "abc123" }, - }, - { ts: 4, type: "assistant", say: "assistant", text: "After checkpoint" }, + { ts: 2, type: "assistant", say: "checkpoint_saved", text: "abc123" }, + { ts: 3, type: "user", say: "user", text: "Message to delete" }, + { ts: 4, type: "assistant", say: "assistant", text: "After message" }, ], apiConversationHistory: [ { ts: 1, role: "user", content: [{ type: "text", text: "First message" }] }, - { ts: 2, role: "assistant", content: [{ type: "text", text: "Response" }] }, - { ts: 3, role: "user", content: [{ type: "text", text: "Checkpoint message" }] }, - { ts: 4, role: "assistant", content: [{ type: "text", text: "After checkpoint" }] }, + { ts: 3, role: "user", content: [{ type: "text", text: "Message to delete" }] }, + { ts: 4, role: "assistant", content: [{ type: "text", text: "After message" }] }, ], checkpointRestore: vi.fn(), overwriteClineMessages: vi.fn(), @@ -130,7 +123,7 @@ describe("webviewMessageHandler - checkpoint operations", () => { editData: { editedContent: "Edited checkpoint message", images: undefined, - apiConversationHistoryIndex: 2, + apiConversationHistoryIndex: 1, }, }) }) diff --git a/src/core/webview/checkpointRestoreHandler.ts b/src/core/webview/checkpointRestoreHandler.ts index ec9ab0c94f..3aa11d3c23 100644 --- a/src/core/webview/checkpointRestoreHandler.ts +++ b/src/core/webview/checkpointRestoreHandler.ts @@ -51,7 +51,6 @@ export async function handleCheckpointRestoreOperation(config: CheckpointRestore images: editData.images, messageIndex: config.messageIndex, apiConversationHistoryIndex: editData.apiConversationHistoryIndex, - originalCheckpoint: checkpoint, }) } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 4595e59f70..346c469ab2 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -102,18 +102,18 @@ export const webviewMessageHandler = async ( * Handles message deletion operations with user confirmation */ const handleDeleteOperation = async (messageTs: number): Promise => { - // Check if the message has a checkpoint + // Check if there's a checkpoint before this message const currentCline = provider.getCurrentCline() let hasCheckpoint = false if (currentCline) { const { messageIndex } = findMessageIndices(messageTs, currentCline) if (messageIndex !== -1) { - const targetMessage = currentCline.clineMessages[messageIndex] - hasCheckpoint = !!( - targetMessage?.checkpoint && - typeof targetMessage.checkpoint === "object" && - "hash" in targetMessage.checkpoint - ) + // Find the last checkpoint before this message + const checkpoints = currentCline.clineMessages + .filter((msg) => msg.say === "checkpoint_saved" && msg.ts < messageTs) + .sort((a, b) => b.ts - a.ts) + + hasCheckpoint = checkpoints.length > 0 } else { console.log("[webviewMessageHandler] Message not found! Looking for ts:", messageTs) } @@ -149,16 +149,29 @@ export const webviewMessageHandler = async ( try { const targetMessage = currentCline.clineMessages[messageIndex] - // If checkpoint restoration is requested, restore to the checkpoint first - if (restoreCheckpoint && hasValidCheckpoint(targetMessage)) { - await handleCheckpointRestoreOperation({ - provider, - currentCline, - messageTs: targetMessage.ts!, - messageIndex, - checkpoint: targetMessage.checkpoint as ValidCheckpoint, - operation: "delete", - }) + // If checkpoint restoration is requested, find and restore to the last checkpoint before this message + if (restoreCheckpoint) { + // Find the last checkpoint before this message + const checkpoints = currentCline.clineMessages + .filter((msg) => msg.say === "checkpoint_saved" && msg.ts < messageTs) + .sort((a, b) => b.ts - a.ts) + + const lastCheckpoint = checkpoints[0] + + if (lastCheckpoint && lastCheckpoint.text) { + await handleCheckpointRestoreOperation({ + provider, + currentCline, + messageTs: targetMessage.ts!, + messageIndex, + checkpoint: { hash: lastCheckpoint.text }, + operation: "delete", + }) + } else { + // No checkpoint found before this message + console.log("[handleDeleteMessageConfirm] No checkpoint found before message") + vscode.window.showWarningMessage("No checkpoint found before this message") + } } else { // For non-checkpoint deletes, preserve checkpoint associations for remaining messages // Store checkpoints from messages that will be preserved @@ -200,43 +213,33 @@ export const webviewMessageHandler = async ( * Handles message editing operations with user confirmation */ const handleEditOperation = async (messageTs: number, editedContent: string, images?: string[]): Promise => { - // Always check if the message has a checkpoint first + // Check if there's a checkpoint before this message const currentCline = provider.getCurrentCline() let hasCheckpoint = false if (currentCline) { const { messageIndex } = findMessageIndices(messageTs, currentCline) if (messageIndex !== -1) { - const targetMessage = currentCline.clineMessages[messageIndex] - hasCheckpoint = !!( - targetMessage?.checkpoint && - typeof targetMessage.checkpoint === "object" && - "hash" in targetMessage.checkpoint - ) + // Find the last checkpoint before this message + const checkpoints = currentCline.clineMessages + .filter((msg) => msg.say === "checkpoint_saved" && msg.ts < messageTs) + .sort((a, b) => b.ts - a.ts) + + hasCheckpoint = checkpoints.length > 0 } else { console.log("[webviewMessageHandler] Edit - Message not found in clineMessages!") } } else { console.log("[webviewMessageHandler] Edit - No currentCline available!") } - // If there's a checkpoint, show the checkpoint dialog even when skipping confirmation - if (hasCheckpoint) { - await provider.postMessageToWebview({ - type: "showEditMessageDialog", - messageTs, - text: editedContent, - hasCheckpoint, - images, - }) - } else { - // Send message to webview to show edit confirmation dialog - await provider.postMessageToWebview({ - type: "showEditMessageDialog", - messageTs, - text: editedContent, - hasCheckpoint, - images, - }) - } + + // Send message to webview to show edit confirmation dialog + await provider.postMessageToWebview({ + type: "showEditMessageDialog", + messageTs, + text: editedContent, + hasCheckpoint, + images, + }) } /** @@ -267,27 +270,38 @@ export const webviewMessageHandler = async ( try { const targetMessage = currentCline.clineMessages[messageIndex] - // Preserve the original checkpoint data for the edited message - const originalCheckpoint = targetMessage?.checkpoint + // If checkpoint restoration is requested, find and restore to the last checkpoint before this message + if (restoreCheckpoint) { + // Find the last checkpoint before this message + const checkpoints = currentCline.clineMessages + .filter((msg) => msg.say === "checkpoint_saved" && msg.ts < messageTs) + .sort((a, b) => b.ts - a.ts) - // If checkpoint restoration is requested, restore to the checkpoint first - if (restoreCheckpoint && hasValidCheckpoint(targetMessage)) { - await handleCheckpointRestoreOperation({ - provider, - currentCline, - messageTs: targetMessage.ts!, - messageIndex, - checkpoint: targetMessage.checkpoint as ValidCheckpoint, - operation: "edit", - editData: { - editedContent, - images, - apiConversationHistoryIndex, - }, - }) - // The task will be cancelled and reinitialized by checkpointRestore - // The pending edit will be processed in the reinitialized task - return + const lastCheckpoint = checkpoints[0] + + if (lastCheckpoint && lastCheckpoint.text) { + await handleCheckpointRestoreOperation({ + provider, + currentCline, + messageTs: targetMessage.ts!, + messageIndex, + checkpoint: { hash: lastCheckpoint.text }, + operation: "edit", + editData: { + editedContent, + images, + apiConversationHistoryIndex, + }, + }) + // The task will be cancelled and reinitialized by checkpointRestore + // The pending edit will be processed in the reinitialized task + return + } else { + // No checkpoint found before this message + console.log("[handleEditMessageConfirm] No checkpoint found before message") + vscode.window.showWarningMessage("No checkpoint found before this message") + // Continue with non-checkpoint edit + } } // For non-checkpoint edits, preserve checkpoint associations for remaining messages @@ -319,12 +333,6 @@ export const webviewMessageHandler = async ( }) // Process the edited message as a regular user message - // Preserve the original checkpoint for the new message - if (originalCheckpoint) { - // Store the checkpoint to be attached to the new message - currentCline.pendingUserMessageCheckpoint = originalCheckpoint - } - webviewMessageHandler(provider, { type: "askResponse", askResponse: "messageResponse", @@ -495,25 +503,7 @@ export const webviewMessageHandler = async ( await provider.postStateToWebview() break case "askResponse": - // Save checkpoint BEFORE processing the user message if checkpoints are enabled - const currentCline = provider.getCurrentCline() - if (currentCline && currentCline.enableCheckpoints && message.askResponse === "messageResponse") { - try { - const checkpointResult = await currentCline.checkpointSave(true) // Force checkpoint save - if (checkpointResult?.commit) { - // Store checkpoint data temporarily to be used when creating the user_feedback message - currentCline.pendingUserMessageCheckpoint = { - hash: checkpointResult.commit, - timestamp: Date.now(), - type: "user_message", - } - } - } catch (error) { - console.error("[webviewMessageHandler] Failed to save checkpoint before user message:", error) - } - } - - currentCline?.handleWebviewAskResponse(message.askResponse!, message.text, message.images) + provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images) break case "autoCondenseContext": await updateGlobalState("autoCondenseContext", message.bool)