Compare commits

...

2 Commits

Author SHA1 Message Date
0xtoshii f1624aaf71 changeset 2025-04-30 11:04:14 -07:00
0xtoshii 71c4f71b29 checkpoints after messages 2025-04-30 11:03:41 -07:00
2 changed files with 78 additions and 45 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
add checkpoints after more messages
+73 -45
View File
@@ -927,6 +927,7 @@ export class Task {
let responseImages: string[] | undefined
if (response === "messageResponse") {
await this.say("user_feedback", text, images)
await this.saveCheckpoint()
responseText = text
responseImages = images
}
@@ -1326,6 +1327,7 @@ export class Task {
if (userFeedback) {
await this.say("user_feedback", userFeedback.text, userFeedback.images)
await this.saveCheckpoint()
return [
true,
formatResponse.toolResult(
@@ -1763,6 +1765,7 @@ export class Task {
if (text || images?.length) {
pushAdditionalToolFeedback(text, images)
await this.say("user_feedback", text, images)
await this.saveCheckpoint()
}
this.didRejectTool = true // Prevent further tool uses in this message
return false
@@ -1771,6 +1774,7 @@ export class Task {
if (text || images?.length) {
pushAdditionalToolFeedback(text, images)
await this.say("user_feedback", text, images)
await this.saveCheckpoint()
}
return true
}
@@ -1845,7 +1849,7 @@ export class Task {
if (!accessAllowed) {
await this.say("clineignore_error", relPath)
pushToolResult(formatResponse.toolError(formatResponse.clineIgnoreError(relPath)))
await this.saveCheckpoint()
break
}
@@ -1901,6 +1905,7 @@ export class Task {
)
await this.diffViewProvider.revertChanges()
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
} else if (content) {
@@ -1958,28 +1963,28 @@ export class Task {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError(block.name, "path"))
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
if (block.name === "replace_in_file" && !diff) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("replace_in_file", "diff"))
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
if (block.name === "write_to_file" && !content) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content"))
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
if (block.name === "new_rule" && !content) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("new_rule", "content"))
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
@@ -2038,6 +2043,7 @@ export class Task {
if (text || images?.length) {
pushAdditionalToolFeedback(text, images)
await this.say("user_feedback", text, images)
await this.saveCheckpoint()
}
this.didRejectTool = true
didApprove = false
@@ -2047,12 +2053,14 @@ export class Task {
if (text || images?.length) {
pushAdditionalToolFeedback(text, images)
await this.say("user_feedback", text, images)
await this.saveCheckpoint()
}
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
}
if (!didApprove) {
await this.diffViewProvider.revertChanges()
await this.saveCheckpoint()
break
}
}
@@ -2113,7 +2121,7 @@ export class Task {
await handleError("writing file", error)
await this.diffViewProvider.revertChanges()
await this.diffViewProvider.reset()
await this.saveCheckpoint()
break
}
}
@@ -2142,7 +2150,7 @@ export class Task {
if (!relPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("read_file", "path"))
await this.saveCheckpoint()
break
}
@@ -2150,7 +2158,7 @@ export class Task {
if (!accessAllowed) {
await this.say("clineignore_error", relPath)
pushToolResult(formatResponse.toolError(formatResponse.clineIgnoreError(relPath)))
await this.saveCheckpoint()
break
}
@@ -2173,6 +2181,7 @@ export class Task {
this.removeLastPartialMessageIfExistsWithType("say", "tool")
const didApprove = await askApproval("tool", completeMessage)
if (!didApprove) {
await this.saveCheckpoint()
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
break
}
@@ -2185,12 +2194,12 @@ export class Task {
await this.fileContextTracker.trackFileContext(relPath, "read_tool")
pushToolResult(content)
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("reading file", error)
await this.saveCheckpoint()
break
}
}
@@ -2221,7 +2230,7 @@ export class Task {
if (!relDirPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2254,17 +2263,18 @@ export class Task {
const didApprove = await askApproval("tool", completeMessage)
if (!didApprove) {
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
await this.saveCheckpoint()
break
}
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
}
pushToolResult(result)
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("listing files", error)
await this.saveCheckpoint()
break
}
}
@@ -2293,7 +2303,7 @@ export class Task {
if (!relDirPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("list_code_definition_names", "path"))
await this.saveCheckpoint()
break
}
@@ -2323,17 +2333,18 @@ export class Task {
const didApprove = await askApproval("tool", completeMessage)
if (!didApprove) {
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
await this.saveCheckpoint()
break
}
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
}
pushToolResult(result)
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("parsing source code definitions", error)
await this.saveCheckpoint()
break
}
}
@@ -2366,13 +2377,13 @@ export class Task {
if (!relDirPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("search_files", "path"))
await this.saveCheckpoint()
break
}
if (!regex) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("search_files", "regex"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2404,17 +2415,18 @@ export class Task {
const didApprove = await askApproval("tool", completeMessage)
if (!didApprove) {
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
await this.saveCheckpoint()
break
}
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
}
pushToolResult(results)
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("searching files", error)
await this.saveCheckpoint()
break
}
}
@@ -2430,6 +2442,7 @@ export class Task {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "action"))
await this.browserSession.closeBrowser()
await this.saveCheckpoint()
}
break
}
@@ -2473,7 +2486,7 @@ export class Task {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "url"))
await this.browserSession.closeBrowser()
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2489,6 +2502,7 @@ export class Task {
this.removeLastPartialMessageIfExistsWithType("say", "browser_action_launch")
const didApprove = await askApproval("browser_action_launch", url)
if (!didApprove) {
await this.saveCheckpoint()
break
}
}
@@ -2514,7 +2528,7 @@ export class Task {
await this.sayAndCreateMissingParamError("browser_action", "coordinate"),
)
await this.browserSession.closeBrowser()
await this.saveCheckpoint()
break // can't be within an inner switch
}
}
@@ -2523,7 +2537,7 @@ export class Task {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "text"))
await this.browserSession.closeBrowser()
await this.saveCheckpoint()
break
}
}
@@ -2572,7 +2586,7 @@ export class Task {
browserActionResult.screenshot ? [browserActionResult.screenshot] : [],
),
)
await this.saveCheckpoint()
break
case "close":
pushToolResult(
@@ -2580,7 +2594,7 @@ export class Task {
`The browser has been closed. You may now proceed to using other tools.`,
),
)
await this.saveCheckpoint()
break
}
@@ -2589,7 +2603,7 @@ export class Task {
} catch (error) {
await this.browserSession.closeBrowser() // if any error occurs, the browser session is terminated
await handleError("executing browser action", error)
await this.saveCheckpoint()
break
}
}
@@ -2617,7 +2631,7 @@ export class Task {
if (!command) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("execute_command", "command"))
await this.saveCheckpoint()
break
}
if (!requiresApprovalRaw) {
@@ -2625,7 +2639,7 @@ export class Task {
pushToolResult(
await this.sayAndCreateMissingParamError("execute_command", "requires_approval"),
)
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2641,7 +2655,7 @@ export class Task {
pushToolResult(
formatResponse.toolError(formatResponse.clineIgnoreError(ignoredFileAttemptedToAccess)),
)
await this.saveCheckpoint()
break
}
@@ -2673,6 +2687,7 @@ export class Task {
`${this.shouldAutoApproveTool(block.name) && requiresApprovalPerLLM ? COMMAND_REQ_APP_STRING : ""}`, // ugly hack until we refactor combineCommandSequences
)
if (!didApprove) {
await this.saveCheckpoint()
break
}
}
@@ -2708,7 +2723,7 @@ export class Task {
}
} catch (error) {
await handleError("executing command", error)
await this.saveCheckpoint()
break
}
}
@@ -2738,13 +2753,13 @@ export class Task {
if (!server_name) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"))
await this.saveCheckpoint()
break
}
if (!tool_name) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"))
await this.saveCheckpoint()
break
}
// arguments are optional, but if they are provided they must be valid JSON
@@ -2768,7 +2783,7 @@ export class Task {
formatResponse.invalidMcpToolArgumentError(server_name, tool_name),
),
)
await this.saveCheckpoint()
break
}
}
@@ -2795,6 +2810,7 @@ export class Task {
this.removeLastPartialMessageIfExistsWithType("say", "use_mcp_server")
const didApprove = await askApproval("use_mcp_server", completeMessage)
if (!didApprove) {
await this.saveCheckpoint()
break
}
}
@@ -2846,7 +2862,7 @@ export class Task {
}
} catch (error) {
await handleError("executing MCP tool", error)
await this.saveCheckpoint()
break
}
}
@@ -2874,13 +2890,13 @@ export class Task {
if (!server_name) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"))
await this.saveCheckpoint()
break
}
if (!uri) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2901,6 +2917,7 @@ export class Task {
this.removeLastPartialMessageIfExistsWithType("say", "use_mcp_server")
const didApprove = await askApproval("use_mcp_server", completeMessage)
if (!didApprove) {
await this.saveCheckpoint()
break
}
}
@@ -2920,12 +2937,12 @@ export class Task {
.join("\n\n") || "(Empty response)"
await this.say("mcp_server_response", resourceResultPretty)
pushToolResult(formatResponse.toolResult(resourceResultPretty))
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("accessing MCP resource", error)
await this.saveCheckpoint()
break
}
}
@@ -2944,7 +2961,7 @@ export class Task {
if (!question) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("ask_followup_question", "question"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -2981,12 +2998,12 @@ export class Task {
}
pushToolResult(formatResponse.toolResult(`<answer>\n${text}\n</answer>`, images))
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("asking question", error)
await this.saveCheckpoint()
break
}
}
@@ -3000,6 +3017,7 @@ export class Task {
if (!context) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("new_task", "context"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -3028,10 +3046,12 @@ export class Task {
formatResponse.toolResult(`The user has created a new task with the provided context.`),
)
}
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("creating new task", error)
await this.saveCheckpoint()
break
}
}
@@ -3045,6 +3065,7 @@ export class Task {
if (!context) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("condense", "context"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
@@ -3087,10 +3108,12 @@ export class Task {
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
)
}
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("condensing context window", error)
await this.saveCheckpoint()
break
}
}
@@ -3151,6 +3174,7 @@ export class Task {
if (text || images?.length) {
telemetryService.captureOptionsIgnored(this.taskId, options.length, "plan")
await this.say("user_feedback", text ?? "", images)
await this.saveCheckpoint()
}
}
@@ -3297,12 +3321,14 @@ export class Task {
// complete command message
const didApprove = await askApproval("command", command)
if (!didApprove) {
await this.saveCheckpoint()
break
}
const [userRejected, execCommandResult] = await this.executeCommandTool(command!)
if (userRejected) {
this.didRejectTool = true
pushToolResult(execCommandResult)
await this.saveCheckpoint()
break
}
// user didn't reject, but the command may have output
@@ -3321,6 +3347,7 @@ export class Task {
break
}
await this.say("user_feedback", text ?? "", images)
await this.saveCheckpoint()
const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = []
if (commandResult) {
@@ -3349,7 +3376,7 @@ export class Task {
}
} catch (error) {
await handleError("attempting completion", error)
await this.saveCheckpoint()
break
}
}
@@ -3451,9 +3478,6 @@ export class Task {
// Save checkpoint if this is the first API request
const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
if (isFirstRequest) {
await this.say("checkpoint_created") // no hash since we need to wait for CheckpointTracker to be initialized
}
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
@@ -3464,6 +3488,10 @@ export class Task {
}),
)
if (isFirstRequest) {
await this.say("checkpoint_created") // no hash since we need to wait for CheckpointTracker to be initialized
}
// use this opportunity to initialize the checkpoint tracker (can be expensive to initialize in the constructor)
// FIXME: right now we're letting users init checkpoints for old tasks, but this could be a problem if opening a task in the wrong workspace
// isNewTask &&