From 7706c6b4799e03be685ee5e3e94654b0ce4a6cbd Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Fri, 14 Nov 2025 17:39:39 -0500 Subject: [PATCH 1/9] feat: enable multiple native tool calls per turn - Remove one-tool-per-message restriction for native protocol in presentAssistantMessage - Allow sequential execution of multiple tool calls with accumulated results - Update system prompt to allow multiple tools for native protocol - Add conversation structure logging for debugging - Keep XML protocol behavior unchanged (one tool per message) --- src/api/providers/openrouter.ts | 1 - .../presentAssistantMessage.ts | 17 +++++--- .../prompts/sections/tool-use-guidelines.ts | 14 ++++-- src/core/task/Task.ts | 43 ++++++++++++++++--- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 867a3d84fa0..8a2412b1a79 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -207,7 +207,6 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH allow_fallbacks: false, }, }), - parallel_tool_calls: false, // Ensure only one tool call at a time ...(transforms && { transforms }), ...(reasoning && { reasoning }), ...(metadata?.tools && { tools: metadata.tools }), diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 3282eb90d18..18c94d76b74 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -276,7 +276,7 @@ export async function presentAssistantMessage(cline: Task) { break } - + if (cline.didAlreadyUseTool) { // Ignore any content after a tool has already been used. // For native protocol, we must send a tool_result for every tool_use to avoid API errors @@ -320,7 +320,6 @@ export async function presentAssistantMessage(cline: Task) { ) return } - // For native protocol, tool_result content must be a string // Images are added as separate blocks in the user message let resultContent: string @@ -365,11 +364,15 @@ export async function presentAssistantMessage(cline: Task) { cline.userMessageContent.push(...content) } } - - // Once a tool result has been collected, ignore all other tool - // uses since we should only ever present one tool result per - // message. - cline.didAlreadyUseTool = true + + // For XML protocol: Only one tool per message is allowed + // For native protocol: Multiple tools can be executed in sequence + if (toolProtocol !== TOOL_PROTOCOL.NATIVE) { + // Once a tool result has been collected, ignore all other tool + // uses since we should only ever present one tool result per + // message (XML protocol only). + cline.didAlreadyUseTool = true + } } const askApproval = async ( diff --git a/src/core/prompts/sections/tool-use-guidelines.ts b/src/core/prompts/sections/tool-use-guidelines.ts index c5651264a0a..159c42d2944 100644 --- a/src/core/prompts/sections/tool-use-guidelines.ts +++ b/src/core/prompts/sections/tool-use-guidelines.ts @@ -35,10 +35,16 @@ export function getToolUseGuidelinesSection( ) } - // Remaining guidelines - guidelinesList.push( - `${itemNumber++}. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, - ) + // Remaining guidelines - different for native vs XML protocol + if (isNativeProtocol(protocol)) { + guidelinesList.push( + `${itemNumber++}. If multiple actions are needed, you may use multiple tools in a single message when appropriate, or use tools iteratively across messages. Each tool use should be informed by the results of previous tool uses. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, + ) + } else { + guidelinesList.push( + `${itemNumber++}. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.`, + ) + } // Protocol-specific guideline - only add for XML protocol if (!isNativeProtocol(protocol)) { diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6c17e201210..c5af50fff7d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2359,7 +2359,8 @@ export class Task extends EventEmitter implements TaskLike { // Mark that we have new content to process this.userMessageContentReady = false - // Present the tool call to user + // Present the tool call to user - presentAssistantMessage will execute + // tools sequentially and accumulate all results in userMessageContent presentAssistantMessage(this) break } @@ -2704,12 +2705,12 @@ export class Task extends EventEmitter implements TaskLike { this.assistantMessageContent = parsedBlocks } - if (partialBlocks.length > 0) { + // Only present partial blocks that were just completed (from XML parsing) + // Native tool blocks were already presented during streaming, so don't re-present them + if (partialBlocks.length > 0 && partialBlocks.some((block) => block.type !== "tool_use")) { // If there is content to update then it will complete and // update `this.userMessageContentReady` to true, which we - // `pWaitFor` before making the next request. All this is really - // doing is presenting the last partial message that we just set - // to complete. + // `pWaitFor` before making the next request. presentAssistantMessage(this) } @@ -3288,6 +3289,38 @@ export class Task extends EventEmitter implements TaskLike { this.currentRequestAbortController = new AbortController() const abortSignal = this.currentRequestAbortController.signal + // Log conversation structure for debugging (omit content) + if (shouldIncludeTools) { + console.log(`[Task#${this.taskId}] Conversation structure being sent to API:`) + cleanConversationHistory.forEach((msg, idx) => { + if ("role" in msg) { + const contentSummary = Array.isArray(msg.content) + ? msg.content + .map((block) => { + if ("type" in block) { + if (block.type === "tool_use") { + return `tool_use(${block.name}, id:${block.id})` + } else if (block.type === "tool_result") { + return `tool_result(id:${block.tool_use_id})` + } else if (block.type === "text") { + return `text(${(block.text as string).substring(0, 50)}...)` + } else if (block.type === "image") { + return "image" + } + } + return "unknown_block" + }) + .join(", ") + : typeof msg.content === "string" + ? `text(${msg.content.substring(0, 50)}...)` + : "unknown_content" + console.log(` [${idx}] ${msg.role}: [${contentSummary}]`) + } else if ("type" in msg && msg.type === "reasoning") { + console.log(` [${idx}] reasoning (encrypted)`) + } + }) + } + // The provider accepts reasoning items alongside standard messages; cast to the expected parameter type. const stream = this.api.createMessage( systemPrompt, From 7684da21fcb98337639759c31e6fe19a8df2792b Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Wed, 19 Nov 2025 12:01:56 -0500 Subject: [PATCH 2/9] feat: prevent attempt_completion after tool failures and update native protocol prompts - Add didToolFailInCurrentTurn flag to prevent attempt_completion when any tool fails in the same assistant message - Flag resets at start of each new API request cycle, scoping failures to single assistant messages - Updated all 19 tools to set the flag when they encounter errors - Modified system prompts for native protocol to allow multiple tools per message - Removed 'ALWAYS wait for confirmation' guideline for native protocol - Updated tool-use section to clarify native protocol behavior - All tests passing (11/11) This ensures tools called in sequence within one message are properly validated, while still allowing users to override with a follow-up message in the next turn. --- .../presentAssistantMessage.ts | 24 ++++++- .../prompts/sections/tool-use-guidelines.ts | 32 ++++++---- src/core/task/Task.ts | 5 ++ src/core/tools/ApplyDiffTool.ts | 1 + src/core/tools/AskFollowupQuestionTool.ts | 1 + src/core/tools/AttemptCompletionTool.ts | 9 +++ src/core/tools/BrowserActionTool.ts | 12 +++- src/core/tools/CodebaseSearchTool.ts | 1 + src/core/tools/ExecuteCommandTool.ts | 1 + src/core/tools/FetchInstructionsTool.ts | 1 + src/core/tools/GenerateImageTool.ts | 6 ++ src/core/tools/InsertContentTool.ts | 1 + src/core/tools/ListCodeDefinitionNamesTool.ts | 1 + src/core/tools/ListFilesTool.ts | 1 + src/core/tools/NewTaskTool.ts | 4 ++ src/core/tools/ReadFileTool.ts | 9 +++ src/core/tools/RunSlashCommandTool.ts | 2 + src/core/tools/SearchFilesTool.ts | 2 + src/core/tools/SwitchModeTool.ts | 2 + src/core/tools/UpdateTodoListTool.ts | 2 + src/core/tools/UseMcpToolTool.ts | 5 ++ src/core/tools/WriteToFileTool.ts | 1 + .../__tests__/attemptCompletionTool.spec.ts | 64 +++++++++++++++++++ 23 files changed, 169 insertions(+), 18 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 18c94d76b74..e47b1564c7f 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -428,13 +428,16 @@ export async function presentAssistantMessage(cline: Task) { const handleError = async (action: string, error: Error) => { const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` - + await cline.say( "error", `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, ) - + pushToolResult(formatResponse.toolError(errorString, toolProtocol)) + + // Mark that a tool failed in this turn to prevent attempt_completion + cline.didToolFailInCurrentTurn = true } // If block is partial, remove partial closing tag so its not @@ -510,6 +513,7 @@ export async function presentAssistantMessage(cline: Task) { ) } catch (error) { cline.consecutiveMistakeCount++ + cline.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(error.message, toolProtocol)) break } @@ -544,7 +548,10 @@ export async function presentAssistantMessage(cline: Task) { // Track tool repetition in telemetry. TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId) } - + + // Mark that a tool failed in this turn to prevent attempt_completion + cline.didToolFailInCurrentTurn = true + // Return tool result message about the repetition pushToolResult( formatResponse.toolError( @@ -761,6 +768,17 @@ export async function presentAssistantMessage(cline: Task) { }) break case "attempt_completion": { + // Prevent attempt_completion if any tool failed in the current assistant message (turn). + // This only applies to tools called within the same message, not across different turns. + // For example, this blocks: read_file (fails) + attempt_completion in same message + // But allows: read_file (fails) → user message → attempt_completion in next turn + if (cline.didToolFailInCurrentTurn) { + const errorMsg = `Cannot execute attempt_completion because a previous tool call failed in this turn. Please address the tool failure before attempting completion.` + await cline.say("error", errorMsg) + pushToolResult(formatResponse.toolError(errorMsg)) + break + } + const completionCallbacks: AttemptCompletionCallbacks = { askApproval, handleError, diff --git a/src/core/prompts/sections/tool-use-guidelines.ts b/src/core/prompts/sections/tool-use-guidelines.ts index 159c42d2944..01fcc07bf2f 100644 --- a/src/core/prompts/sections/tool-use-guidelines.ts +++ b/src/core/prompts/sections/tool-use-guidelines.ts @@ -51,24 +51,32 @@ export function getToolUseGuidelinesSection( guidelinesList.push(`${itemNumber++}. Formulate your tool use using the XML format specified for each tool.`) } guidelinesList.push(`${itemNumber++}. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use.`) - guidelinesList.push( - `${itemNumber++}. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user.`, - ) + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use.`) - // Join guidelines and add the footer - return `# Tool Use Guidelines - -${guidelinesList.join("\n")} + // Only add the "wait for confirmation" guideline for XML protocol + // Native protocol allows multiple tools per message, so waiting after each tool doesn't apply + if (!isNativeProtocol(protocol)) { + guidelinesList.push( + `${itemNumber++}. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user.`, + ) + } -It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: + // Join guidelines and add the footer + // For native protocol, the footer is less relevant since multiple tools can execute in one message + const footer = isNativeProtocol(protocol) + ? `\n\nBy carefully considering the user's response after tool executions, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.` + : `\n\nIt is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: 1. Confirm the success of each step before proceeding. 2. Address any issues or errors that arise immediately. 3. Adapt your approach based on new information or unexpected results. 4. Ensure that each action builds correctly on the previous ones. By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.` + + return `# Tool Use Guidelines + +${guidelinesList.join("\n")}${footer}` } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c5af50fff7d..c903995a929 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -302,6 +302,7 @@ export class Task extends EventEmitter implements TaskLike { userMessageContentReady = false didRejectTool = false didAlreadyUseTool = false + didToolFailInCurrentTurn = false didCompleteReadingStream = false assistantMessageParser?: AssistantMessageParser private providerProfileChangeListener?: (config: { name: string; provider?: string }) => void @@ -2246,6 +2247,10 @@ export class Task extends EventEmitter implements TaskLike { this.userMessageContentReady = false this.didRejectTool = false this.didAlreadyUseTool = false + // Reset tool failure flag for each new assistant turn - this ensures that tool failures + // only prevent attempt_completion within the same assistant message, not across turns + // (e.g., if a tool fails, then user sends a message saying "just complete anyway") + this.didToolFailInCurrentTurn = false this.presentAssistantMessageLocked = false this.presentAssistantMessageHasPendingUpdates = false this.assistantMessageParser?.reset() diff --git a/src/core/tools/ApplyDiffTool.ts b/src/core/tools/ApplyDiffTool.ts index c5ad24bca39..7161c7c08ef 100644 --- a/src/core/tools/ApplyDiffTool.ts +++ b/src/core/tools/ApplyDiffTool.ts @@ -70,6 +70,7 @@ export class ApplyDiffTool extends BaseTool<"apply_diff"> { task.recordToolError("apply_diff") const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await task.say("error", formattedError) + task.didToolFailInCurrentTurn = true pushToolResult(formattedError) return } diff --git a/src/core/tools/AskFollowupQuestionTool.ts b/src/core/tools/AskFollowupQuestionTool.ts index 27189476dc2..4fb673b8524 100644 --- a/src/core/tools/AskFollowupQuestionTool.ts +++ b/src/core/tools/AskFollowupQuestionTool.ts @@ -71,6 +71,7 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { if (!question) { task.consecutiveMistakeCount++ task.recordToolError("ask_followup_question") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "question")) return } diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts index a0b358ea5fb..8e0ed2fc00c 100644 --- a/src/core/tools/AttemptCompletionTool.ts +++ b/src/core/tools/AttemptCompletionTool.ts @@ -34,6 +34,15 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { const { result } = params const { handleError, pushToolResult, askFinishSubTaskApproval, toolDescription, toolProtocol } = callbacks + // Prevent attempt_completion if any tool failed in the current turn + if (task.didToolFailInCurrentTurn) { + const errorMsg = `Cannot execute attempt_completion because a previous tool call failed in this turn. Please address the tool failure before attempting completion.` + + await task.say("error", errorMsg) + pushToolResult(formatResponse.toolError(errorMsg)) + return + } + const preventCompletionWithOpenTodos = vscode.workspace .getConfiguration(Package.name) .get("preventCompletionWithOpenTodos", false) diff --git a/src/core/tools/BrowserActionTool.ts b/src/core/tools/BrowserActionTool.ts index b9afae2fdb1..fa1cfeeb2fa 100644 --- a/src/core/tools/BrowserActionTool.ts +++ b/src/core/tools/BrowserActionTool.ts @@ -30,6 +30,7 @@ export async function browserActionTool( // if the block is complete and we don't have a valid action cline is a mistake cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) // Do not close the browser on parameter validation errors } @@ -63,6 +64,7 @@ export async function browserActionTool( if (!url) { cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) // Do not close the browser on parameter validation errors return @@ -102,22 +104,24 @@ export async function browserActionTool( if (!coordinate) { cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) // Do not close the browser on parameter validation errors return // can't be within an inner switch } - + // Get viewport dimensions from the browser session const viewportSize = cline.browserSession.getViewportSize() const viewportWidth = viewportSize.width || 900 // default to 900 if not available const viewportHeight = viewportSize.height || 600 // default to 600 if not available - + // Scale coordinate from image dimensions to viewport dimensions try { processedCoordinate = scaleCoordinate(coordinate, viewportWidth, viewportHeight) } catch (error) { cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult( await cline.sayAndCreateMissingParamError( "browser_action", @@ -133,16 +137,18 @@ export async function browserActionTool( if (!text) { cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) // Do not close the browser on parameter validation errors return } } - + if (action === "resize") { if (!size) { cline.consecutiveMistakeCount++ cline.recordToolError("browser_action") + cline.didToolFailInCurrentTurn = true pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "size")) // Do not close the browser on parameter validation errors return diff --git a/src/core/tools/CodebaseSearchTool.ts b/src/core/tools/CodebaseSearchTool.ts index 0637ac52411..23cd239f384 100644 --- a/src/core/tools/CodebaseSearchTool.ts +++ b/src/core/tools/CodebaseSearchTool.ts @@ -44,6 +44,7 @@ export class CodebaseSearchTool extends BaseTool<"codebase_search"> { if (!query) { task.consecutiveMistakeCount++ + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("codebase_search", "query")) return } diff --git a/src/core/tools/ExecuteCommandTool.ts b/src/core/tools/ExecuteCommandTool.ts index aa6bb097d00..f7271bffe9c 100644 --- a/src/core/tools/ExecuteCommandTool.ts +++ b/src/core/tools/ExecuteCommandTool.ts @@ -291,6 +291,7 @@ export async function executeCommandInTerminal( const status: CommandExecutionStatus = { executionId, status: "timeout" } provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) }) await task.say("error", t("common:errors:command_timeout", { seconds: commandExecutionTimeoutSeconds })) + task.didToolFailInCurrentTurn = true task.terminalProcess = undefined return [ diff --git a/src/core/tools/FetchInstructionsTool.ts b/src/core/tools/FetchInstructionsTool.ts index 0632fc12b22..d12b434bb0c 100644 --- a/src/core/tools/FetchInstructionsTool.ts +++ b/src/core/tools/FetchInstructionsTool.ts @@ -26,6 +26,7 @@ export class FetchInstructionsTool extends BaseTool<"fetch_instructions"> { if (!taskParam) { task.consecutiveMistakeCount++ task.recordToolError("fetch_instructions") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("fetch_instructions", "task")) return } diff --git a/src/core/tools/GenerateImageTool.ts b/src/core/tools/GenerateImageTool.ts index 4c4f6819155..cdbbe3341a2 100644 --- a/src/core/tools/GenerateImageTool.ts +++ b/src/core/tools/GenerateImageTool.ts @@ -78,6 +78,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { const inputImageExists = await fileExistsAtPath(inputImageFullPath) if (!inputImageExists) { await task.say("error", `Input image not found: ${getReadablePath(task.cwd, inputImagePath)}`) + task.didToolFailInCurrentTurn = true pushToolResult( formatResponse.toolError(`Input image not found: ${getReadablePath(task.cwd, inputImagePath)}`), ) @@ -101,6 +102,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { "error", `Unsupported image format: ${imageExtension}. Supported formats: ${supportedFormats.join(", ")}`, ) + task.didToolFailInCurrentTurn = true pushToolResult( formatResponse.toolError( `Unsupported image format: ${imageExtension}. Supported formats: ${supportedFormats.join(", ")}`, @@ -116,6 +118,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { "error", `Failed to read input image: ${error instanceof Error ? error.message : "Unknown error"}`, ) + task.didToolFailInCurrentTurn = true pushToolResult( formatResponse.toolError( `Failed to read input image: ${error instanceof Error ? error.message : "Unknown error"}`, @@ -203,6 +206,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { if (!result.success) { await task.say("error", result.error || "Failed to generate image") + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(result.error || "Failed to generate image")) return } @@ -210,6 +214,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { if (!result.imageData) { const errorMessage = "No image data received" await task.say("error", errorMessage) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(errorMessage)) return } @@ -218,6 +223,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { if (!base64Match) { const errorMessage = "Invalid image format received" await task.say("error", errorMessage) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(errorMessage)) return } diff --git a/src/core/tools/InsertContentTool.ts b/src/core/tools/InsertContentTool.ts index 68acc229b92..f998d1c4ed2 100644 --- a/src/core/tools/InsertContentTool.ts +++ b/src/core/tools/InsertContentTool.ts @@ -85,6 +85,7 @@ export class InsertContentTool extends BaseTool<"insert_content"> { task.recordToolError("insert_content") const formattedError = `Cannot insert content at line ${lineNumber} into a non-existent file. For new files, 'line' must be 0 (to append) or 1 (to insert at the beginning).` await task.say("error", formattedError) + task.didToolFailInCurrentTurn = true pushToolResult(formattedError) return } diff --git a/src/core/tools/ListCodeDefinitionNamesTool.ts b/src/core/tools/ListCodeDefinitionNamesTool.ts index 981b508ee32..e76a45c880b 100644 --- a/src/core/tools/ListCodeDefinitionNamesTool.ts +++ b/src/core/tools/ListCodeDefinitionNamesTool.ts @@ -31,6 +31,7 @@ export class ListCodeDefinitionNamesTool extends BaseTool<"list_code_definition_ if (!relPath) { task.consecutiveMistakeCount++ task.recordToolError("list_code_definition_names") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("list_code_definition_names", "path")) return } diff --git a/src/core/tools/ListFilesTool.ts b/src/core/tools/ListFilesTool.ts index 795bebf85ab..37e3676a03b 100644 --- a/src/core/tools/ListFilesTool.ts +++ b/src/core/tools/ListFilesTool.ts @@ -35,6 +35,7 @@ export class ListFilesTool extends BaseTool<"list_files"> { if (!relDirPath) { task.consecutiveMistakeCount++ task.recordToolError("list_files") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("list_files", "path")) return } diff --git a/src/core/tools/NewTaskTool.ts b/src/core/tools/NewTaskTool.ts index ce68b20f3ee..ec7047d1bf3 100644 --- a/src/core/tools/NewTaskTool.ts +++ b/src/core/tools/NewTaskTool.ts @@ -37,6 +37,7 @@ export class NewTaskTool extends BaseTool<"new_task"> { if (!mode) { task.consecutiveMistakeCount++ task.recordToolError("new_task") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("new_task", "mode")) return } @@ -44,6 +45,7 @@ export class NewTaskTool extends BaseTool<"new_task"> { if (!message) { task.consecutiveMistakeCount++ task.recordToolError("new_task") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("new_task", "message")) return } @@ -69,6 +71,7 @@ export class NewTaskTool extends BaseTool<"new_task"> { if (requireTodos && todos === undefined) { task.consecutiveMistakeCount++ task.recordToolError("new_task") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("new_task", "todos")) return } @@ -81,6 +84,7 @@ export class NewTaskTool extends BaseTool<"new_task"> { } catch (error) { task.consecutiveMistakeCount++ task.recordToolError("new_task") + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError("Invalid todos format: must be a markdown checklist")) return } diff --git a/src/core/tools/ReadFileTool.ts b/src/core/tools/ReadFileTool.ts index d6989c103ef..6fd24c66a50 100644 --- a/src/core/tools/ReadFileTool.ts +++ b/src/core/tools/ReadFileTool.ts @@ -543,6 +543,12 @@ export class ReadFileTool extends BaseTool<"read_file"> { } } + // Check if any files had errors or were blocked and mark the turn as failed + const hasErrors = fileResults.some((result) => result.status === "error" || result.status === "blocked") + if (hasErrors) { + task.didToolFailInCurrentTurn = true + } + // Build final result based on protocol let finalResult: string if (useNative) { @@ -623,6 +629,9 @@ export class ReadFileTool extends BaseTool<"read_file"> { await task.say("error", `Error reading file ${relPath}: ${errorMsg}`) + // Mark that a tool failed in this turn + task.didToolFailInCurrentTurn = true + // Build final error result based on protocol let errorResult: string if (useNative) { diff --git a/src/core/tools/RunSlashCommandTool.ts b/src/core/tools/RunSlashCommandTool.ts index c82b6cc0a8c..8af8bd6e12a 100644 --- a/src/core/tools/RunSlashCommandTool.ts +++ b/src/core/tools/RunSlashCommandTool.ts @@ -45,6 +45,7 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> { if (!commandName) { task.consecutiveMistakeCount++ task.recordToolError("run_slash_command") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("run_slash_command", "command")) return } @@ -58,6 +59,7 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> { // Get available commands for error message const availableCommands = await getCommandNames(task.cwd) task.recordToolError("run_slash_command") + task.didToolFailInCurrentTurn = true pushToolResult( formatResponse.toolError( `Command '${commandName}' not found. Available commands: ${availableCommands.join(", ") || "(none)"}`, diff --git a/src/core/tools/SearchFilesTool.ts b/src/core/tools/SearchFilesTool.ts index f22462c22e0..ee8a946bf65 100644 --- a/src/core/tools/SearchFilesTool.ts +++ b/src/core/tools/SearchFilesTool.ts @@ -35,6 +35,7 @@ export class SearchFilesTool extends BaseTool<"search_files"> { if (!relDirPath) { task.consecutiveMistakeCount++ task.recordToolError("search_files") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("search_files", "path")) return } @@ -42,6 +43,7 @@ export class SearchFilesTool extends BaseTool<"search_files"> { if (!regex) { task.consecutiveMistakeCount++ task.recordToolError("search_files") + task.didToolFailInCurrentTurn = true pushToolResult(await task.sayAndCreateMissingParamError("search_files", "regex")) return } diff --git a/src/core/tools/SwitchModeTool.ts b/src/core/tools/SwitchModeTool.ts index df418cfdfcb..c5fedaedcf4 100644 --- a/src/core/tools/SwitchModeTool.ts +++ b/src/core/tools/SwitchModeTool.ts @@ -40,6 +40,7 @@ export class SwitchModeTool extends BaseTool<"switch_mode"> { if (!targetMode) { task.recordToolError("switch_mode") + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) return } @@ -49,6 +50,7 @@ export class SwitchModeTool extends BaseTool<"switch_mode"> { if (currentMode === mode_slug) { task.recordToolError("switch_mode") + task.didToolFailInCurrentTurn = true pushToolResult(`Already in ${targetMode.name} mode.`) return } diff --git a/src/core/tools/UpdateTodoListTool.ts b/src/core/tools/UpdateTodoListTool.ts index bf2c2b5301d..f8b3653b9a3 100644 --- a/src/core/tools/UpdateTodoListTool.ts +++ b/src/core/tools/UpdateTodoListTool.ts @@ -34,6 +34,7 @@ export class UpdateTodoListTool extends BaseTool<"update_todo_list"> { } catch { task.consecutiveMistakeCount++ task.recordToolError("update_todo_list") + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError("The todos parameter is not valid markdown checklist or JSON")) return } @@ -42,6 +43,7 @@ export class UpdateTodoListTool extends BaseTool<"update_todo_list"> { if (!valid) { task.consecutiveMistakeCount++ task.recordToolError("update_todo_list") + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(error || "todos parameter validation failed")) return } diff --git a/src/core/tools/UseMcpToolTool.ts b/src/core/tools/UseMcpToolTool.ts index b276293f6f1..b44b96054ee 100644 --- a/src/core/tools/UseMcpToolTool.ts +++ b/src/core/tools/UseMcpToolTool.ts @@ -130,6 +130,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { task.consecutiveMistakeCount++ task.recordToolError("use_mcp_tool") await task.say("error", t("mcp:errors.invalidJsonArgument", { toolName: params.tool_name })) + task.didToolFailInCurrentTurn = true pushToolResult( formatResponse.toolError( @@ -178,6 +179,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { task.consecutiveMistakeCount++ task.recordToolError("use_mcp_tool") await task.say("error", t("mcp:errors.serverNotFound", { serverName, availableServers })) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.unknownMcpServerError(serverName, availableServersArray)) return { isValid: false, availableTools: [] } @@ -196,6 +198,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { availableTools: "No tools available", }), ) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, [])) return { isValid: false, availableTools: [] } @@ -218,6 +221,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { availableTools: availableToolNames.join(", "), }), ) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, availableToolNames)) return { isValid: false, availableTools: availableToolNames } @@ -240,6 +244,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { enabledToolNames.length > 0 ? enabledToolNames.join(", ") : "No enabled tools available", }), ) + task.didToolFailInCurrentTurn = true pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, enabledToolNames)) return { isValid: false, availableTools: enabledToolNames } diff --git a/src/core/tools/WriteToFileTool.ts b/src/core/tools/WriteToFileTool.ts index 4c355beb073..090329e2a8b 100644 --- a/src/core/tools/WriteToFileTool.ts +++ b/src/core/tools/WriteToFileTool.ts @@ -106,6 +106,7 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { if (predictedLineCount === undefined || predictedLineCount === 0) { task.consecutiveMistakeCount++ task.recordToolError("write_to_file") + task.didToolFailInCurrentTurn = true const actualLineCount = newContent.split("\n").length const isNewFile = !fileExists diff --git a/src/core/tools/__tests__/attemptCompletionTool.spec.ts b/src/core/tools/__tests__/attemptCompletionTool.spec.ts index 9ab4d57ebbc..a737789ca79 100644 --- a/src/core/tools/__tests__/attemptCompletionTool.spec.ts +++ b/src/core/tools/__tests__/attemptCompletionTool.spec.ts @@ -408,5 +408,69 @@ describe("attemptCompletionTool", () => { expect.stringContaining("Cannot complete task while there are incomplete todos"), ) }) + + describe("tool failure guardrail", () => { + it("should prevent completion when a previous tool failed in the current turn", async () => { + const block: AttemptCompletionToolUse = { + type: "tool_use", + name: "attempt_completion", + params: { result: "Task completed successfully" }, + partial: false, + } + + mockTask.todoList = undefined + mockTask.didToolFailInCurrentTurn = true + + const callbacks: AttemptCompletionCallbacks = { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + removeClosingTag: mockRemoveClosingTag, + askFinishSubTaskApproval: mockAskFinishSubTaskApproval, + toolDescription: mockToolDescription, + toolProtocol: "xml", + } + + const mockSay = vi.fn() + mockTask.say = mockSay + + await attemptCompletionTool.handle(mockTask as Task, block, callbacks) + + expect(mockSay).toHaveBeenCalledWith( + "error", + expect.stringContaining("Cannot execute attempt_completion because a previous tool call failed"), + ) + expect(mockPushToolResult).toHaveBeenCalledWith( + expect.stringContaining("Cannot execute attempt_completion because a previous tool call failed"), + ) + }) + + it("should allow completion when no tools failed", async () => { + const block: AttemptCompletionToolUse = { + type: "tool_use", + name: "attempt_completion", + params: { result: "Task completed successfully" }, + partial: false, + } + + mockTask.todoList = undefined + mockTask.didToolFailInCurrentTurn = false + + const callbacks: AttemptCompletionCallbacks = { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + removeClosingTag: mockRemoveClosingTag, + askFinishSubTaskApproval: mockAskFinishSubTaskApproval, + toolDescription: mockToolDescription, + toolProtocol: "xml", + } + + await attemptCompletionTool.handle(mockTask as Task, block, callbacks) + + expect(mockTask.consecutiveMistakeCount).toBe(0) + expect(mockTask.recordToolError).not.toHaveBeenCalled() + }) + }) }) }) From bd6040bdf4412bd5dbc3371ef6bc197bdefad4ca Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Wed, 19 Nov 2025 12:08:15 -0500 Subject: [PATCH 3/9] test: update snapshots for tool-use-guidelines formatting changes --- .../add-custom-instructions/architect-mode-prompt.snap | 8 ++++---- .../add-custom-instructions/ask-mode-prompt.snap | 8 ++++---- .../mcp-server-creation-disabled.snap | 8 ++++---- .../mcp-server-creation-enabled.snap | 8 ++++---- .../add-custom-instructions/partial-reads-enabled.snap | 8 ++++---- .../system-prompt/consistent-system-prompt.snap | 8 ++++---- .../system-prompt/with-computer-use-support.snap | 8 ++++---- .../system-prompt/with-diff-enabled-false.snap | 8 ++++---- .../system-prompt/with-diff-enabled-true.snap | 8 ++++---- .../system-prompt/with-diff-enabled-undefined.snap | 8 ++++---- .../system-prompt/with-different-viewport-size.snap | 8 ++++---- .../system-prompt/with-mcp-hub-provided.snap | 8 ++++---- .../system-prompt/with-undefined-mcp-hub.snap | 8 ++++---- 13 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index e49d2bc0a73..0ffedb27dbd 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -318,10 +318,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 3581a924c92..9125f356b89 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -384,10 +384,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 0e0e9200ecf..af4d152cbe1 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -434,10 +434,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 44a34544dc1..4c5e38b82a8 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -390,10 +390,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 323aa0bdbe4..030e7a05650 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -453,10 +453,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 9bb8c3e4624..44a560219b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -473,10 +473,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 0e0e9200ecf..af4d152cbe1 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -434,10 +434,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index dbab96eca1d..c99f7b47f7e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -385,10 +385,10 @@ Replace the entire TODO list with an updated checklist reflecting the current st 3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. 4. Formulate your tool use using the XML format specified for each tool. 5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: - - Information about whether the tool succeeded or failed, along with any reasons for failure. - - Linter errors that may have arisen due to the changes you made, which you'll need to address. - - New terminal output in reaction to the changes, which you may need to consider or act upon. - - Any other relevant feedback or information related to the tool use. + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Linter errors that may have arisen due to the changes you made, which you'll need to address. + - New terminal output in reaction to the changes, which you may need to consider or act upon. + - Any other relevant feedback or information related to the tool use. 6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: From d8e05013999142bf83cf348c245073a5a2348509 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Thu, 20 Nov 2025 11:19:26 -0500 Subject: [PATCH 4/9] refactor: centralize attempt_completion failure guard in tool --- .../presentAssistantMessage.ts | 111 ++++++------------ 1 file changed, 36 insertions(+), 75 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index e47b1564c7f..0787f9a853b 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -71,8 +71,6 @@ export async function presentAssistantMessage(cline: Task) { cline.presentAssistantMessageLocked = true cline.presentAssistantMessageHasPendingUpdates = false - const cachedModelId = cline.api.getModel().id - if (cline.currentStreamingContentIndex >= cline.assistantMessageContent.length) { // This may happen if the last content block was completed before // streaming could finish. If streaming is finished, and we're out of @@ -176,7 +174,8 @@ export async function presentAssistantMessage(cline: Task) { return `[${block.name} for '${block.params.command}']` case "read_file": // Check if this model should use the simplified description - if (shouldUseSingleFileRead(cachedModelId)) { + const modelId = cline.api.getModel().id + if (shouldUseSingleFileRead(modelId)) { return getSimpleReadFileToolDescription(block.name, block.params) } else { // Prefer native typed args when available; fall back to legacy params @@ -252,52 +251,28 @@ export async function presentAssistantMessage(cline: Task) { if (cline.didRejectTool) { // Ignore any tool content after user has rejected tool once. - // For native protocol, we must send a tool_result for every tool_use to avoid API errors - const toolCallId = block.id - const errorMessage = !block.partial - ? `Skipping tool ${toolDescription()} due to user rejecting a previous tool.` - : `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.` - - if (toolCallId) { - // Native protocol: MUST send tool_result for every tool_use + if (!block.partial) { cline.userMessageContent.push({ - type: "tool_result", - tool_use_id: toolCallId, - content: errorMessage, - is_error: true, - } as Anthropic.ToolResultBlockParam) + type: "text", + text: `Skipping tool ${toolDescription()} due to user rejecting a previous tool.`, + }) } else { - // XML protocol: send as text + // Partial tool after user rejected a previous tool. cline.userMessageContent.push({ type: "text", - text: errorMessage, + text: `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.`, }) } break } - + if (cline.didAlreadyUseTool) { // Ignore any content after a tool has already been used. - // For native protocol, we must send a tool_result for every tool_use to avoid API errors - const toolCallId = block.id - const errorMessage = `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.` - - if (toolCallId) { - // Native protocol: MUST send tool_result for every tool_use - cline.userMessageContent.push({ - type: "tool_result", - tool_use_id: toolCallId, - content: errorMessage, - is_error: true, - } as Anthropic.ToolResultBlockParam) - } else { - // XML protocol: send as text - cline.userMessageContent.push({ - type: "text", - text: errorMessage, - }) - } + cline.userMessageContent.push({ + type: "text", + text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`, + }) break } @@ -320,36 +295,32 @@ export async function presentAssistantMessage(cline: Task) { ) return } - // For native protocol, tool_result content must be a string - // Images are added as separate blocks in the user message - let resultContent: string - let imageBlocks: Anthropic.ImageBlockParam[] = [] + // For native protocol, add as tool_result block + let resultContent: string if (typeof content === "string") { resultContent = content || "(tool did not return anything)" } else { - // Separate text and image blocks - const textBlocks = content.filter((item) => item.type === "text") - imageBlocks = content.filter((item) => item.type === "image") as Anthropic.ImageBlockParam[] - - // Convert text blocks to string for tool_result - resultContent = - textBlocks.map((item) => (item as Anthropic.TextBlockParam).text).join("\n") || - "(tool did not return anything)" + // Convert array of content blocks to string for tool result + // Tool results in OpenAI format only support strings + resultContent = content + .map((item) => { + if (item.type === "text") { + return item.text + } else if (item.type === "image") { + return "(image content)" + } + return "" + }) + .join("\n") } - // Add tool_result with text content only cline.userMessageContent.push({ type: "tool_result", tool_use_id: toolCallId, content: resultContent, } as Anthropic.ToolResultBlockParam) - // Add image blocks separately after tool_result - if (imageBlocks.length > 0) { - cline.userMessageContent.push(...imageBlocks) - } - hasToolResult = true } else { // For XML protocol, add as text blocks (legacy behavior) @@ -364,10 +335,10 @@ export async function presentAssistantMessage(cline: Task) { cline.userMessageContent.push(...content) } } - + // For XML protocol: Only one tool per message is allowed // For native protocol: Multiple tools can be executed in sequence - if (toolProtocol !== TOOL_PROTOCOL.NATIVE) { + if (toolProtocol === TOOL_PROTOCOL.XML) { // Once a tool result has been collected, ignore all other tool // uses since we should only ever present one tool result per // message (XML protocol only). @@ -428,14 +399,14 @@ export async function presentAssistantMessage(cline: Task) { const handleError = async (action: string, error: Error) => { const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` - + await cline.say( "error", `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, ) - + pushToolResult(formatResponse.toolError(errorString, toolProtocol)) - + // Mark that a tool failed in this turn to prevent attempt_completion cline.didToolFailInCurrentTurn = true } @@ -548,10 +519,10 @@ export async function presentAssistantMessage(cline: Task) { // Track tool repetition in telemetry. TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId) } - + // Mark that a tool failed in this turn to prevent attempt_completion cline.didToolFailInCurrentTurn = true - + // Return tool result message about the repetition pushToolResult( formatResponse.toolError( @@ -636,7 +607,8 @@ export async function presentAssistantMessage(cline: Task) { break case "read_file": // Check if this model should use the simplified single-file read tool - if (shouldUseSingleFileRead(cachedModelId)) { + const modelId = cline.api.getModel().id + if (shouldUseSingleFileRead(modelId)) { await simpleReadFileTool( cline, block, @@ -768,17 +740,6 @@ export async function presentAssistantMessage(cline: Task) { }) break case "attempt_completion": { - // Prevent attempt_completion if any tool failed in the current assistant message (turn). - // This only applies to tools called within the same message, not across different turns. - // For example, this blocks: read_file (fails) + attempt_completion in same message - // But allows: read_file (fails) → user message → attempt_completion in next turn - if (cline.didToolFailInCurrentTurn) { - const errorMsg = `Cannot execute attempt_completion because a previous tool call failed in this turn. Please address the tool failure before attempting completion.` - await cline.say("error", errorMsg) - pushToolResult(formatResponse.toolError(errorMsg)) - break - } - const completionCallbacks: AttemptCompletionCallbacks = { askApproval, handleError, From 847431ad9af643f7f743afb74983bd9504f0379d Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Fri, 21 Nov 2025 18:21:31 -0500 Subject: [PATCH 5/9] feat: make multiple native tool calls an experimental setting - Add 'multipleNativeToolCalls' experiment ID to types - Add experiment to shared/experiments.ts with default disabled - Modify presentAssistantMessage to check experimental flag - When disabled, enforce single tool per message for native protocol (safer default) - When enabled, allow multiple tool calls per turn for native protocol This makes the multiple native tool calls feature opt-in via experimental settings, providing a safer default behavior while allowing users to enable the feature if desired. --- packages/types/src/experiment.ts | 2 ++ .../assistant-message/presentAssistantMessage.ts | 16 +++++++++++++++- src/shared/__tests__/experiments.spec.ts | 3 +++ src/shared/experiments.ts | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 37c6eecee75..cc0aabd6f6b 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -12,6 +12,7 @@ export const experimentIds = [ "preventFocusDisruption", "imageGeneration", "runSlashCommand", + "multipleNativeToolCalls", ] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -28,6 +29,7 @@ export const experimentsSchema = z.object({ preventFocusDisruption: z.boolean().optional(), imageGeneration: z.boolean().optional(), runSlashCommand: z.boolean().optional(), + multipleNativeToolCalls: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 0787f9a853b..e8d4720bef8 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -286,6 +286,14 @@ export async function presentAssistantMessage(cline: Task) { const toolCallId = (block as any).id const toolProtocol = toolCallId ? TOOL_PROTOCOL.NATIVE : TOOL_PROTOCOL.XML + // Check experimental setting for multiple native tool calls + const provider = cline.providerRef.deref() + const state = await provider?.getState() + const isMultipleNativeToolCallsEnabled = experiments.isEnabled( + state?.experiments ?? {}, + EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS, + ) + const pushToolResult = (content: ToolResponse) => { if (toolProtocol === TOOL_PROTOCOL.NATIVE) { // For native protocol, only allow ONE tool_result per tool call @@ -337,13 +345,19 @@ export async function presentAssistantMessage(cline: Task) { } // For XML protocol: Only one tool per message is allowed - // For native protocol: Multiple tools can be executed in sequence + // For native protocol with experimental flag enabled: Multiple tools can be executed in sequence + // For native protocol with experimental flag disabled: Single tool per message (default safe behavior) if (toolProtocol === TOOL_PROTOCOL.XML) { // Once a tool result has been collected, ignore all other tool // uses since we should only ever present one tool result per // message (XML protocol only). cline.didAlreadyUseTool = true + } else if (toolProtocol === TOOL_PROTOCOL.NATIVE && !isMultipleNativeToolCallsEnabled) { + // For native protocol with experimental flag disabled, enforce single tool per message + cline.didAlreadyUseTool = true } + // If toolProtocol is NATIVE and isMultipleNativeToolCallsEnabled is true, + // allow multiple tool calls in sequence (don't set didAlreadyUseTool) } const askApproval = async ( diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 8a3c3004416..23116b19b20 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -31,6 +31,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multipleNativeToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -42,6 +43,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multipleNativeToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -53,6 +55,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multipleNativeToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 90495c56b70..0b11edfcdf0 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -6,6 +6,7 @@ export const EXPERIMENT_IDS = { PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption", IMAGE_GENERATION: "imageGeneration", RUN_SLASH_COMMAND: "runSlashCommand", + MULTIPLE_NATIVE_TOOL_CALLS: "multipleNativeToolCalls", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -22,6 +23,7 @@ export const experimentConfigsMap: Record = { PREVENT_FOCUS_DISRUPTION: { enabled: false }, IMAGE_GENERATION: { enabled: false }, RUN_SLASH_COMMAND: { enabled: false }, + MULTIPLE_NATIVE_TOOL_CALLS: { enabled: false }, } export const experimentDefault = Object.fromEntries( From 3b19de67a2b36f2261c0fa734455fba8b9ed1945 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 25 Nov 2025 16:46:09 -0500 Subject: [PATCH 6/9] fix: restore main version of presentAssistantMessage with PR changes --- .../presentAssistantMessage.ts | 82 ++++++++++++------- .../__tests__/ExtensionStateContext.spec.tsx | 2 + 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index e8d4720bef8..71cda2f0a0f 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -251,16 +251,25 @@ export async function presentAssistantMessage(cline: Task) { if (cline.didRejectTool) { // Ignore any tool content after user has rejected tool once. - if (!block.partial) { + // For native protocol, we must send a tool_result for every tool_use to avoid API errors + const toolCallId = block.id + const errorMessage = !block.partial + ? `Skipping tool ${toolDescription()} due to user rejecting a previous tool.` + : `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.` + + if (toolCallId) { + // Native protocol: MUST send tool_result for every tool_use cline.userMessageContent.push({ - type: "text", - text: `Skipping tool ${toolDescription()} due to user rejecting a previous tool.`, - }) + type: "tool_result", + tool_use_id: toolCallId, + content: errorMessage, + is_error: true, + } as Anthropic.ToolResultBlockParam) } else { - // Partial tool after user rejected a previous tool. + // XML protocol: send as text cline.userMessageContent.push({ type: "text", - text: `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.`, + text: errorMessage, }) } @@ -269,10 +278,25 @@ export async function presentAssistantMessage(cline: Task) { if (cline.didAlreadyUseTool) { // Ignore any content after a tool has already been used. - cline.userMessageContent.push({ - type: "text", - text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`, - }) + // For native protocol, we must send a tool_result for every tool_use to avoid API errors + const toolCallId = block.id + const errorMessage = `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.` + + if (toolCallId) { + // Native protocol: MUST send tool_result for every tool_use + cline.userMessageContent.push({ + type: "tool_result", + tool_use_id: toolCallId, + content: errorMessage, + is_error: true, + } as Anthropic.ToolResultBlockParam) + } else { + // XML protocol: send as text + cline.userMessageContent.push({ + type: "text", + text: errorMessage, + }) + } break } @@ -304,31 +328,36 @@ export async function presentAssistantMessage(cline: Task) { return } - // For native protocol, add as tool_result block + // For native protocol, tool_result content must be a string + // Images are added as separate blocks in the user message let resultContent: string + let imageBlocks: Anthropic.ImageBlockParam[] = [] + if (typeof content === "string") { resultContent = content || "(tool did not return anything)" } else { - // Convert array of content blocks to string for tool result - // Tool results in OpenAI format only support strings - resultContent = content - .map((item) => { - if (item.type === "text") { - return item.text - } else if (item.type === "image") { - return "(image content)" - } - return "" - }) - .join("\n") + // Separate text and image blocks + const textBlocks = content.filter((item) => item.type === "text") + imageBlocks = content.filter((item) => item.type === "image") as Anthropic.ImageBlockParam[] + + // Convert text blocks to string for tool_result + resultContent = + textBlocks.map((item) => (item as Anthropic.TextBlockParam).text).join("\n") || + "(tool did not return anything)" } + // Add tool_result with text content only cline.userMessageContent.push({ type: "tool_result", tool_use_id: toolCallId, content: resultContent, } as Anthropic.ToolResultBlockParam) + // Add image blocks separately after tool_result + if (imageBlocks.length > 0) { + cline.userMessageContent.push(...imageBlocks) + } + hasToolResult = true } else { // For XML protocol, add as text blocks (legacy behavior) @@ -420,9 +449,6 @@ export async function presentAssistantMessage(cline: Task) { ) pushToolResult(formatResponse.toolError(errorString, toolProtocol)) - - // Mark that a tool failed in this turn to prevent attempt_completion - cline.didToolFailInCurrentTurn = true } // If block is partial, remove partial closing tag so its not @@ -498,7 +524,6 @@ export async function presentAssistantMessage(cline: Task) { ) } catch (error) { cline.consecutiveMistakeCount++ - cline.didToolFailInCurrentTurn = true pushToolResult(formatResponse.toolError(error.message, toolProtocol)) break } @@ -534,9 +559,6 @@ export async function presentAssistantMessage(cline: Task) { TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId) } - // Mark that a tool failed in this turn to prevent attempt_completion - cline.didToolFailInCurrentTurn = true - // Return tool result message about the repetition pushToolResult( formatResponse.toolError( diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 28899e342a0..dca353d1086 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -239,6 +239,7 @@ describe("mergeExtensionState", () => { imageGeneration: false, runSlashCommand: false, nativeToolCalling: false, + multipleNativeToolCalls: false, } as Record, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS + 5, } @@ -261,6 +262,7 @@ describe("mergeExtensionState", () => { imageGeneration: false, runSlashCommand: false, nativeToolCalling: false, + multipleNativeToolCalls: false, }) }) }) From e5ecc2924aa82f79b1fbec3dedf9d27a5e3da0d5 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 25 Nov 2025 18:04:29 -0500 Subject: [PATCH 7/9] feat: add support for multiple native tool calls in localization settings --- 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 ++++ webview-ui/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 ++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-TW/settings.json | 4 ++++ 18 files changed, 72 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 6b9dce485c7..15670eba2bd 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -789,6 +789,10 @@ "RUN_SLASH_COMMAND": { "name": "Habilitar comandes de barra diagonal iniciades pel model", "description": "Quan està habilitat, Roo pot executar les vostres comandes de barra diagonal per executar fluxos de treball." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Crides paral·leles a eines", + "description": "Quan està activat, el protocol natiu pot executar múltiples eines en un sol torn de missatge de l'assistent." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index cf8a5247bed..eff297dbcb5 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -789,6 +789,10 @@ "RUN_SLASH_COMMAND": { "name": "Modellinitierte Slash-Befehle aktivieren", "description": "Wenn aktiviert, kann Roo deine Slash-Befehle ausführen, um Workflows zu starten." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Parallele Tool-Aufrufe", + "description": "Wenn aktiviert, kann das native Protokoll mehrere Tools in einer einzigen Assistenten-Nachrichtenrunde ausführen." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 82b2df3f686..4b9d7bfc1cc 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -794,6 +794,10 @@ "RUN_SLASH_COMMAND": { "name": "Enable model-initiated slash commands", "description": "When enabled, Roo can run your slash commands to execute workflows." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Parallel tool calls", + "description": "When enabled, the native protocol can execute multiple tools in a single assistant message turn." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index d0f2f5e1b7b..a642706ca35 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -789,6 +789,10 @@ "RUN_SLASH_COMMAND": { "name": "Habilitar comandos slash iniciados por el modelo", "description": "Cuando está habilitado, Roo puede ejecutar tus comandos slash para ejecutar flujos de trabajo." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Llamadas paralelas a herramientas", + "description": "Cuando está habilitado, el protocolo nativo puede ejecutar múltiples herramientas en un solo turno de mensaje del asistente." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 0a5e1488a1a..212f204308e 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -789,6 +789,10 @@ "RUN_SLASH_COMMAND": { "name": "Activer les commandes slash initiées par le modèle", "description": "Lorsque activé, Roo peut exécuter tes commandes slash pour lancer des workflows." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Appels d'outils parallèles", + "description": "Lorsqu'activé, le protocole natif peut exécuter plusieurs outils en un seul tour de message d'assistant." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 231facd5352..9c991912ca7 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "मॉडल द्वारा शुरू किए गए स्लैश कमांड सक्षम करें", "description": "जब सक्षम होता है, Roo वर्कफ़्लो चलाने के लिए आपके स्लैश कमांड चला सकता है।" + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "समानांतर टूल कॉल", + "description": "सक्षम होने पर, नेटिव प्रोटोकॉल एकल सहायक संदेश टर्न में एकाधिक टूल निष्पादित कर सकता है।" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index d003bdec575..3682d44c4ac 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -819,6 +819,10 @@ "RUN_SLASH_COMMAND": { "name": "Aktifkan perintah slash yang dimulai model", "description": "Ketika diaktifkan, Roo dapat menjalankan perintah slash Anda untuk mengeksekusi alur kerja." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Panggilan tool paralel", + "description": "Ketika diaktifkan, protokol native dapat mengeksekusi beberapa tool dalam satu giliran pesan asisten." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 3c014d1fa93..7b98f2b8e0b 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Abilita comandi slash avviati dal modello", "description": "Quando abilitato, Roo può eseguire i tuoi comandi slash per eseguire flussi di lavoro." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Chiamate parallele agli strumenti", + "description": "Quando abilitato, il protocollo nativo può eseguire più strumenti in un singolo turno di messaggio dell'assistente." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index bcaeb389c8d..25675f365f5 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "モデル開始スラッシュコマンドを有効にする", "description": "有効にすると、Rooがワークフローを実行するためにあなたのスラッシュコマンドを実行できます。" + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "並列ツール呼び出し", + "description": "有効にすると、ネイティブプロトコルは単一のアシスタントメッセージターンで複数のツールを実行できます。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 835e872e301..d0ab63dec0e 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "모델 시작 슬래시 명령 활성화", "description": "활성화되면 Roo가 워크플로를 실행하기 위해 슬래시 명령을 실행할 수 있습니다." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "병렬 도구 호출", + "description": "활성화되면 네이티브 프로토콜이 단일 어시스턴트 메시지 턴에서 여러 도구를 실행할 수 있습니다." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 7bcaf7d7207..e03ab710371 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Model-geïnitieerde slash-commando's inschakelen", "description": "Wanneer ingeschakeld, kan Roo je slash-commando's uitvoeren om workflows uit te voeren." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Parallelle tool-aanroepen", + "description": "Wanneer ingeschakeld, kan het native protocol meerdere tools uitvoeren in één enkele assistent-berichtbeurt." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index f39e6f8b2a8..e45782f52f9 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Włącz polecenia slash inicjowane przez model", "description": "Gdy włączone, Roo może uruchamiać twoje polecenia slash w celu wykonywania przepływów pracy." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Równoległe wywołania narzędzi", + "description": "Po włączeniu protokół natywny może wykonywać wiele narzędzi w jednej turze wiadomości asystenta." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index b9a623cc418..cdd5a98ad7d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Ativar comandos slash iniciados pelo modelo", "description": "Quando ativado, Roo pode executar seus comandos slash para executar fluxos de trabalho." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Chamadas paralelas de ferramentas", + "description": "Quando habilitado, o protocolo nativo pode executar múltiplas ferramentas em um único turno de mensagem do assistente." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 240360dd344..5130b697c14 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Включить слэш-команды, инициированные моделью", "description": "Когда включено, Roo может выполнять ваши слэш-команды для выполнения рабочих процессов." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Параллельные вызовы инструментов", + "description": "При включении нативный протокол может выполнять несколько инструментов в одном ходе сообщения ассистента." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 4885c3e0170..1aa8a93fb4c 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Model tarafından başlatılan slash komutlarını etkinleştir", "description": "Etkinleştirildiğinde, Roo iş akışlarını yürütmek için slash komutlarınızı çalıştırabilir." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Paralel araç çağrıları", + "description": "Etkinleştirildiğinde, yerel protokol tek bir asistan mesaj turunda birden fazla araç yürütebilir." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index b7c4db68f05..c5521501e3b 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "Bật lệnh slash do mô hình khởi tạo", "description": "Khi được bật, Roo có thể chạy các lệnh slash của bạn để thực hiện các quy trình làm việc." + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "Lệnh gọi công cụ song song", + "description": "Khi được bật, giao thức native có thể thực thi nhiều công cụ trong một lượt tin nhắn của trợ lý." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 39667ddbb13..be0af14c351 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "启用模型发起的斜杠命令", "description": "启用后 Roo 可运行斜杠命令执行工作流程。" + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "并行工具调用", + "description": "启用后,原生协议可在单个助手消息轮次中执行多个工具。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index b41739e3677..9a551f1cbd6 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -790,6 +790,10 @@ "RUN_SLASH_COMMAND": { "name": "啟用模型啟動的斜線命令", "description": "啟用時,Roo 可以執行您的斜線命令來執行工作流程。" + }, + "MULTIPLE_NATIVE_TOOL_CALLS": { + "name": "並行工具呼叫", + "description": "啟用後,原生協定可在單個助理訊息輪次中執行多個工具。" } }, "promptCaching": { From ef8f0a252f1206c498a853c0532db0610922bb09 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 25 Nov 2025 19:12:42 -0500 Subject: [PATCH 8/9] fix: address comments --- src/core/task/Task.ts | 32 ------------------- src/core/tools/AttemptCompletionTool.ts | 3 +- webview-ui/src/i18n/locales/ca/common.json | 3 +- webview-ui/src/i18n/locales/de/common.json | 3 +- webview-ui/src/i18n/locales/en/common.json | 3 +- webview-ui/src/i18n/locales/es/common.json | 3 +- webview-ui/src/i18n/locales/fr/common.json | 3 +- webview-ui/src/i18n/locales/hi/common.json | 3 +- webview-ui/src/i18n/locales/id/common.json | 3 +- webview-ui/src/i18n/locales/it/common.json | 3 +- webview-ui/src/i18n/locales/ja/common.json | 3 +- webview-ui/src/i18n/locales/ko/common.json | 3 +- webview-ui/src/i18n/locales/nl/common.json | 3 +- webview-ui/src/i18n/locales/pl/common.json | 3 +- webview-ui/src/i18n/locales/pt-BR/common.json | 3 +- webview-ui/src/i18n/locales/ru/common.json | 3 +- webview-ui/src/i18n/locales/tr/common.json | 3 +- webview-ui/src/i18n/locales/vi/common.json | 3 +- webview-ui/src/i18n/locales/zh-CN/common.json | 3 +- webview-ui/src/i18n/locales/zh-TW/common.json | 3 +- 20 files changed, 38 insertions(+), 51 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c903995a929..3c2e939cf8c 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3294,38 +3294,6 @@ export class Task extends EventEmitter implements TaskLike { this.currentRequestAbortController = new AbortController() const abortSignal = this.currentRequestAbortController.signal - // Log conversation structure for debugging (omit content) - if (shouldIncludeTools) { - console.log(`[Task#${this.taskId}] Conversation structure being sent to API:`) - cleanConversationHistory.forEach((msg, idx) => { - if ("role" in msg) { - const contentSummary = Array.isArray(msg.content) - ? msg.content - .map((block) => { - if ("type" in block) { - if (block.type === "tool_use") { - return `tool_use(${block.name}, id:${block.id})` - } else if (block.type === "tool_result") { - return `tool_result(id:${block.tool_use_id})` - } else if (block.type === "text") { - return `text(${(block.text as string).substring(0, 50)}...)` - } else if (block.type === "image") { - return "image" - } - } - return "unknown_block" - }) - .join(", ") - : typeof msg.content === "string" - ? `text(${msg.content.substring(0, 50)}...)` - : "unknown_content" - console.log(` [${idx}] ${msg.role}: [${contentSummary}]`) - } else if ("type" in msg && msg.type === "reasoning") { - console.log(` [${idx}] reasoning (encrypted)`) - } - }) - } - // The provider accepts reasoning items alongside standard messages; cast to the expected parameter type. const stream = this.api.createMessage( systemPrompt, diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts index 8e0ed2fc00c..f7551fb4a30 100644 --- a/src/core/tools/AttemptCompletionTool.ts +++ b/src/core/tools/AttemptCompletionTool.ts @@ -9,6 +9,7 @@ import { formatResponse } from "../prompts/responses" import { Package } from "../../shared/package" import { BaseTool, ToolCallbacks } from "./BaseTool" import type { ToolUse } from "../../shared/tools" +import { t } from "../../i18n" interface AttemptCompletionParams { result: string @@ -36,7 +37,7 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { // Prevent attempt_completion if any tool failed in the current turn if (task.didToolFailInCurrentTurn) { - const errorMsg = `Cannot execute attempt_completion because a previous tool call failed in this turn. Please address the tool failure before attempting completion.` + const errorMsg = t("common:errors.attempt_completion_tool_failed") await task.say("error", errorMsg) pushToolResult(formatResponse.toolError(errorMsg)) diff --git a/webview-ui/src/i18n/locales/ca/common.json b/webview-ui/src/i18n/locales/ca/common.json index 883e9c56288..9146480ae00 100644 --- a/webview-ui/src/i18n/locales/ca/common.json +++ b/webview-ui/src/i18n/locales/ca/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Has esperat {{timeout}} segons per inicialitzar el punt de control. Si no necessites aquesta funció, desactiva-la a la configuració del punt de control.", - "init_checkpoint_fail_long_time": "La inicialització del punt de control ha trigat més de {{timeout}} segons, per això els punts de control estan desactivats per a aquesta tasca. Pots desactivar els punts de control o augmentar el temps d'espera a la configuració del punt de control." + "init_checkpoint_fail_long_time": "La inicialització del punt de control ha trigat més de {{timeout}} segons, per això els punts de control estan desactivats per a aquesta tasca. Pots desactivar els punts de control o augmentar el temps d'espera a la configuració del punt de control.", + "attempt_completion_tool_failed": "No es pot executar attempt_completion perquè una crida d'eina anterior ha fallat en aquest torn. Si us plau, resol el problema de l'eina abans d'intentar completar." } } diff --git a/webview-ui/src/i18n/locales/de/common.json b/webview-ui/src/i18n/locales/de/common.json index fe1d6c41c74..a6a3c683b44 100644 --- a/webview-ui/src/i18n/locales/de/common.json +++ b/webview-ui/src/i18n/locales/de/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Du hast {{timeout}} Sekunden auf die Initialisierung des Checkpoints gewartet. Wenn du die Checkpoint-Funktion nicht brauchst, kannst du sie in den Checkpoint-Einstellungen ausschalten.", - "init_checkpoint_fail_long_time": "Die Initialisierung des Checkpoints dauert länger als {{timeout}} Sekunden, deshalb sind Checkpoints für diese Aufgabe deaktiviert. Du kannst Checkpoints ausschalten oder die Wartezeit in den Checkpoint-Einstellungen verlängern." + "init_checkpoint_fail_long_time": "Die Initialisierung des Checkpoints dauert länger als {{timeout}} Sekunden, deshalb sind Checkpoints für diese Aufgabe deaktiviert. Du kannst Checkpoints ausschalten oder die Wartezeit in den Checkpoint-Einstellungen verlängern.", + "attempt_completion_tool_failed": "Du kannst attempt_completion nicht ausführen, weil ein vorheriger Tool-Aufruf in diesem Durchgang fehlgeschlagen ist. Behebe den Tool-Fehler, bevor du versuchst, abzuschließen." } } diff --git a/webview-ui/src/i18n/locales/en/common.json b/webview-ui/src/i18n/locales/en/common.json index e0c36548e57..77b014eae2f 100644 --- a/webview-ui/src/i18n/locales/en/common.json +++ b/webview-ui/src/i18n/locales/en/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Waited {{timeout}} seconds for checkpoint initialization. If you don't need the checkpoint feature, please turn it off in the checkpoint settings.", - "init_checkpoint_fail_long_time": "Checkpoint initialization has taken more than {{timeout}} seconds, so checkpoints are disabled for this task. You can disable checkpoints or extend the waiting time in the checkpoint settings." + "init_checkpoint_fail_long_time": "Checkpoint initialization has taken more than {{timeout}} seconds, so checkpoints are disabled for this task. You can disable checkpoints or extend the waiting time in the checkpoint settings.", + "attempt_completion_tool_failed": "Cannot execute attempt_completion because a previous tool call failed in this turn. Please address the tool failure before attempting completion." } } diff --git a/webview-ui/src/i18n/locales/es/common.json b/webview-ui/src/i18n/locales/es/common.json index 28707a5e358..8efa7834864 100644 --- a/webview-ui/src/i18n/locales/es/common.json +++ b/webview-ui/src/i18n/locales/es/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Has esperado {{timeout}} segundos para la inicialización del punto de control. Si no necesitas esta función, desactívala en la configuración del punto de control.", - "init_checkpoint_fail_long_time": "La inicialización del punto de control ha tardado más de {{timeout}} segundos, por lo que los puntos de control están desactivados para esta tarea. Puedes desactivar los puntos de control o aumentar el tiempo de espera en la configuración del punto de control." + "init_checkpoint_fail_long_time": "La inicialización del punto de control ha tardado más de {{timeout}} segundos, por lo que los puntos de control están desactivados para esta tarea. Puedes desactivar los puntos de control o aumentar el tiempo de espera en la configuración del punto de control.", + "attempt_completion_tool_failed": "No se puede ejecutar attempt_completion porque una llamada de herramienta anterior falló en este turno. Por favor, resuelve el error de la herramienta antes de intentar completar." } } diff --git a/webview-ui/src/i18n/locales/fr/common.json b/webview-ui/src/i18n/locales/fr/common.json index f52b20c9a1b..70c8ac95115 100644 --- a/webview-ui/src/i18n/locales/fr/common.json +++ b/webview-ui/src/i18n/locales/fr/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Tu as attendu {{timeout}} secondes pour l'initialisation du checkpoint. Si tu n'as pas besoin de cette fonction, désactive-la dans les paramètres du checkpoint.", - "init_checkpoint_fail_long_time": "L'initialisation du checkpoint a pris plus de {{timeout}} secondes, donc les checkpoints sont désactivés pour cette tâche. Tu peux désactiver les checkpoints ou prolonger le délai dans les paramètres du checkpoint." + "init_checkpoint_fail_long_time": "L'initialisation du checkpoint a pris plus de {{timeout}} secondes, donc les checkpoints sont désactivés pour cette tâche. Tu peux désactiver les checkpoints ou prolonger le délai dans les paramètres du checkpoint.", + "attempt_completion_tool_failed": "Tu ne peux pas exécuter attempt_completion car un appel d'outil précédent a échoué dans ce tour. Résous l'échec de l'outil avant de tenter de terminer." } } diff --git a/webview-ui/src/i18n/locales/hi/common.json b/webview-ui/src/i18n/locales/hi/common.json index 544ec3334db..4b97bf73f8b 100644 --- a/webview-ui/src/i18n/locales/hi/common.json +++ b/webview-ui/src/i18n/locales/hi/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "तुमने {{timeout}} सेकंड तक चेकपॉइंट इनिशियलाइज़ेशन का इंतजार किया। अगर तुम्हें यह फ़ीचर नहीं चाहिए, तो चेकपॉइंट सेटिंग्स में बंद कर दो।", - "init_checkpoint_fail_long_time": "चेकपॉइंट इनिशियलाइज़ेशन {{timeout}} सेकंड से ज़्यादा समय ले रहा है, इसलिए इस कार्य के लिए चेकपॉइंट बंद कर दिए गए हैं। तुम चेकपॉइंट बंद कर सकते हो या चेकपॉइंट सेटिंग्स में इंतजार का समय बढ़ा सकते हो।" + "init_checkpoint_fail_long_time": "चेकपॉइंट इनिशियलाइज़ेशन {{timeout}} सेकंड से ज़्यादा समय ले रहा है, इसलिए इस कार्य के लिए चेकपॉइंट बंद कर दिए गए हैं। तुम चेकपॉइंट बंद कर सकते हो या चेकपॉइंट सेटिंग्स में इंतजार का समय बढ़ा सकते हो।", + "attempt_completion_tool_failed": "attempt_completion निष्पादित नहीं किया जा सकता क्योंकि इस टर्न में पिछली टूल कॉल विफल रही है। कृपया पूरा करने का प्रयास करने से पहले टूल विफलता को ठीक करें।" } } diff --git a/webview-ui/src/i18n/locales/id/common.json b/webview-ui/src/i18n/locales/id/common.json index a302aa06a58..27c03c4b023 100644 --- a/webview-ui/src/i18n/locales/id/common.json +++ b/webview-ui/src/i18n/locales/id/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Kamu sudah menunggu {{timeout}} detik untuk inisialisasi checkpoint. Kalau tidak butuh fitur ini, matikan saja di pengaturan checkpoint.", - "init_checkpoint_fail_long_time": "Inisialisasi checkpoint sudah lebih dari {{timeout}} detik, jadi checkpoint dinonaktifkan untuk tugas ini. Kamu bisa mematikan checkpoint atau menambah waktu tunggu di pengaturan checkpoint." + "init_checkpoint_fail_long_time": "Inisialisasi checkpoint sudah lebih dari {{timeout}} detik, jadi checkpoint dinonaktifkan untuk tugas ini. Kamu bisa mematikan checkpoint atau menambah waktu tunggu di pengaturan checkpoint.", + "attempt_completion_tool_failed": "Tidak dapat mengeksekusi attempt_completion karena panggilan alat sebelumnya gagal dalam giliran ini. Harap atasi kegagalan alat sebelum mencoba menyelesaikan." } } diff --git a/webview-ui/src/i18n/locales/it/common.json b/webview-ui/src/i18n/locales/it/common.json index 78f4db65489..0a1c7396dc3 100644 --- a/webview-ui/src/i18n/locales/it/common.json +++ b/webview-ui/src/i18n/locales/it/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Hai aspettato {{timeout}} secondi per l'inizializzazione del checkpoint. Se non ti serve questa funzione, disattivala nelle impostazioni del checkpoint.", - "init_checkpoint_fail_long_time": "L'inizializzazione del checkpoint ha impiegato più di {{timeout}} secondi, quindi i checkpoint sono disabilitati per questa attività. Puoi disattivare i checkpoint o aumentare il tempo di attesa nelle impostazioni del checkpoint." + "init_checkpoint_fail_long_time": "L'inizializzazione del checkpoint ha impiegato più di {{timeout}} secondi, quindi i checkpoint sono disabilitati per questa attività. Puoi disattivare i checkpoint o aumentare il tempo di attesa nelle impostazioni del checkpoint.", + "attempt_completion_tool_failed": "Non puoi eseguire attempt_completion perché una chiamata di strumento precedente è fallita in questo turno. Risolvi il problema dello strumento prima di tentare di completare." } } diff --git a/webview-ui/src/i18n/locales/ja/common.json b/webview-ui/src/i18n/locales/ja/common.json index d82ff96eb9c..12a8cd0c7e3 100644 --- a/webview-ui/src/i18n/locales/ja/common.json +++ b/webview-ui/src/i18n/locales/ja/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "{{timeout}} 秒間チェックポイントの初期化を待機しました。チェックポイント機能が不要な場合は、チェックポイント設定でオフにしてください。", - "init_checkpoint_fail_long_time": "チェックポイントの初期化が {{timeout}} 秒以上かかったため、このタスクではチェックポイントが無効化されました。チェックポイントをオフにするか、チェックポイント設定で待機時間を延長できます。" + "init_checkpoint_fail_long_time": "チェックポイントの初期化が {{timeout}} 秒以上かかったため、このタスクではチェックポイントが無効化されました。チェックポイントをオフにするか、チェックポイント設定で待機時間を延長できます。", + "attempt_completion_tool_failed": "前回のツール呼び出しがこのターンで失敗したため、attempt_completionを実行できません。完了を試みる前にツールの失敗に対処してください。" } } diff --git a/webview-ui/src/i18n/locales/ko/common.json b/webview-ui/src/i18n/locales/ko/common.json index 0b0bc4b6085..c5c8010d073 100644 --- a/webview-ui/src/i18n/locales/ko/common.json +++ b/webview-ui/src/i18n/locales/ko/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "{{timeout}}초 동안 체크포인트 초기화를 기다렸어. 체크포인트 기능이 필요 없다면 체크포인트 설정에서 꺼 줘.", - "init_checkpoint_fail_long_time": "체크포인트 초기화가 {{timeout}}초 이상 걸려서 이 작업에 대해 체크포인트가 꺼졌어. 체크포인트를 끄거나 체크포인트 설정에서 대기 시간을 늘릴 수 있어." + "init_checkpoint_fail_long_time": "체크포인트 초기화가 {{timeout}}초 이상 걸려서 이 작업에 대해 체크포인트가 꺼졌어. 체크포인트를 끄거나 체크포인트 설정에서 대기 시간을 늘릴 수 있어.", + "attempt_completion_tool_failed": "이전 도구 호출이 이 턴에서 실패했기 때문에 attempt_completion을 실행할 수 없습니다. 완료를 시도하기 전에 도구 실패를 해결하세요." } } diff --git a/webview-ui/src/i18n/locales/nl/common.json b/webview-ui/src/i18n/locales/nl/common.json index 8017cb408a5..a030a8a286a 100644 --- a/webview-ui/src/i18n/locales/nl/common.json +++ b/webview-ui/src/i18n/locales/nl/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Je hebt {{timeout}} seconden gewacht op de initialisatie van de checkpoint. Als je deze functie niet nodig hebt, schakel hem dan uit in de checkpoint-instellingen.", - "init_checkpoint_fail_long_time": "De initialisatie van de checkpoint duurde meer dan {{timeout}} seconden, dus checkpoints zijn uitgeschakeld voor deze taak. Je kunt checkpoints uitschakelen of de wachttijd in de checkpoint-instellingen verhogen." + "init_checkpoint_fail_long_time": "De initialisatie van de checkpoint duurde meer dan {{timeout}} seconden, dus checkpoints zijn uitgeschakeld voor deze taak. Je kunt checkpoints uitschakelen of de wachttijd in de checkpoint-instellingen verhogen.", + "attempt_completion_tool_failed": "Je kunt attempt_completion niet uitvoeren omdat een eerdere tool-aanroep in deze beurt is mislukt. Los het tool-probleem op voordat je probeert te voltooien." } } diff --git a/webview-ui/src/i18n/locales/pl/common.json b/webview-ui/src/i18n/locales/pl/common.json index a938ab1ef15..0eeb2ef15e1 100644 --- a/webview-ui/src/i18n/locales/pl/common.json +++ b/webview-ui/src/i18n/locales/pl/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Czekałeś {{timeout}} sekund na inicjalizację punktu kontrolnego. Jeśli nie potrzebujesz tej funkcji, wyłącz ją w ustawieniach punktu kontrolnego.", - "init_checkpoint_fail_long_time": "Inicjalizacja punktu kontrolnego trwała ponad {{timeout}} sekund, więc punkty kontrolne zostały wyłączone dla tego zadania. Możesz wyłączyć punkty kontrolne lub wydłużyć czas oczekiwania w ustawieniach punktu kontrolnego." + "init_checkpoint_fail_long_time": "Inicjalizacja punktu kontrolnego trwała ponad {{timeout}} sekund, więc punkty kontrolne zostały wyłączone dla tego zadania. Możesz wyłączyć punkty kontrolne lub wydłużyć czas oczekiwania w ustawieniach punktu kontrolnego.", + "attempt_completion_tool_failed": "Nie można wykonać attempt_completion, ponieważ poprzednie wywołanie narzędzia nie powiodło się w tym cyklu. Rozwiąż błąd narzędzia przed próbą zakończenia." } } diff --git a/webview-ui/src/i18n/locales/pt-BR/common.json b/webview-ui/src/i18n/locales/pt-BR/common.json index 7bf0cc6d227..c7519fa24f1 100644 --- a/webview-ui/src/i18n/locales/pt-BR/common.json +++ b/webview-ui/src/i18n/locales/pt-BR/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Você esperou {{timeout}} segundos para inicializar o checkpoint. Se não precisa dessa função, desative nas configurações do checkpoint.", - "init_checkpoint_fail_long_time": "A inicialização do checkpoint levou mais de {{timeout}} segundos, então os checkpoints foram desativados para esta tarefa. Você pode desativar os checkpoints ou aumentar o tempo de espera nas configurações do checkpoint." + "init_checkpoint_fail_long_time": "A inicialização do checkpoint levou mais de {{timeout}} segundos, então os checkpoints foram desativados para esta tarefa. Você pode desativar os checkpoints ou aumentar o tempo de espera nas configurações do checkpoint.", + "attempt_completion_tool_failed": "Não é possível executar attempt_completion porque uma chamada de ferramenta anterior falhou neste turno. Por favor, resolva a falha da ferramenta antes de tentar concluir." } } diff --git a/webview-ui/src/i18n/locales/ru/common.json b/webview-ui/src/i18n/locales/ru/common.json index a455721c105..7e98bf12473 100644 --- a/webview-ui/src/i18n/locales/ru/common.json +++ b/webview-ui/src/i18n/locales/ru/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Ожидание инициализации контрольной точки заняло {{timeout}} секунд. Если тебе не нужна эта функция, отключи её в настройках контрольных точек.", - "init_checkpoint_fail_long_time": "Инициализация контрольной точки заняла более {{timeout}} секунд, поэтому контрольные точки отключены для этой задачи. Ты можешь отключить контрольные точки или увеличить время ожидания в настройках контрольных точек." + "init_checkpoint_fail_long_time": "Инициализация контрольной точки заняла более {{timeout}} секунд, поэтому контрольные точки отключены для этой задачи. Ты можешь отключить контрольные точки или увеличить время ожидания в настройках контрольных точек.", + "attempt_completion_tool_failed": "Невозможно выполнить attempt_completion, потому что предыдущий вызов инструмента не удался в этом повороте. Пожалуйста, устрани сбой инструмента перед попыткой завершения." } } diff --git a/webview-ui/src/i18n/locales/tr/common.json b/webview-ui/src/i18n/locales/tr/common.json index 2b0fed19eaa..fbcdb2179a6 100644 --- a/webview-ui/src/i18n/locales/tr/common.json +++ b/webview-ui/src/i18n/locales/tr/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "{{timeout}} saniye boyunca kontrol noktası başlatılması beklendi. Bu özelliğe ihtiyacın yoksa kontrol noktası ayarlarından kapatabilirsin.", - "init_checkpoint_fail_long_time": "Kontrol noktası başlatılması {{timeout}} saniyeden fazla sürdü, bu yüzden bu görev için kontrol noktaları devre dışı bırakıldı. Kontrol noktalarını kapatabilir veya kontrol noktası ayarlarından bekleme süresini artırabilirsin." + "init_checkpoint_fail_long_time": "Kontrol noktası başlatılması {{timeout}} saniyeden fazla sürdü, bu yüzden bu görev için kontrol noktaları devre dışı bırakıldı. Kontrol noktalarını kapatabilir veya kontrol noktası ayarlarından bekleme süresini artırabilirsin.", + "attempt_completion_tool_failed": "attempt_completion çalıştırılamıyor çünkü bu turda önceki bir araç çağrısı başarısız oldu. Lütfen tamamlamayı denemeden önce araç hatasını giderin." } } diff --git a/webview-ui/src/i18n/locales/vi/common.json b/webview-ui/src/i18n/locales/vi/common.json index a7f8c692f9d..8ccf58dac74 100644 --- a/webview-ui/src/i18n/locales/vi/common.json +++ b/webview-ui/src/i18n/locales/vi/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "Bạn đã chờ {{timeout}} giây để khởi tạo điểm kiểm tra. Nếu không cần chức năng này, hãy tắt nó trong cài đặt điểm kiểm tra.", - "init_checkpoint_fail_long_time": "Khởi tạo điểm kiểm tra mất hơn {{timeout}} giây, vì vậy các điểm kiểm tra đã bị vô hiệu hóa cho tác vụ này. Bạn có thể tắt các điểm kiểm tra hoặc tăng thời gian chờ trong cài đặt điểm kiểm tra." + "init_checkpoint_fail_long_time": "Khởi tạo điểm kiểm tra mất hơn {{timeout}} giây, vì vậy các điểm kiểm tra đã bị vô hiệu hóa cho tác vụ này. Bạn có thể tắt các điểm kiểm tra hoặc tăng thời gian chờ trong cài đặt điểm kiểm tra.", + "attempt_completion_tool_failed": "Không thể thực thi attempt_completion vì một lệnh gọi công cụ trước đó đã thất bại trong lượt này. Vui lòng giải quyết lỗi công cụ trước khi cố gắng hoàn thành." } } diff --git a/webview-ui/src/i18n/locales/zh-CN/common.json b/webview-ui/src/i18n/locales/zh-CN/common.json index 8271e0a25eb..53da4313be8 100644 --- a/webview-ui/src/i18n/locales/zh-CN/common.json +++ b/webview-ui/src/i18n/locales/zh-CN/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "初始化存档点已等待 {{timeout}} 秒。如果你不需要存档点功能,请在存档点设置中关闭。", - "init_checkpoint_fail_long_time": "存档点初始化已超过 {{timeout}} 秒,因此本任务已禁用存档点。你可以关闭存档点或在存档点设置中延长等待时间。" + "init_checkpoint_fail_long_time": "存档点初始化已超过 {{timeout}} 秒,因此本任务已禁用存档点。你可以关闭存档点或在存档点设置中延长等待时间。", + "attempt_completion_tool_failed": "无法执行 attempt_completion,因为本轮中先前的工具调用失败了。请在尝试完成前解决工具失败问题。" } } diff --git a/webview-ui/src/i18n/locales/zh-TW/common.json b/webview-ui/src/i18n/locales/zh-TW/common.json index 783129920f6..9b2a69c77d1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/common.json +++ b/webview-ui/src/i18n/locales/zh-TW/common.json @@ -98,6 +98,7 @@ }, "errors": { "wait_checkpoint_long_time": "初始化存檔點已等待 {{timeout}} 秒。如果你不需要存檔點功能,請在存檔點設定中關閉。", - "init_checkpoint_fail_long_time": "存檔點初始化已超過 {{timeout}} 秒,因此此工作已停用存檔點。你可以關閉存檔點或在存檔點設定中延長等待時間。" + "init_checkpoint_fail_long_time": "存檔點初始化已超過 {{timeout}} 秒,因此此工作已停用存檔點。你可以關閉存檔點或在存檔點設定中延長等待時間。", + "attempt_completion_tool_failed": "無法執行 attempt_completion,因為本輪中先前的工具呼叫失敗了。請在嘗試完成前解決工具失敗問題。" } } From 999c0cafb0bfe92192777f4dd88c1254847149aa Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 25 Nov 2025 19:18:50 -0500 Subject: [PATCH 9/9] fix: update error messages in attemptCompletionTool tests for consistency --- src/core/tools/__tests__/attemptCompletionTool.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/tools/__tests__/attemptCompletionTool.spec.ts b/src/core/tools/__tests__/attemptCompletionTool.spec.ts index a737789ca79..3950e3ead71 100644 --- a/src/core/tools/__tests__/attemptCompletionTool.spec.ts +++ b/src/core/tools/__tests__/attemptCompletionTool.spec.ts @@ -438,10 +438,10 @@ describe("attemptCompletionTool", () => { expect(mockSay).toHaveBeenCalledWith( "error", - expect.stringContaining("Cannot execute attempt_completion because a previous tool call failed"), + expect.stringContaining("errors.attempt_completion_tool_failed"), ) expect(mockPushToolResult).toHaveBeenCalledWith( - expect.stringContaining("Cannot execute attempt_completion because a previous tool call failed"), + expect.stringContaining("errors.attempt_completion_tool_failed"), ) })