From be3bda8136ceef5b0e68984ecf08221a9bd09127 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Sat, 11 Oct 2025 16:06:30 -0600 Subject: [PATCH 1/4] Remove XML-like / wrappers; process plain user input; update code/tests --- .../__tests__/processUserContentMentions.spec.ts | 13 ++++++++----- src/core/mentions/processUserContentMentions.ts | 6 +----- src/core/prompts/responses.ts | 6 +++--- src/core/task/Task.ts | 2 +- src/core/task/__tests__/Task.spec.ts | 10 ++++++---- src/core/tools/attemptCompletionTool.ts | 2 +- src/core/tools/executeCommandTool.ts | 3 +-- src/services/command/built-in-commands.ts | 4 +--- 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/core/mentions/__tests__/processUserContentMentions.spec.ts b/src/core/mentions/__tests__/processUserContentMentions.spec.ts index 13c225042d75..6effcc72c0f7 100644 --- a/src/core/mentions/__tests__/processUserContentMentions.spec.ts +++ b/src/core/mentions/__tests__/processUserContentMentions.spec.ts @@ -162,7 +162,7 @@ describe("processUserContentMentions", () => { }) }) - it("should not process text blocks without task or feedback tags", async () => { + it("should process text blocks without special tags", async () => { const userContent = [ { type: "text" as const, @@ -177,8 +177,11 @@ describe("processUserContentMentions", () => { fileContextTracker: mockFileContextTracker, }) - expect(parseMentions).not.toHaveBeenCalled() - expect(result[0]).toEqual(userContent[0]) + expect(parseMentions).toHaveBeenCalled() + expect(result[0]).toEqual({ + type: "text", + text: "parsed: Regular text without special tags", + }) }) it("should process tool_result blocks with string content", async () => { @@ -230,7 +233,7 @@ describe("processUserContentMentions", () => { fileContextTracker: mockFileContextTracker, }) - expect(parseMentions).toHaveBeenCalledTimes(1) + expect(parseMentions).toHaveBeenCalledTimes(2) expect(result[0]).toEqual({ type: "tool_result", tool_use_id: "123", @@ -241,7 +244,7 @@ describe("processUserContentMentions", () => { }, { type: "text", - text: "Regular text", + text: "parsed: Regular text", }, ], }) diff --git a/src/core/mentions/processUserContentMentions.ts b/src/core/mentions/processUserContentMentions.ts index 4bdb422d48ba..148b3c5227b5 100644 --- a/src/core/mentions/processUserContentMentions.ts +++ b/src/core/mentions/processUserContentMentions.ts @@ -39,11 +39,7 @@ export async function processUserContentMentions({ // should parse mentions). return Promise.all( userContent.map(async (block) => { - const shouldProcessMentions = (text: string) => - text.includes("") || - text.includes("") || - text.includes("") || - text.includes("") + const shouldProcessMentions = (_text: string) => true if (block.type === "text") { if (shouldProcessMentions(block.text)) { diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index fd51b18feda4..cf4de10026af 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -8,10 +8,10 @@ export const formatResponse = { toolDenied: () => `The user denied this operation.`, toolDeniedWithFeedback: (feedback?: string) => - `The user denied this operation and provided the following feedback:\n\n${feedback}\n`, + `The user denied this operation and provided the following feedback:\n${feedback}`, toolApprovedWithFeedback: (feedback?: string) => - `The user approved this operation and provided the following context:\n\n${feedback}\n`, + `The user approved this operation and provided the following context:\n${feedback}`, toolError: (error?: string) => `The tool execution failed with the following error:\n\n${error}\n`, @@ -31,7 +31,7 @@ Otherwise, if you have not completed the task and do not need additional informa (This is an automated message, so do not respond to it conversationally.)`, tooManyMistakes: (feedback?: string) => - `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n\n${feedback}\n`, + `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n${feedback}`, missingToolParameterError: (paramName: string) => `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 851df91e6c5e..9e965495d8fb 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1222,7 +1222,7 @@ export class Task extends EventEmitter implements TaskLike { await this.initiateTaskLoop([ { type: "text", - text: `\n${task}\n`, + text: task ?? "", }, ...imageBlocks, ]) diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 116c78d76096..0414b26a7b4c 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -918,8 +918,9 @@ describe("Cline", () => { fileContextTracker: cline.fileContextTracker, }) - // Regular text should not be processed - expect((processedContent[0] as Anthropic.TextBlockParam).text).toBe( + // Regular text should be processed + expect((processedContent[0] as Anthropic.TextBlockParam).text).toContain("processed:") + expect((processedContent[0] as Anthropic.TextBlockParam).text).toContain( "Regular text with 'some/path' (see below for file content)", ) @@ -937,10 +938,11 @@ describe("Cline", () => { "Check 'some/path' (see below for file content)", ) - // Regular tool result should not be processed + // Regular tool result should be processed now const toolResult2 = processedContent[3] as Anthropic.ToolResultBlockParam const content2 = Array.isArray(toolResult2.content) ? toolResult2.content[0] : toolResult2.content - expect((content2 as Anthropic.TextBlockParam).text).toBe( + expect((content2 as Anthropic.TextBlockParam).text).toContain("processed:") + expect((content2 as Anthropic.TextBlockParam).text).toContain( "Regular tool result with 'path' (see below for file content)", ) diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index 5074d7f4e808..b81ffcdcab8d 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -125,7 +125,7 @@ export async function attemptCompletionTool( toolResults.push({ type: "text", - text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n`, + text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n${text}`, }) toolResults.push(...formatResponse.imageBlocks(images)) diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 2c7ce0d023e2..b5a3e99056ee 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -317,8 +317,7 @@ export async function executeCommand( [ `Command is still running in terminal from '${terminal.getCurrentWorkingDirectory().toPosix()}'.`, result.length > 0 ? `Here's the output so far:\n${result}\n` : "\n", - `The user provided the following feedback:`, - `\n${text}\n`, + `The user provided the following feedback:\n${text}`, ].join("\n"), images, ), diff --git a/src/services/command/built-in-commands.ts b/src/services/command/built-in-commands.ts index db113c489593..921462cfda9a 100644 --- a/src/services/command/built-in-commands.ts +++ b/src/services/command/built-in-commands.ts @@ -11,11 +11,9 @@ const BUILT_IN_COMMANDS: Record = { init: { name: "init", description: "Analyze codebase and create concise AGENTS.md files for AI assistants", - content: ` -Please analyze this codebase and create an AGENTS.md file containing: + content: `Please analyze this codebase and create an AGENTS.md file containing: 1. Build/lint/test commands - especially for running a single test 2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc. - From e2d57e46fa248d3846628717cba750dfabd66c1c Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 3 Nov 2025 23:44:54 +0000 Subject: [PATCH 2/4] docs: update comment to reflect uniform mention parsing behavior --- src/core/mentions/processUserContentMentions.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core/mentions/processUserContentMentions.ts b/src/core/mentions/processUserContentMentions.ts index 148b3c5227b5..be3aee71f3a6 100644 --- a/src/core/mentions/processUserContentMentions.ts +++ b/src/core/mentions/processUserContentMentions.ts @@ -29,14 +29,10 @@ export async function processUserContentMentions({ }) { // Process userContent array, which contains various block types: // TextBlockParam, ImageBlockParam, ToolUseBlockParam, and ToolResultBlockParam. - // We need to apply parseMentions() to: - // 1. All TextBlockParam's text (first user message with task) - // 2. ToolResultBlockParam's content/context text arrays if it contains - // "" (see formatToolDeniedFeedback, attemptCompletion, - // executeCommand, and consecutiveMistakeCount >= 3) or "" - // (see askFollowupQuestion), we place all user generated content in - // these tags so they can effectively be used as markers for when we - // should parse mentions). + // We apply parseMentions() to all text content: + // 1. All TextBlockParam's text + // 2. ToolResultBlockParam's string content + // 3. ToolResultBlockParam's array content where blocks are text type return Promise.all( userContent.map(async (block) => { const shouldProcessMentions = (_text: string) => true From 7171bffd8cc775f895288780622a322d9ddbfb4f Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 3 Nov 2025 23:45:49 +0000 Subject: [PATCH 3/4] fix: remove wrapper from resume flow for plain text consistency --- src/core/task/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 9e965495d8fb..da3060c5b19c 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1475,7 +1475,7 @@ export class Task extends EventEmitter implements TaskLike { if (responseText) { newUserContent.push({ type: "text", - text: `\n\nNew instructions for task continuation:\n\n${responseText}\n`, + text: `\n\nNew instructions for task continuation:\n${responseText}`, }) } From b899f29557db31af11c1f03872d8a020909ff494 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 3 Nov 2025 19:12:03 -0700 Subject: [PATCH 4/4] fix(tools): avoid rendering undefined feedback; omit empty feedback line refactor(mentions): always process text; remove no-op gate; update docstring --- .../mentions/processUserContentMentions.ts | 50 ++++++++----------- src/core/tools/attemptCompletionTool.ts | 3 +- src/core/tools/executeCommandTool.ts | 18 +++---- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/core/mentions/processUserContentMentions.ts b/src/core/mentions/processUserContentMentions.ts index be3aee71f3a6..c4f5b603326f 100644 --- a/src/core/mentions/processUserContentMentions.ts +++ b/src/core/mentions/processUserContentMentions.ts @@ -4,7 +4,7 @@ import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher" import { FileContextTracker } from "../context-tracking/FileContextTracker" /** - * Process mentions in user content, specifically within task and feedback tags + * Process mentions in all user content uniformly (text and tool_result text blocks). */ export async function processUserContentMentions({ userContent, @@ -35,14 +35,27 @@ export async function processUserContentMentions({ // 3. ToolResultBlockParam's array content where blocks are text type return Promise.all( userContent.map(async (block) => { - const shouldProcessMentions = (_text: string) => true - if (block.type === "text") { - if (shouldProcessMentions(block.text)) { + return { + ...block, + text: await parseMentions( + block.text, + cwd, + urlContentFetcher, + fileContextTracker, + rooIgnoreController, + showRooIgnoredFiles, + includeDiagnosticMessages, + maxDiagnosticMessages, + maxReadFileLine, + ), + } + } else if (block.type === "tool_result") { + if (typeof block.content === "string") { return { ...block, - text: await parseMentions( - block.text, + content: await parseMentions( + block.content, cwd, urlContentFetcher, fileContextTracker, @@ -53,33 +66,10 @@ export async function processUserContentMentions({ maxReadFileLine, ), } - } - - return block - } else if (block.type === "tool_result") { - if (typeof block.content === "string") { - if (shouldProcessMentions(block.content)) { - return { - ...block, - content: await parseMentions( - block.content, - cwd, - urlContentFetcher, - fileContextTracker, - rooIgnoreController, - showRooIgnoredFiles, - includeDiagnosticMessages, - maxDiagnosticMessages, - maxReadFileLine, - ), - } - } - - return block } else if (Array.isArray(block.content)) { const parsedContent = await Promise.all( block.content.map(async (contentBlock) => { - if (contentBlock.type === "text" && shouldProcessMentions(contentBlock.text)) { + if (contentBlock.type === "text") { return { ...contentBlock, text: await parseMentions( diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index b81ffcdcab8d..9657e555a48f 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -123,9 +123,10 @@ export async function attemptCompletionTool( await cline.say("user_feedback", text ?? "", images) const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] + const feedbackSuffix = text && text.trim().length > 0 ? `\n${text}` : "" toolResults.push({ type: "text", - text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n${text}`, + text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.${feedbackSuffix}`, }) toolResults.push(...formatResponse.imageBlocks(images)) diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index b5a3e99056ee..59908d22bfbb 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -311,17 +311,15 @@ export async function executeCommand( const { text, images } = message await task.say("user_feedback", text, images) - return [ - true, - formatResponse.toolResult( - [ - `Command is still running in terminal from '${terminal.getCurrentWorkingDirectory().toPosix()}'.`, - result.length > 0 ? `Here's the output so far:\n${result}\n` : "\n", - `The user provided the following feedback:\n${text}`, - ].join("\n"), - images, - ), + const parts: string[] = [ + `Command is still running in terminal from '${terminal.getCurrentWorkingDirectory().toPosix()}'.`, + result.length > 0 ? `Here's the output so far:\n${result}\n` : "\n", ] + if (text && text.trim().length > 0) { + parts.push(`The user provided the following feedback:\n${text}`) + } + + return [true, formatResponse.toolResult(parts.join("\n"), images)] } else if (completed || exitDetails) { let exitStatus: string = ""