fix: allow text after update_todo_list and support multiple calls

- Modified presentAssistantMessage.ts to skip tool blocking for update_todo_list
- Prevented setting didAlreadyUseTool flag when update_todo_list is used
- Updated Task.ts to check for update_todo_list before interrupting stream
- Allows update_todo_list to be called multiple times in a single message
- Text can now appear after update_todo_list tool calls

Fixes #5847
This commit is contained in:
Daniel Riccio 2025-07-21 09:39:52 -05:00
parent 4f5549391e
commit d2c1a2655c
No known key found for this signature in database
GPG key ID: FFD5FD825F8E8209
2 changed files with 17 additions and 6 deletions

View file

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

View file

@ -1427,7 +1427,13 @@ export class Task extends EventEmitter<ClineEvents> {
// 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