Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 22 additions & 44 deletions src/core/mentions/__tests__/processUserContentMentions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>Read file with limit</task>",
text: "<user_message>Read file with limit</user_message>",
},
]

Expand All @@ -45,7 +45,7 @@ describe("processUserContentMentions", () => {
})

expect(parseMentions).toHaveBeenCalledWith(
"<task>Read file with limit</task>",
"<user_message>Read file with limit</user_message>",
"/test",
mockUrlContentFetcher,
mockFileContextTracker,
Expand All @@ -61,7 +61,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>Read file without limit</task>",
text: "<user_message>Read file without limit</user_message>",
},
]

Expand All @@ -74,7 +74,7 @@ describe("processUserContentMentions", () => {
})

expect(parseMentions).toHaveBeenCalledWith(
"<task>Read file without limit</task>",
"<user_message>Read file without limit</user_message>",
"/test",
mockUrlContentFetcher,
mockFileContextTracker,
Expand All @@ -90,7 +90,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>Read unlimited lines</task>",
text: "<user_message>Read unlimited lines</user_message>",
},
]

Expand All @@ -104,7 +104,7 @@ describe("processUserContentMentions", () => {
})

expect(parseMentions).toHaveBeenCalledWith(
"<task>Read unlimited lines</task>",
"<user_message>Read unlimited lines</user_message>",
"/test",
mockUrlContentFetcher,
mockFileContextTracker,
Expand All @@ -118,11 +118,11 @@ describe("processUserContentMentions", () => {
})

describe("content processing", () => {
it("should process text blocks with <task> tags", async () => {
it("should process text blocks with <user_message> tags", async () => {
const userContent = [
{
type: "text" as const,
text: "<task>Do something</task>",
text: "<user_message>Do something</user_message>",
},
]

Expand All @@ -136,33 +136,11 @@ describe("processUserContentMentions", () => {
expect(parseMentions).toHaveBeenCalled()
expect(result[0]).toEqual({
type: "text",
text: "parsed: <task>Do something</task>",
text: "parsed: <user_message>Do something</user_message>",
})
})

it("should process text blocks with <feedback> tags", async () => {
const userContent = [
{
type: "text" as const,
text: "<feedback>Fix this issue</feedback>",
},
]

const result = await processUserContentMentions({
userContent,
cwd: "/test",
urlContentFetcher: mockUrlContentFetcher,
fileContextTracker: mockFileContextTracker,
})

expect(parseMentions).toHaveBeenCalled()
expect(result[0]).toEqual({
type: "text",
text: "parsed: <feedback>Fix this issue</feedback>",
})
})

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,
Expand All @@ -186,7 +164,7 @@ describe("processUserContentMentions", () => {
{
type: "tool_result" as const,
tool_use_id: "123",
content: "<feedback>Tool feedback</feedback>",
content: "<user_message>Tool feedback</user_message>",
},
]

Expand All @@ -201,7 +179,7 @@ describe("processUserContentMentions", () => {
expect(result[0]).toEqual({
type: "tool_result",
tool_use_id: "123",
content: "parsed: <feedback>Tool feedback</feedback>",
content: "parsed: <user_message>Tool feedback</user_message>",
})
})

Expand All @@ -213,7 +191,7 @@ describe("processUserContentMentions", () => {
content: [
{
type: "text" as const,
text: "<task>Array task</task>",
text: "<user_message>Array task</user_message>",
},
{
type: "text" as const,
Expand All @@ -237,7 +215,7 @@ describe("processUserContentMentions", () => {
content: [
{
type: "text",
text: "parsed: <task>Array task</task>",
text: "parsed: <user_message>Array task</user_message>",
},
{
type: "text",
Expand All @@ -251,7 +229,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>First task</task>",
text: "<user_message>First task</user_message>",
},
{
type: "image" as const,
Expand All @@ -264,7 +242,7 @@ describe("processUserContentMentions", () => {
{
type: "tool_result" as const,
tool_use_id: "456",
content: "<feedback>Feedback</feedback>",
content: "<user_message>Feedback</user_message>",
},
]

Expand All @@ -280,13 +258,13 @@ describe("processUserContentMentions", () => {
expect(result).toHaveLength(3)
expect(result[0]).toEqual({
type: "text",
text: "parsed: <task>First task</task>",
text: "parsed: <user_message>First task</user_message>",
})
expect(result[1]).toEqual(userContent[1]) // Image block unchanged
expect(result[2]).toEqual({
type: "tool_result",
tool_use_id: "456",
content: "parsed: <feedback>Feedback</feedback>",
content: "parsed: <user_message>Feedback</user_message>",
})
})
})
Expand All @@ -296,7 +274,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>Test default</task>",
text: "<user_message>Test default</user_message>",
},
]

Expand All @@ -308,7 +286,7 @@ describe("processUserContentMentions", () => {
})

expect(parseMentions).toHaveBeenCalledWith(
"<task>Test default</task>",
"<user_message>Test default</user_message>",
"/test",
mockUrlContentFetcher,
mockFileContextTracker,
Expand All @@ -324,7 +302,7 @@ describe("processUserContentMentions", () => {
const userContent = [
{
type: "text" as const,
text: "<task>Test explicit false</task>",
text: "<user_message>Test explicit false</user_message>",
},
]

Expand All @@ -337,7 +315,7 @@ describe("processUserContentMentions", () => {
})

expect(parseMentions).toHaveBeenCalledWith(
"<task>Test explicit false</task>",
"<user_message>Test explicit false</user_message>",
"/test",
mockUrlContentFetcher,
mockFileContextTracker,
Expand Down
15 changes: 4 additions & 11 deletions src/core/mentions/processUserContentMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,13 @@ 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)
// 1. All TextBlockParam's text (first user message)
// 2. ToolResultBlockParam's content/context text arrays if it contains
// "<feedback>" (see formatToolDeniedFeedback, attemptCompletion,
// executeCommand, and consecutiveMistakeCount >= 3) or "<answer>"
// (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).
// "<user_message>" - we place all user generated content in this tag
// so it can effectively be used as a marker for when we should parse mentions.
return Promise.all(
userContent.map(async (block) => {
const shouldProcessMentions = (text: string) =>
text.includes("<task>") ||
text.includes("<feedback>") ||
text.includes("<answer>") ||
text.includes("<user_message>")
const shouldProcessMentions = (text: string) => text.includes("<user_message>")

if (block.type === "text") {
if (shouldProcessMentions(block.text)) {
Expand Down
9 changes: 3 additions & 6 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<feedback>\n${feedback}\n</feedback>`
return `The user denied this operation and responded with the message:\n<user_message>\n${feedback}\n</user_message>`
},

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<feedback>\n${feedback}\n</feedback>`
return `The user approved this operation and responded with the message:\n<user_message>\n${feedback}\n</user_message>`
},

toolError: (error?: string, protocol?: ToolProtocol) => {
Expand Down Expand Up @@ -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<feedback>\n${feedback}\n</feedback>`
return `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n<user_message>\n${feedback}\n</user_message>`
},

missingToolParameterError: (paramName: string, protocol?: ToolProtocol) => {
Expand Down
4 changes: 2 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1546,7 +1546,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
await this.initiateTaskLoop([
{
type: "text",
text: `<task>\n${task}\n</task>`,
text: `<user_message>\n${task}\n</user_message>`,
},
...imageBlocks,
]).catch((error) => {
Expand Down Expand Up @@ -1801,7 +1801,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
if (responseText) {
newUserContent.push({
type: "text",
text: `\n\nNew instructions for task continuation:\n<user_message>\n${responseText}\n</user_message>`,
text: `<user_message>\n${responseText}\n</user_message>`,
})
}

Expand Down
14 changes: 7 additions & 7 deletions src/core/task/__tests__/Task.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -895,15 +895,15 @@ describe("Cline", () => {
} as const,
{
type: "text",
text: "<task>Text with 'some/path' (see below for file content) in task tags</task>",
text: "<user_message>Text with 'some/path' (see below for file content) in user_message tags</user_message>",
} as const,
{
type: "tool_result",
tool_use_id: "test-id",
content: [
{
type: "text",
text: "<feedback>Check 'some/path' (see below for file content)</feedback>",
text: "<user_message>Check 'some/path' (see below for file content)</user_message>",
},
],
} as Anthropic.ToolResultBlockParam,
Expand Down Expand Up @@ -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(
"<task>Text with 'some/path' (see below for file content) in task tags</task>",
"<user_message>Text with 'some/path' (see below for file content) in user_message tags</user_message>",
)

// 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(
"<feedback>Check 'some/path' (see below for file content)</feedback>",
"<user_message>Check 'some/path' (see below for file content)</user_message>",
)

// Regular tool result should not be processed
Expand Down
2 changes: 1 addition & 1 deletion src/core/task/__tests__/task-tool-history.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ describe("Task Tool History Handling", () => {
},
{
type: "text" as const,
text: "Another message with <task> tags",
text: "Another message with <user_message> tags",
},
{
type: "tool_result" as const,
Expand Down
2 changes: 1 addition & 1 deletion src/core/tools/AskFollowupQuestionTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<answer>\n${text}\n</answer>`, images))
pushToolResult(formatResponse.toolResult(`<user_message>\n${text}\n</user_message>`, images))
} catch (error) {
await handleError("asking question", error as Error)
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/tools/AttemptCompletionTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<feedback>\n${text}\n</feedback>`
const feedbackText = `<user_message>\n${text}\n</user_message>`
pushToolResult(formatResponse.toolResult(feedbackText, images))
} catch (error) {
await handleError("inspecting site", error as Error)
Expand Down
3 changes: 1 addition & 2 deletions src/core/tools/ExecuteCommandTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,7 @@ export async function executeCommandInTerminal(
[
`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:`,
`<feedback>\n${text}\n</feedback>`,
`<user_message>\n${text}\n</user_message>`,
].join("\n"),
images,
),
Expand Down
4 changes: 2 additions & 2 deletions src/core/tools/__tests__/readFileTool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ vi.mock("../../prompts/responses", () => ({
toolDenied: vi.fn(() => "The user denied this operation."),
toolDeniedWithFeedback: vi.fn(
(feedback?: string) =>
`The user denied this operation and provided the following feedback:\n<feedback>\n${feedback}\n</feedback>`,
`The user denied this operation and responded with the message:\n<user_message>\n${feedback}\n</user_message>`,
),
toolApprovedWithFeedback: vi.fn(
(feedback?: string) =>
`The user approved this operation and provided the following context:\n<feedback>\n${feedback}\n</feedback>`,
`The user approved this operation and responded with the message:\n<user_message>\n${feedback}\n</user_message>`,
),
rooIgnoreError: vi.fn(
(path: string) =>
Expand Down
Loading