diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 344af8bfcc..ca922df543 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -82,7 +82,7 @@ export async function presentAssistantMessage(cline: Task) { switch (block.type) { case "text": { - if (cline.didRejectTool || cline.didAlreadyUseTool) { + if (cline.didRejectTool) { break } @@ -235,8 +235,9 @@ export async function presentAssistantMessage(cline: Task) { break } - if (cline.didAlreadyUseTool) { + if (cline.didAlreadyUseTool && block.name !== "update_todo_list") { // Ignore any content after a tool has already been used. + // Exception: update_todo_list can be used multiple times 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.`, @@ -256,8 +257,7 @@ export async function presentAssistantMessage(cline: Task) { // Once a tool result has been collected, ignore all other tool // uses since we should only ever present one tool result per - // message. Exception: update_todo_list is allowed to be followed - // by text content. + // message. Exception: update_todo_list can be used multiple times. if (block.name !== "update_todo_list") { cline.didAlreadyUseTool = true } @@ -554,7 +554,12 @@ export async function presentAssistantMessage(cline: Task) { // skip execution since `didRejectTool` and iterate until `contentIndex` is // set to message length and it sets userMessageContentReady to true itself // (instead of preemptively doing it in iterator). - if (!block.partial || cline.didRejectTool || cline.didAlreadyUseTool) { + if ( + !block.partial || + cline.didRejectTool || + (cline.didAlreadyUseTool && block.type !== "tool_use") || + (cline.didAlreadyUseTool && block.type === "tool_use" && block.name !== "update_todo_list") + ) { // Block is finished streaming and executing. if (cline.currentStreamingContentIndex === cline.assistantMessageContent.length - 1) { // It's okay that we increment if !didCompleteReadingStream, it'll diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 53b8ef5b87..112b711629 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1427,7 +1427,13 @@ export class Task extends EventEmitter { // get generation details. // UPDATE: It's better UX to interrupt the request at the // cost of the API cost not being retrieved. - if (this.didAlreadyUseTool) { + // Exception: update_todo_list can be used multiple times + // Check if any update_todo_list is being processed (partial or complete) + const hasUpdateTodoList = this.assistantMessageContent.some( + (block) => block.type === "tool_use" && block.name === "update_todo_list", + ) + + if (this.didAlreadyUseTool && !hasUpdateTodoList) { assistantMessage += "\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]" break