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..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, @@ -29,28 +29,33 @@ 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) => - text.includes("") || - text.includes("") || - text.includes("") || - text.includes("") - 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, @@ -61,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/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..da3060c5b19c 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, ]) @@ -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}`, }) } 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..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\n${text}\n`, + 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 2c7ce0d023e2..59908d22bfbb 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -311,18 +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}\n`, - ].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 = "" 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. -