diff --git a/src/core/tools/__tests__/runSlashCommandTool.spec.ts b/src/core/tools/__tests__/runSlashCommandTool.spec.ts
index 07143e96cc1..a6d591e5cb5 100644
--- a/src/core/tools/__tests__/runSlashCommandTool.spec.ts
+++ b/src/core/tools/__tests__/runSlashCommandTool.spec.ts
@@ -3,6 +3,7 @@ import { runSlashCommandTool } from "../runSlashCommandTool"
import { Task } from "../../task/Task"
import { formatResponse } from "../../prompts/responses"
import { getCommand, getCommandNames } from "../../../services/command/commands"
+import { parseMentions } from "../../mentions"
// Mock dependencies
vi.mock("../../../services/command/commands", () => ({
@@ -10,6 +11,14 @@ vi.mock("../../../services/command/commands", () => ({
getCommandNames: vi.fn(),
}))
+vi.mock("../../mentions", () => ({
+ parseMentions: vi.fn(),
+}))
+
+vi.mock("../../../services/browser/UrlContentFetcher", () => ({
+ UrlContentFetcher: vi.fn().mockImplementation(() => ({})),
+}))
+
describe("runSlashCommandTool", () => {
let mockTask: any
let mockAskApproval: any
@@ -20,6 +29,9 @@ describe("runSlashCommandTool", () => {
beforeEach(() => {
vi.clearAllMocks()
+ // By default, mock parseMentions to return the original content unchanged
+ vi.mocked(parseMentions).mockImplementation((content) => Promise.resolve(content))
+
mockTask = {
consecutiveMistakeCount: 0,
recordToolError: vi.fn(),
@@ -377,4 +389,147 @@ Deploy application to production`,
expect(mockTask.consecutiveMistakeCount).toBe(0)
})
+
+ it("should process mentions in command content", async () => {
+ const mockCommand = {
+ name: "test",
+ content: "Check @/README.md for details",
+ source: "project" as const,
+ filePath: ".roo/commands/test.md",
+ description: "Test command with file reference",
+ }
+
+ vi.mocked(getCommand).mockResolvedValue(mockCommand)
+ vi.mocked(parseMentions).mockResolvedValue(
+ "Check 'README.md' (see below for file content)\n\n\n# README\nTest content\n",
+ )
+
+ mockAskApproval.mockResolvedValue(true)
+
+ const block = {
+ type: "tool_use" as const,
+ name: "run_slash_command" as const,
+ params: {
+ command: "test",
+ },
+ partial: false,
+ }
+
+ await runSlashCommandTool(
+ mockTask as Task,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify parseMentions was called with the command content
+ expect(vi.mocked(parseMentions)).toHaveBeenCalledWith(
+ "Check @/README.md for details",
+ "/test/project",
+ expect.any(Object), // UrlContentFetcher instance
+ undefined,
+ undefined,
+ false,
+ true,
+ 50,
+ undefined,
+ )
+
+ // Verify the processed content is included in the result
+ expect(mockPushToolResult).toHaveBeenCalledWith(
+ expect.stringContaining("Check 'README.md' (see below for file content)"),
+ )
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining(''))
+ })
+
+ it("should handle mention processing errors gracefully", async () => {
+ const mockCommand = {
+ name: "test",
+ content: "Check @/README.md for details",
+ source: "project" as const,
+ filePath: ".roo/commands/test.md",
+ description: "Test command with file reference",
+ }
+
+ vi.mocked(getCommand).mockResolvedValue(mockCommand)
+ vi.mocked(parseMentions).mockRejectedValue(new Error("Failed to process mentions"))
+
+ mockAskApproval.mockResolvedValue(true)
+
+ const block = {
+ type: "tool_use" as const,
+ name: "run_slash_command" as const,
+ params: {
+ command: "test",
+ },
+ partial: false,
+ }
+
+ // Mock console.warn to verify it's called
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
+
+ await runSlashCommandTool(
+ mockTask as Task,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should log a warning when mention processing fails
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ expect.stringContaining("Failed to process mentions in slash command content:"),
+ )
+
+ // Should still return the original content when mention processing fails
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Check @/README.md for details"))
+
+ consoleWarnSpy.mockRestore()
+ })
+
+ it("should process multiple file references in command content", async () => {
+ const mockCommand = {
+ name: "docs",
+ content: "Review @/README.md and @/CONTRIBUTING.md for guidelines",
+ source: "project" as const,
+ filePath: ".roo/commands/docs.md",
+ description: "Documentation command",
+ }
+
+ vi.mocked(getCommand).mockResolvedValue(mockCommand)
+ vi.mocked(parseMentions).mockResolvedValue(
+ "Review 'README.md' (see below for file content) and 'CONTRIBUTING.md' (see below for file content)\n\n" +
+ '\n# README\n\n\n' +
+ '\n# Contributing\n',
+ )
+
+ mockAskApproval.mockResolvedValue(true)
+
+ const block = {
+ type: "tool_use" as const,
+ name: "run_slash_command" as const,
+ params: {
+ command: "docs",
+ },
+ partial: false,
+ }
+
+ await runSlashCommandTool(
+ mockTask as Task,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify both files are included in the processed content
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining(''))
+ expect(mockPushToolResult).toHaveBeenCalledWith(
+ expect.stringContaining(''),
+ )
+ })
})
diff --git a/src/core/tools/runSlashCommandTool.ts b/src/core/tools/runSlashCommandTool.ts
index 06ceb5f19ce..72314add30b 100644
--- a/src/core/tools/runSlashCommandTool.ts
+++ b/src/core/tools/runSlashCommandTool.ts
@@ -3,6 +3,8 @@ import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } f
import { formatResponse } from "../prompts/responses"
import { getCommand, getCommandNames } from "../../services/command/commands"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
+import { parseMentions } from "../mentions"
+import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
export async function runSlashCommandTool(
task: Task,
@@ -78,6 +80,29 @@ export async function runSlashCommandTool(
return
}
+ // Process mentions in the command content
+ const provider = task.providerRef.deref()
+ const urlContentFetcher = new UrlContentFetcher(provider!.context)
+ let processedContent = command.content
+
+ try {
+ // Process @/file references and other mentions in the command content
+ processedContent = await parseMentions(
+ command.content,
+ task.cwd,
+ urlContentFetcher,
+ undefined, // fileContextTracker - not needed for slash commands
+ undefined, // rooIgnoreController - will use default
+ false, // showRooIgnoredFiles
+ true, // includeDiagnosticMessages
+ 50, // maxDiagnosticMessages
+ undefined, // maxReadFileLine
+ )
+ } catch (error) {
+ // If mention processing fails, log the error but continue with original content
+ console.warn(`Failed to process mentions in slash command content: ${error}`)
+ }
+
// Build the result message
let result = `Command: /${commandName}`
@@ -94,7 +119,7 @@ export async function runSlashCommandTool(
}
result += `\nSource: ${command.source}`
- result += `\n\n--- Command Content ---\n\n${command.content}`
+ result += `\n\n--- Command Content ---\n\n${processedContent}`
// Return the command content as the tool result
pushToolResult(result)