From 0d013e35c4f4803f0037902c8dd23605c7d84d44 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 3 Dec 2025 21:41:09 -0700 Subject: [PATCH] refactor: unify user content tags to Replace all user content wrapper tags (, , , ) with a single unified tag and strip all accompanying explanatory blurbs. Changes: - processUserContentMentions.ts: Simplified shouldProcessMentions() to only detect - responses.ts: Updated toolDeniedWithFeedback(), toolApprovedWithFeedback(), tooManyMistakes() - Removed explanatory blurbs from XML output - Removed 'message' field from native protocol JSON - Task.ts: Changed initial task and continuation wrappers to use - AttemptCompletionTool.ts: Removed blurb, changed to - AskFollowupQuestionTool.ts: Changed to - Updated all related test files Breaking changes: - Old task history with , , or tags will no longer have mentions re-parsed - Models no longer receive explicit blurbs explaining the nature of user input --- .../processUserContentMentions.spec.ts | 66 +++++++------------ .../mentions/processUserContentMentions.ts | 6 +- src/core/prompts/responses.ts | 9 +-- src/core/task/Task.ts | 4 +- src/core/task/__tests__/Task.spec.ts | 14 ++-- .../task/__tests__/task-tool-history.spec.ts | 2 +- src/core/tools/AskFollowupQuestionTool.ts | 2 +- src/core/tools/AttemptCompletionTool.ts | 2 +- 8 files changed, 38 insertions(+), 67 deletions(-) diff --git a/src/core/mentions/__tests__/processUserContentMentions.spec.ts b/src/core/mentions/__tests__/processUserContentMentions.spec.ts index 13c225042d..c105d00f08 100644 --- a/src/core/mentions/__tests__/processUserContentMentions.spec.ts +++ b/src/core/mentions/__tests__/processUserContentMentions.spec.ts @@ -31,7 +31,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "Read file with limit", + text: "Read file with limit", }, ] @@ -45,7 +45,7 @@ describe("processUserContentMentions", () => { }) expect(parseMentions).toHaveBeenCalledWith( - "Read file with limit", + "Read file with limit", "/test", mockUrlContentFetcher, mockFileContextTracker, @@ -61,7 +61,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "Read file without limit", + text: "Read file without limit", }, ] @@ -74,7 +74,7 @@ describe("processUserContentMentions", () => { }) expect(parseMentions).toHaveBeenCalledWith( - "Read file without limit", + "Read file without limit", "/test", mockUrlContentFetcher, mockFileContextTracker, @@ -90,7 +90,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "Read unlimited lines", + text: "Read unlimited lines", }, ] @@ -104,7 +104,7 @@ describe("processUserContentMentions", () => { }) expect(parseMentions).toHaveBeenCalledWith( - "Read unlimited lines", + "Read unlimited lines", "/test", mockUrlContentFetcher, mockFileContextTracker, @@ -118,11 +118,11 @@ describe("processUserContentMentions", () => { }) describe("content processing", () => { - it("should process text blocks with tags", async () => { + it("should process text blocks with tags", async () => { const userContent = [ { type: "text" as const, - text: "Do something", + text: "Do something", }, ] @@ -136,33 +136,11 @@ describe("processUserContentMentions", () => { expect(parseMentions).toHaveBeenCalled() expect(result[0]).toEqual({ type: "text", - text: "parsed: Do something", + text: "parsed: Do something", }) }) - it("should process text blocks with tags", async () => { - const userContent = [ - { - type: "text" as const, - text: "Fix this issue", - }, - ] - - const result = await processUserContentMentions({ - userContent, - cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, - fileContextTracker: mockFileContextTracker, - }) - - expect(parseMentions).toHaveBeenCalled() - expect(result[0]).toEqual({ - type: "text", - text: "parsed: Fix this issue", - }) - }) - - it("should not process text blocks without task or feedback tags", async () => { + it("should not process text blocks without user_message tags", async () => { const userContent = [ { type: "text" as const, @@ -186,7 +164,7 @@ describe("processUserContentMentions", () => { { type: "tool_result" as const, tool_use_id: "123", - content: "Tool feedback", + content: "Tool feedback", }, ] @@ -201,7 +179,7 @@ describe("processUserContentMentions", () => { expect(result[0]).toEqual({ type: "tool_result", tool_use_id: "123", - content: "parsed: Tool feedback", + content: "parsed: Tool feedback", }) }) @@ -213,7 +191,7 @@ describe("processUserContentMentions", () => { content: [ { type: "text" as const, - text: "Array task", + text: "Array task", }, { type: "text" as const, @@ -237,7 +215,7 @@ describe("processUserContentMentions", () => { content: [ { type: "text", - text: "parsed: Array task", + text: "parsed: Array task", }, { type: "text", @@ -251,7 +229,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "First task", + text: "First task", }, { type: "image" as const, @@ -264,7 +242,7 @@ describe("processUserContentMentions", () => { { type: "tool_result" as const, tool_use_id: "456", - content: "Feedback", + content: "Feedback", }, ] @@ -280,13 +258,13 @@ describe("processUserContentMentions", () => { expect(result).toHaveLength(3) expect(result[0]).toEqual({ type: "text", - text: "parsed: First task", + text: "parsed: First task", }) expect(result[1]).toEqual(userContent[1]) // Image block unchanged expect(result[2]).toEqual({ type: "tool_result", tool_use_id: "456", - content: "parsed: Feedback", + content: "parsed: Feedback", }) }) }) @@ -296,7 +274,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "Test default", + text: "Test default", }, ] @@ -308,7 +286,7 @@ describe("processUserContentMentions", () => { }) expect(parseMentions).toHaveBeenCalledWith( - "Test default", + "Test default", "/test", mockUrlContentFetcher, mockFileContextTracker, @@ -324,7 +302,7 @@ describe("processUserContentMentions", () => { const userContent = [ { type: "text" as const, - text: "Test explicit false", + text: "Test explicit false", }, ] @@ -337,7 +315,7 @@ describe("processUserContentMentions", () => { }) expect(parseMentions).toHaveBeenCalledWith( - "Test explicit false", + "Test explicit false", "/test", mockUrlContentFetcher, mockFileContextTracker, diff --git a/src/core/mentions/processUserContentMentions.ts b/src/core/mentions/processUserContentMentions.ts index 4bdb422d48..8e12a536a0 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) => text.includes("") if (block.type === "text") { if (shouldProcessMentions(block.text)) { diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index ccb09e68e1..0696bf6f11 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -20,22 +20,20 @@ export const formatResponse = { if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { return JSON.stringify({ status: "denied", - message: "The user denied this operation and provided the following feedback", feedback: feedback, }) } - return `The user denied this operation and provided the following feedback:\n\n${feedback}\n` + return `\n${feedback}\n` }, toolApprovedWithFeedback: (feedback?: string, protocol?: ToolProtocol) => { if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { return JSON.stringify({ status: "approved", - message: "The user approved this operation and provided the following context", feedback: feedback, }) } - return `The user approved this operation and provided the following context:\n\n${feedback}\n` + return `\n${feedback}\n` }, toolError: (error?: string, protocol?: ToolProtocol) => { @@ -81,11 +79,10 @@ Otherwise, if you have not completed the task and do not need additional informa if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { return JSON.stringify({ status: "guidance", - message: "You seem to be having trouble proceeding", feedback: feedback, }) } - return `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n\n${feedback}\n` + return `\n${feedback}\n` }, missingToolParameterError: (paramName: string, protocol?: ToolProtocol) => { diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 3043b3c946..6e34f0a90d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1546,7 +1546,7 @@ export class Task extends EventEmitter implements TaskLike { await this.initiateTaskLoop([ { type: "text", - text: `\n${task}\n`, + text: `\n${task}\n`, }, ...imageBlocks, ]).catch((error) => { @@ -1801,7 +1801,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${responseText}\n`, }) } diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 4bae9c49d0..4c5599d7e5 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -881,7 +881,7 @@ describe("Cline", () => { }) describe("processUserContentMentions", () => { - it("should process mentions in task and feedback tags", async () => { + it("should process mentions in user_message tags", async () => { const [cline, task] = Task.create({ provider: mockProvider, apiConfiguration: mockApiConfig, @@ -895,7 +895,7 @@ describe("Cline", () => { } as const, { type: "text", - text: "Text with 'some/path' (see below for file content) in task tags", + text: "Text with 'some/path' (see below for file content) in user_message tags", } as const, { type: "tool_result", @@ -903,7 +903,7 @@ describe("Cline", () => { content: [ { type: "text", - text: "Check 'some/path' (see below for file content)", + text: "Check 'some/path' (see below for file content)", }, ], } as Anthropic.ToolResultBlockParam, @@ -931,18 +931,18 @@ describe("Cline", () => { "Regular text with 'some/path' (see below for file content)", ) - // Text within task tags should be processed + // Text within user_message tags should be processed expect((processedContent[1] as Anthropic.TextBlockParam).text).toContain("processed:") expect((processedContent[1] as Anthropic.TextBlockParam).text).toContain( - "Text with 'some/path' (see below for file content) in task tags", + "Text with 'some/path' (see below for file content) in user_message tags", ) - // Feedback tag content should be processed + // user_message tag content should be processed const toolResult1 = processedContent[2] as Anthropic.ToolResultBlockParam const content1 = Array.isArray(toolResult1.content) ? toolResult1.content[0] : toolResult1.content expect((content1 as Anthropic.TextBlockParam).text).toContain("processed:") expect((content1 as Anthropic.TextBlockParam).text).toContain( - "Check 'some/path' (see below for file content)", + "Check 'some/path' (see below for file content)", ) // Regular tool result should not be processed diff --git a/src/core/task/__tests__/task-tool-history.spec.ts b/src/core/task/__tests__/task-tool-history.spec.ts index 832e81c37b..fc7f2fd131 100644 --- a/src/core/task/__tests__/task-tool-history.spec.ts +++ b/src/core/task/__tests__/task-tool-history.spec.ts @@ -292,7 +292,7 @@ describe("Task Tool History Handling", () => { }, { type: "text" as const, - text: "Another message with tags", + text: "Another message with tags", }, { type: "tool_result" as const, diff --git a/src/core/tools/AskFollowupQuestionTool.ts b/src/core/tools/AskFollowupQuestionTool.ts index 16caf39992..8916edd36b 100644 --- a/src/core/tools/AskFollowupQuestionTool.ts +++ b/src/core/tools/AskFollowupQuestionTool.ts @@ -85,7 +85,7 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { task.consecutiveMistakeCount = 0 const { text, images } = await task.ask("followup", JSON.stringify(follow_up_json), false) await task.say("user_feedback", text ?? "", images) - pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) } catch (error) { await handleError("asking question", error as Error) } diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts index e21f3b7188..d9ce83694f 100644 --- a/src/core/tools/AttemptCompletionTool.ts +++ b/src/core/tools/AttemptCompletionTool.ts @@ -143,7 +143,7 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { // User provided feedback - push tool result to continue the conversation await task.say("user_feedback", text ?? "", images) - const feedbackText = `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n` + const feedbackText = `\n${text}\n` pushToolResult(formatResponse.toolResult(feedbackText, images)) } catch (error) { await handleError("inspecting site", error as Error)