reduce checkpoint changes

This commit is contained in:
Will Li 2025-07-23 01:20:46 -07:00
parent ab46f94c26
commit 7baefefec6
6 changed files with 85 additions and 116 deletions

View file

@ -181,7 +181,6 @@ export class Task extends EventEmitter<ClineEvents> {
// LLM Messages & Chat Messages
apiConversationHistory: ApiMessage[] = []
clineMessages: ClineMessage[] = []
public pendingUserMessageCheckpoint?: Record<string, unknown>
// Ask
private askResponse?: ClineAskResponse
@ -718,17 +717,13 @@ export class Task extends EventEmitter<ClineEvents> {
}
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 {

View file

@ -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",

View file

@ -134,7 +134,6 @@ describe("checkpointRestoreHandler", () => {
images: ["image1.png"],
messageIndex: 2,
apiConversationHistoryIndex: 2,
originalCheckpoint: { hash: "abc123" },
})
// Verify checkpoint restore was called with edit operation

View file

@ -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,
},
})
})

View file

@ -51,7 +51,6 @@ export async function handleCheckpointRestoreOperation(config: CheckpointRestore
images: editData.images,
messageIndex: config.messageIndex,
apiConversationHistoryIndex: editData.apiConversationHistoryIndex,
originalCheckpoint: checkpoint,
})
}

View file

@ -102,18 +102,18 @@ export const webviewMessageHandler = async (
* Handles message deletion operations with user confirmation
*/
const handleDeleteOperation = async (messageTs: number): Promise<void> => {
// 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<void> => {
// 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)