Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
efaaed9
feat: add Issue Fixer Orchestrator mode
MuriloFP Jul 3, 2025
57d3fbe
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 3, 2025
ef61905
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 4, 2025
f5a51c4
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 4, 2025
bcbf329
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 5, 2025
80413c0
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 5, 2025
ab10140
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 7, 2025
39c5cf7
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 7, 2025
00a0b63
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
080b61b
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
7a5ad14
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
2c73ff2
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 9, 2025
05ccf57
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 10, 2025
fdb1f35
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 11, 2025
10ce509
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 14, 2025
ab1f9fc
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 15, 2025
74fd8b4
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 15, 2025
6745c8f
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 16, 2025
faf2ee5
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
b2dadf9
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
f648e4c
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
a6d1e60
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 21, 2025
be90907
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 21, 2025
ed3a077
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 22, 2025
856313f
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 24, 2025
22d5789
fix: resolve focus and performance issues with file mentions in first…
MuriloFP Jul 24, 2025
2eab16d
fix: update test mocks to fix CI failures
MuriloFP Jul 24, 2025
688c493
fix: address PR feedback and fix failing tests
MuriloFP Jul 24, 2025
c59edca
refactor: extract synthetic message functionality to separate module
MuriloFP Jul 24, 2025
31a788e
fix: use multi-file read_file tool for synthetic messages
MuriloFP Jul 29, 2025
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
91 changes: 91 additions & 0 deletions src/core/mentions/__tests__/extractFileMentions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, it, expect } from "vitest"
import { extractFileMentions, hasFileMentions } from "../extractFileMentions"

describe("extractFileMentions", () => {
it("should extract single file mention", () => {
const text = "Please analyze @/src/main.ts and provide feedback"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(1)
expect(mentions[0]).toEqual({
mention: "@/src/main.ts",
path: "src/main.ts",
})
})

it("should extract multiple file mentions", () => {
const text = "Compare @/src/index.ts with @/src/utils.ts and @/tests/main.spec.ts"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(3)
expect(mentions[0].path).toBe("src/index.ts")
expect(mentions[1].path).toBe("src/utils.ts")
expect(mentions[2].path).toBe("tests/main.spec.ts")
})

it("should not extract folder mentions", () => {
const text = "Check the @/src/ folder and @/src/file.ts"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(1)
expect(mentions[0].path).toBe("src/file.ts")
})

it("should not extract non-file mentions", () => {
const text = "Check @problems and @terminal output"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(0)
})

it("should handle mentions with escaped spaces", () => {
const text = "Read @/path/to/file\\ with\\ spaces.txt"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(1)
expect(mentions[0].path).toBe("path/to/file\\ with\\ spaces.txt")
})

it("should return empty array for text without mentions", () => {
const text = "This is just regular text without any mentions"
const mentions = extractFileMentions(text)

expect(mentions).toHaveLength(0)
})
})

describe("hasFileMentions", () => {
it("should return true when content has file mentions", () => {
const content = [{ type: "text", text: "Check @/src/main.ts" }]

expect(hasFileMentions(content)).toBe(true)
})

it("should return false when content has no file mentions", () => {
const content = [{ type: "text", text: "Just regular text" }]

expect(hasFileMentions(content)).toBe(false)
})

it("should return false for non-file mentions", () => {
const content = [{ type: "text", text: "Check @problems and @terminal" }]

expect(hasFileMentions(content)).toBe(false)
})

it("should check multiple content blocks", () => {
const content = [
{ type: "text", text: "First block without mentions" },
{ type: "text", text: "Second block with @/src/file.ts" },
{ type: "image", text: undefined },
]

expect(hasFileMentions(content)).toBe(true)
})

it("should handle content blocks without text", () => {
const content = [{ type: "image" }, { type: "text", text: undefined }, { type: "text", text: "" }]

expect(hasFileMentions(content)).toBe(false)
})
})
49 changes: 49 additions & 0 deletions src/core/mentions/extractFileMentions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { mentionRegexGlobal } from "../../shared/context-mentions"

export interface FileMention {
mention: string
path: string
}

/**
* Extracts file mentions from text content.
* Only extracts mentions that start with "/" (file paths).
*
* @param text The text to extract mentions from
* @returns Array of file mentions found in the text
*/
export function extractFileMentions(text: string): FileMention[] {
const mentions: FileMention[] = []
const matches = text.matchAll(mentionRegexGlobal)

for (const match of matches) {
const mention = match[1]
if (mention.startsWith("/") && !mention.endsWith("/")) {
// This is a file mention (not a folder)
mentions.push({
mention: `@${mention}`,
path: mention.slice(1), // Remove leading slash
})
}
}

return mentions
}

/**
* Checks if the given content blocks contain any file mentions
*
* @param content The content blocks to check
* @returns true if any file mentions are found
*/
export function hasFileMentions(content: Array<{ type: string; text?: string }>): boolean {
for (const block of content) {
if (block.type === "text" && block.text) {
const mentions = extractFileMentions(block.text)
if (mentions.length > 0) {
return true
}
}
}
return false
}
4 changes: 1 addition & 3 deletions src/core/mentions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ export async function parseMentions(
return `'${mention}' (see below for site content)`
} else if (mention.startsWith("/")) {
const mentionPath = mention.slice(1)
return mentionPath.endsWith("/")
? `'${mentionPath}' (see below for folder content)`
: `'${mentionPath}' (see below for file content)`
return `'${mentionPath}'`
} else if (mention === "problems") {
return `Workspace Problems (see below for diagnostics)`
} else if (mention === "git-changes") {
Expand Down
21 changes: 20 additions & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-
import { MultiFileSearchReplaceDiffStrategy } from "../diff/strategies/multi-file-search-replace"
import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "../task-persistence"
import { getEnvironmentDetails } from "../environment/getEnvironmentDetails"
import {
shouldUseSyntheticMessages,
handleFirstMessageWithFileMentions,
createSyntheticReadFileMessage,
} from "./synthetic-messages"
import {
type CheckpointDiffOptions,
type CheckpointRestoreOptions,
Expand Down Expand Up @@ -329,7 +334,7 @@ export class Task extends EventEmitter<ClineEvents> {
return readApiMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath })
}

private async addToApiConversationHistory(message: Anthropic.MessageParam) {
public async addToApiConversationHistory(message: Anthropic.MessageParam) {
const messageWithTs = { ...message, ts: Date.now() }
this.apiConversationHistory.push(messageWithTs)
await this.saveApiConversationHistory()
Expand Down Expand Up @@ -1117,6 +1122,8 @@ export class Task extends EventEmitter<ClineEvents> {
})
}

// Helper methods for synthetic message generation

// Task Loop

private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise<void> {
Expand All @@ -1125,12 +1132,24 @@ export class Task extends EventEmitter<ClineEvents> {

let nextUserContent = userContent
let includeFileDetails = true
let isFirstMessage = true

this.emit("taskStarted")

while (!this.abort) {
// Check if this is the first message and contains file mentions
if (isFirstMessage && shouldUseSyntheticMessages(nextUserContent)) {
// Handle first message with file mentions using synthetic messages
await handleFirstMessageWithFileMentions(this, nextUserContent)
isFirstMessage = false
// The synthetic messages have been processed, continue with normal flow
// but skip the first iteration since we've already handled it
continue
}

const didEndLoop = await this.recursivelyMakeClineRequests(nextUserContent, includeFileDetails)
includeFileDetails = false // we only need file details the first time
isFirstMessage = false

// The way this agentic loop works is that cline will be given a
// task that he then calls tools to complete. Unless there's an
Expand Down
Loading