mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: improve multi-folder workspace support
- Fix Task to store initial workspace folder and use it consistently throughout task lifetime - Fix file mentions to use relative paths in multi-folder workspaces - Fix .roo folder detection to check if .roo is a workspace folder itself - Add support for workspace folder selection based on active editor - Add tests for multi-folder workspace functionality Fixes #6897 and #6720
This commit is contained in:
parent
f53fd39014
commit
27a05f8d16
6 changed files with 441 additions and 18 deletions
|
|
@ -96,8 +96,18 @@ export class CustomModesManager {
|
|||
return undefined
|
||||
}
|
||||
|
||||
const workspaceRoot = getWorkspacePath()
|
||||
const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME)
|
||||
// Check if .roo is a workspace folder itself
|
||||
const rooWorkspaceFolder = workspaceFolders.find((folder) => path.basename(folder.uri.fsPath) === ".roo")
|
||||
|
||||
let roomodesPath: string
|
||||
if (rooWorkspaceFolder) {
|
||||
// .roo is a workspace folder itself, look for .roomodes directly in it
|
||||
roomodesPath = path.join(rooWorkspaceFolder.uri.fsPath, ROOMODES_FILENAME.replace(".roo/", ""))
|
||||
} else {
|
||||
// Use the current workspace path (which considers the active file's workspace)
|
||||
const workspaceRoot = getWorkspacePath()
|
||||
roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME)
|
||||
}
|
||||
const exists = await fileExistsAtPath(roomodesPath)
|
||||
return exists ? roomodesPath : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,20 +274,41 @@ async function getFileOrFolderContent(
|
|||
maxReadFileLine?: number,
|
||||
): Promise<string> {
|
||||
const unescapedPath = unescapeSpaces(mentionPath)
|
||||
const absPath = path.resolve(cwd, unescapedPath)
|
||||
|
||||
// Check if this is an absolute path from a different workspace folder
|
||||
let absPath: string
|
||||
let displayPath: string = mentionPath
|
||||
|
||||
if (path.isAbsolute(unescapedPath)) {
|
||||
absPath = unescapedPath
|
||||
|
||||
// In multi-folder workspaces, convert absolute paths to relative paths
|
||||
// based on the workspace folder they belong to
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(absPath))
|
||||
if (workspaceFolder) {
|
||||
// Use relative path from the workspace folder
|
||||
const relativePath = path.relative(workspaceFolder.uri.fsPath, absPath)
|
||||
displayPath = path.join(workspaceFolder.name, relativePath).toPosix()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
absPath = path.resolve(cwd, unescapedPath)
|
||||
displayPath = mentionPath
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(absPath)
|
||||
|
||||
if (stats.isFile()) {
|
||||
if (rooIgnoreController && !rooIgnoreController.validateAccess(absPath)) {
|
||||
return `(File ${mentionPath} is ignored by .rooignore)`
|
||||
return `(File ${displayPath} is ignored by .rooignore)`
|
||||
}
|
||||
try {
|
||||
const content = await extractTextFromFile(absPath, maxReadFileLine)
|
||||
return content
|
||||
} catch (error) {
|
||||
return `(Failed to read contents of ${mentionPath}): ${error.message}`
|
||||
return `(Failed to read contents of ${displayPath}): ${error.message}`
|
||||
}
|
||||
} else if (stats.isDirectory()) {
|
||||
const entries = await fs.readdir(absPath, { withFileTypes: true })
|
||||
|
|
@ -315,8 +336,24 @@ async function getFileOrFolderContent(
|
|||
if (entry.isFile()) {
|
||||
folderContent += `${linePrefix}${displayName}\n`
|
||||
if (!isIgnored) {
|
||||
const filePath = path.join(mentionPath, entry.name)
|
||||
const absoluteFilePath = path.resolve(absPath, entry.name)
|
||||
|
||||
// Determine the display path for the file
|
||||
let fileDisplayPath: string
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(
|
||||
vscode.Uri.file(absoluteFilePath),
|
||||
)
|
||||
if (workspaceFolder) {
|
||||
const relativePath = path.relative(workspaceFolder.uri.fsPath, absoluteFilePath)
|
||||
fileDisplayPath = path.join(workspaceFolder.name, relativePath).toPosix()
|
||||
} else {
|
||||
fileDisplayPath = path.join(displayPath, entry.name).toPosix()
|
||||
}
|
||||
} else {
|
||||
fileDisplayPath = path.join(displayPath, entry.name).toPosix()
|
||||
}
|
||||
|
||||
fileContentPromises.push(
|
||||
(async () => {
|
||||
try {
|
||||
|
|
@ -325,7 +362,7 @@ async function getFileOrFolderContent(
|
|||
return undefined
|
||||
}
|
||||
const content = await extractTextFromFile(absoluteFilePath, maxReadFileLine)
|
||||
return `<file_content path="${filePath.toPosix()}">\n${content}\n</file_content>`
|
||||
return `<file_content path="${fileDisplayPath}">\n${content}\n</file_content>`
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -341,10 +378,10 @@ async function getFileOrFolderContent(
|
|||
const fileContents = (await Promise.all(fileContentPromises)).filter((content) => content)
|
||||
return `${folderContent}\n${fileContents.join("\n\n")}`.trim()
|
||||
} else {
|
||||
return `(Failed to read contents of ${mentionPath})`
|
||||
return `(Failed to read contents of ${displayPath})`
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to access path "${mentionPath}": ${error.message}`)
|
||||
throw new Error(`Failed to access path "${displayPath}": ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,10 +279,23 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
|
||||
|
||||
// Normal use-case is usually retry similar history task with new workspace.
|
||||
this.workspacePath = parentTask
|
||||
? parentTask.workspacePath
|
||||
: getWorkspacePath(path.join(os.homedir(), "Desktop"))
|
||||
// Store the initial workspace path when the task is created
|
||||
// In multi-folder workspaces, we want to maintain the same workspace context throughout the task
|
||||
if (parentTask) {
|
||||
// Inherit workspace from parent task
|
||||
this.workspacePath = parentTask.workspacePath
|
||||
} else {
|
||||
// Determine workspace based on current context
|
||||
// If there's an active editor, use its workspace folder
|
||||
// Otherwise, use the first workspace folder or fallback to Desktop
|
||||
const currentFileUri = vscode.window.activeTextEditor?.document.uri
|
||||
if (currentFileUri) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(currentFileUri)
|
||||
this.workspacePath = workspaceFolder?.uri.fsPath || getWorkspacePath(path.join(os.homedir(), "Desktop"))
|
||||
} else {
|
||||
this.workspacePath = getWorkspacePath(path.join(os.homedir(), "Desktop"))
|
||||
}
|
||||
}
|
||||
|
||||
this.instanceId = crypto.randomUUID().slice(0, 8)
|
||||
this.taskNumber = -1
|
||||
|
|
|
|||
302
src/core/task/__tests__/multi-folder-workspace.spec.ts
Normal file
302
src/core/task/__tests__/multi-folder-workspace.spec.ts
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import { Task } from "../Task"
|
||||
import { ClineProvider } from "../../webview/ClineProvider"
|
||||
|
||||
// Mock vscode module
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
workspaceFolders: [],
|
||||
getWorkspaceFolder: vi.fn(),
|
||||
createFileSystemWatcher: vi.fn(() => ({
|
||||
onDidChange: vi.fn(),
|
||||
onDidCreate: vi.fn(),
|
||||
onDidDelete: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
},
|
||||
window: {
|
||||
activeTextEditor: undefined,
|
||||
showErrorMessage: vi.fn(),
|
||||
createTextEditorDecorationType: vi.fn(() => ({
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
},
|
||||
Uri: {
|
||||
file: vi.fn((path) => ({ fsPath: path })),
|
||||
},
|
||||
RelativePattern: vi.fn(),
|
||||
DiagnosticSeverity: {
|
||||
Error: 0,
|
||||
Warning: 1,
|
||||
Information: 2,
|
||||
Hint: 3,
|
||||
},
|
||||
languages: {
|
||||
getDiagnostics: vi.fn(() => []),
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock other dependencies
|
||||
vi.mock("../../webview/ClineProvider")
|
||||
vi.mock("../../../utils/path", () => ({
|
||||
getWorkspacePath: vi.fn(() => "/default/workspace"),
|
||||
}))
|
||||
vi.mock("../../ignore/RooIgnoreController")
|
||||
vi.mock("../../protect/RooProtectedController")
|
||||
vi.mock("../../context-tracking/FileContextTracker")
|
||||
vi.mock("../../../integrations/editor/DiffViewProvider")
|
||||
vi.mock("../../../integrations/editor/DecorationController")
|
||||
vi.mock("../../../services/browser/UrlContentFetcher")
|
||||
vi.mock("../../../integrations/terminal/TerminalRegistry")
|
||||
vi.mock("../../../utils/fs", () => ({
|
||||
fileExistsAtPath: vi.fn(() => Promise.resolve(false)),
|
||||
}))
|
||||
vi.mock("../../../api", () => ({
|
||||
buildApiHandler: vi.fn(() => ({
|
||||
getModel: vi.fn(() => ({ info: {} })),
|
||||
})),
|
||||
}))
|
||||
vi.mock("../../tools/ToolRepetitionDetector", () => ({
|
||||
ToolRepetitionDetector: vi.fn().mockImplementation(() => ({})),
|
||||
}))
|
||||
vi.mock("../../tools/AutoApprovalHandler", () => ({
|
||||
AutoApprovalHandler: vi.fn().mockImplementation(() => ({})),
|
||||
}))
|
||||
|
||||
describe("Multi-folder Workspace Support", () => {
|
||||
let mockProvider: ClineProvider
|
||||
|
||||
beforeEach(() => {
|
||||
mockProvider = {
|
||||
context: {
|
||||
globalStorageUri: { fsPath: "/global/storage" },
|
||||
},
|
||||
} as any
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe("Task workspace path initialization", () => {
|
||||
it("should use the active editor's workspace folder in multi-folder workspace", () => {
|
||||
// Setup multi-folder workspace
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Set active editor in backend folder
|
||||
const activeFileUri = { fsPath: "/workspace/backend/src/index.ts" }
|
||||
;(vscode.window as any).activeTextEditor = {
|
||||
document: { uri: activeFileUri },
|
||||
}
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockReturnValue(workspaceFolders[1])
|
||||
|
||||
// Create task
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: {} as any,
|
||||
task: "Test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// Should use backend workspace folder
|
||||
expect(task.workspacePath).toBe("/workspace/backend")
|
||||
})
|
||||
|
||||
it("should maintain consistent workspace path throughout task lifetime", () => {
|
||||
// Setup multi-folder workspace
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Set active editor in frontend folder
|
||||
const activeFileUri = { fsPath: "/workspace/frontend/src/App.tsx" }
|
||||
;(vscode.window as any).activeTextEditor = {
|
||||
document: { uri: activeFileUri },
|
||||
}
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockReturnValue(workspaceFolders[0])
|
||||
|
||||
// Create task
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: {} as any,
|
||||
task: "Test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
const initialWorkspacePath = task.workspacePath
|
||||
expect(initialWorkspacePath).toBe("/workspace/frontend")
|
||||
|
||||
// Change active editor to backend folder
|
||||
;(vscode.window as any).activeTextEditor = {
|
||||
document: { uri: { fsPath: "/workspace/backend/src/index.ts" } },
|
||||
}
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockReturnValue(workspaceFolders[1])
|
||||
|
||||
// Workspace path should remain the same
|
||||
expect(task.workspacePath).toBe(initialWorkspacePath)
|
||||
expect(task.cwd).toBe(initialWorkspacePath)
|
||||
})
|
||||
|
||||
it("should inherit workspace path from parent task", () => {
|
||||
const parentTask = {
|
||||
workspacePath: "/workspace/parent",
|
||||
} as any
|
||||
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: {} as any,
|
||||
task: "Child task",
|
||||
parentTask,
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
expect(task.workspacePath).toBe("/workspace/parent")
|
||||
})
|
||||
|
||||
it("should fallback to first workspace folder when no active editor", () => {
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
;(vscode.window as any).activeTextEditor = undefined
|
||||
|
||||
// Mock getWorkspacePath to return first folder
|
||||
const { getWorkspacePath } = require("../../../utils/path")
|
||||
getWorkspacePath.mockReturnValue("/workspace/frontend")
|
||||
|
||||
const task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: {} as any,
|
||||
task: "Test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
expect(task.workspacePath).toBe("/workspace/frontend")
|
||||
})
|
||||
})
|
||||
|
||||
describe(".roo folder detection", () => {
|
||||
it("should detect .roo folder as a workspace folder", () => {
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/src" }, name: "src" },
|
||||
{ uri: { fsPath: "/workspace/.roo" }, name: ".roo" },
|
||||
{ uri: { fsPath: "/workspace/docs" }, name: "docs" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Check if .roo is detected as a workspace folder
|
||||
const rooWorkspaceFolder = workspaceFolders.find((folder) => path.basename(folder.uri.fsPath) === ".roo")
|
||||
|
||||
expect(rooWorkspaceFolder).toBeDefined()
|
||||
expect(rooWorkspaceFolder?.uri.fsPath).toBe("/workspace/.roo")
|
||||
})
|
||||
|
||||
it("should use .roo subfolder in active workspace folder", () => {
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Set active editor in backend folder
|
||||
const activeFileUri = { fsPath: "/workspace/backend/src/index.ts" }
|
||||
;(vscode.window as any).activeTextEditor = {
|
||||
document: { uri: activeFileUri },
|
||||
}
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockReturnValue(workspaceFolders[1])
|
||||
|
||||
// Determine .roo folder path
|
||||
const targetWorkspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(activeFileUri.fsPath))
|
||||
const rooPath = path.join(targetWorkspaceFolder!.uri.fsPath, ".roo")
|
||||
|
||||
expect(rooPath).toBe("/workspace/backend/.roo")
|
||||
})
|
||||
})
|
||||
|
||||
describe("File mention paths in multi-folder workspace", () => {
|
||||
it("should convert absolute paths to workspace-relative paths", () => {
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Mock getWorkspaceFolder to return the correct folder
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockImplementation((uri: any) => {
|
||||
const filePath = uri.fsPath
|
||||
if (filePath.startsWith("/workspace/frontend")) {
|
||||
return workspaceFolders[0]
|
||||
} else if (filePath.startsWith("/workspace/backend")) {
|
||||
return workspaceFolders[1]
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
// Test absolute path from backend folder
|
||||
const absolutePath = "/workspace/backend/src/api/server.ts"
|
||||
const fileUri = vscode.Uri.file(absolutePath)
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri)
|
||||
|
||||
expect(workspaceFolder).toBeDefined()
|
||||
expect(workspaceFolder?.name).toBe("backend")
|
||||
|
||||
// Convert to relative path
|
||||
const relativePath = path.relative(workspaceFolder!.uri.fsPath, absolutePath)
|
||||
const displayPath = path.join(workspaceFolder!.name, relativePath)
|
||||
|
||||
expect(displayPath).toBe("backend/src/api/server.ts")
|
||||
})
|
||||
|
||||
it("should handle file mentions across different workspace folders", () => {
|
||||
const workspaceFolders = [
|
||||
{ uri: { fsPath: "/workspace/frontend" }, name: "frontend" },
|
||||
{ uri: { fsPath: "/workspace/backend" }, name: "backend" },
|
||||
]
|
||||
;(vscode.workspace as any).workspaceFolders = workspaceFolders
|
||||
|
||||
// Mock getWorkspaceFolder
|
||||
;(vscode.workspace.getWorkspaceFolder as any).mockImplementation((uri: any) => {
|
||||
const filePath = uri.fsPath
|
||||
if (filePath.startsWith("/workspace/frontend")) {
|
||||
return workspaceFolders[0]
|
||||
} else if (filePath.startsWith("/workspace/backend")) {
|
||||
return workspaceFolders[1]
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
// Test files from different folders
|
||||
const frontendFile = "/workspace/frontend/src/App.tsx"
|
||||
const backendFile = "/workspace/backend/src/server.ts"
|
||||
|
||||
// Get workspace folders for each file
|
||||
const frontendFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(frontendFile))
|
||||
const backendFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(backendFile))
|
||||
|
||||
// Convert to display paths
|
||||
const frontendDisplayPath = path.join(
|
||||
frontendFolder!.name,
|
||||
path.relative(frontendFolder!.uri.fsPath, frontendFile),
|
||||
)
|
||||
const backendDisplayPath = path.join(
|
||||
backendFolder!.name,
|
||||
path.relative(backendFolder!.uri.fsPath, backendFile),
|
||||
)
|
||||
|
||||
expect(frontendDisplayPath).toBe("frontend/src/App.tsx")
|
||||
expect(backendDisplayPath).toBe("backend/src/server.ts")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -800,9 +800,33 @@ export const webviewMessageHandler = async (
|
|||
return
|
||||
}
|
||||
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders[0]
|
||||
const rooDir = path.join(workspaceFolder.uri.fsPath, ".roo")
|
||||
const mcpPath = path.join(rooDir, "mcp.json")
|
||||
// Check if .roo is a workspace folder itself
|
||||
const rooWorkspaceFolder = vscode.workspace.workspaceFolders.find(
|
||||
(folder) => path.basename(folder.uri.fsPath) === ".roo",
|
||||
)
|
||||
|
||||
let rooDir: string
|
||||
let mcpPath: string
|
||||
|
||||
if (rooWorkspaceFolder) {
|
||||
// .roo is a workspace folder itself
|
||||
rooDir = rooWorkspaceFolder.uri.fsPath
|
||||
mcpPath = path.join(rooDir, "mcp.json")
|
||||
} else {
|
||||
// Use the workspace folder of the active file, or fall back to first workspace folder
|
||||
const activeFileUri = vscode.window.activeTextEditor?.document.uri
|
||||
let targetWorkspaceFolder = vscode.workspace.workspaceFolders[0]
|
||||
|
||||
if (activeFileUri) {
|
||||
const activeWorkspaceFolder = vscode.workspace.getWorkspaceFolder(activeFileUri)
|
||||
if (activeWorkspaceFolder) {
|
||||
targetWorkspaceFolder = activeWorkspaceFolder
|
||||
}
|
||||
}
|
||||
|
||||
rooDir = path.join(targetWorkspaceFolder.uri.fsPath, ".roo")
|
||||
mcpPath = path.join(rooDir, "mcp.json")
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.mkdir(rooDir, { recursive: true })
|
||||
|
|
|
|||
|
|
@ -347,6 +347,20 @@ export class McpHub {
|
|||
return
|
||||
}
|
||||
|
||||
// Watch for mcp.json in all workspace folders
|
||||
for (const workspaceFolder of vscode.workspace.workspaceFolders) {
|
||||
// Check if .roo is a workspace folder itself
|
||||
if (path.basename(workspaceFolder.uri.fsPath) === ".roo") {
|
||||
const projectMcpPattern = new vscode.RelativePattern(workspaceFolder, "mcp.json")
|
||||
this.projectMcpWatcher = vscode.workspace.createFileSystemWatcher(projectMcpPattern)
|
||||
} else {
|
||||
// Watch for .roo/mcp.json in regular workspace folders
|
||||
const projectMcpPattern = new vscode.RelativePattern(workspaceFolder, ".roo/mcp.json")
|
||||
this.projectMcpWatcher = vscode.workspace.createFileSystemWatcher(projectMcpPattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the first watcher if multiple were created (for simplicity)
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders[0]
|
||||
const projectMcpPattern = new vscode.RelativePattern(workspaceFolder, ".roo/mcp.json")
|
||||
|
||||
|
|
@ -553,8 +567,31 @@ export class McpHub {
|
|||
return null
|
||||
}
|
||||
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders[0]
|
||||
const projectMcpDir = path.join(workspaceFolder.uri.fsPath, ".roo")
|
||||
// Check if there's a .roo folder in any workspace folder
|
||||
// First, check if there's a .roo folder that is itself a workspace folder
|
||||
const rooWorkspaceFolder = vscode.workspace.workspaceFolders.find(
|
||||
(folder) => path.basename(folder.uri.fsPath) === ".roo",
|
||||
)
|
||||
|
||||
if (rooWorkspaceFolder) {
|
||||
// .roo is a workspace folder itself
|
||||
const projectMcpPath = path.join(rooWorkspaceFolder.uri.fsPath, "mcp.json")
|
||||
return projectMcpPath
|
||||
}
|
||||
|
||||
// Otherwise, look for .roo subfolder in workspace folders
|
||||
// Prioritize the workspace folder containing the active file
|
||||
const activeFileUri = vscode.window.activeTextEditor?.document.uri
|
||||
let targetWorkspaceFolder = vscode.workspace.workspaceFolders[0]
|
||||
|
||||
if (activeFileUri) {
|
||||
const activeWorkspaceFolder = vscode.workspace.getWorkspaceFolder(activeFileUri)
|
||||
if (activeWorkspaceFolder) {
|
||||
targetWorkspaceFolder = activeWorkspaceFolder
|
||||
}
|
||||
}
|
||||
|
||||
const projectMcpDir = path.join(targetWorkspaceFolder.uri.fsPath, ".roo")
|
||||
const projectMcpPath = path.join(projectMcpDir, "mcp.json")
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue