fix: identify mcp and slash command config path in multiple folder workspace (#6904)

This commit is contained in:
NaccOll 2025-09-06 02:52:48 +08:00 • committed by GitHub
parent 0510c03684
commit 1e403a7c92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 50 additions and 38 deletions

View file

@ -95,7 +95,7 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
TelemetryService.instance.captureTitleButtonClicked("plus")
await visibleProvider.removeClineFromStack()
await visibleProvider.postStateToWebview()
await visibleProvider.refreshWorkspace()
await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
// Send focusInput action immediately after chatButtonClicked
// This ensures the focus happens after the view has switched

View file

@ -7,7 +7,6 @@ import { isBinaryFile } from "isbinaryfile"
import { mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "../../shared/context-mentions"
import { getCommitInfo, getWorkingState } from "../../utils/git"
import { getWorkspacePath } from "../../utils/path"
import { openFile } from "../../integrations/misc/open-file"
import { extractTextFromFile } from "../../integrations/misc/extract-text"
@ -49,16 +48,11 @@ function getUrlErrorMessage(error: unknown): string {
return t("common:errors.url_fetch_failed", { error: errorMessage })
}
export async function openMention(mention?: string): Promise<void> {
export async function openMention(cwd: string, mention?: string): Promise<void> {
if (!mention) {
return
}
const cwd = getWorkspacePath()
if (!cwd) {
return
}
if (mention.startsWith("/")) {
// Slice off the leading slash and unescape any spaces in the path
const relPath = unescapeSpaces(mention.slice(1))

View file

@ -138,6 +138,7 @@ export interface TaskOptions extends CreateTaskOptions {
taskNumber?: number
onCreated?: (task: Task) => void
initialTodos?: TodoItem[]
workspacePath?: string
}
export class Task extends EventEmitter<TaskEvents> implements TaskLike {
@ -313,6 +314,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
taskNumber = -1,
onCreated,
initialTodos,
workspacePath,
}: TaskOptions) {
super()
@ -333,7 +335,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Normal use-case is usually retry similar history task with new workspace.
this.workspacePath = parentTask
? parentTask.workspacePath
: getWorkspacePath(path.join(os.homedir(), "Desktop"))
: (workspacePath ?? getWorkspacePath(path.join(os.homedir(), "Desktop")))
this.instanceId = crypto.randomUUID().slice(0, 8)
this.taskNumber = -1

View file

@ -126,13 +126,14 @@ export class ClineProvider
private view?: vscode.WebviewView | vscode.WebviewPanel
private clineStack: Task[] = []
private codeIndexStatusSubscription?: vscode.Disposable
private currentWorkspaceManager?: CodeIndexManager
private codeIndexManager?: CodeIndexManager
private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
protected mcpHub?: McpHub // Change from private to protected
private marketplaceManager: MarketplaceManager
private mdmService?: MdmService
private taskCreationCallback: (task: Task) => void
private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
private currentWorkspacePath: string | undefined
private recentTasksCache?: string[]
private pendingOperations: Map<string, PendingEditOperation> = new Map()
@ -152,6 +153,7 @@ export class ClineProvider
mdmService?: MdmService,
) {
super()
this.currentWorkspacePath = getWorkspacePath()
ClineProvider.activeInstances.add(this)
@ -792,7 +794,7 @@ export class ClineProvider
this.log("Clearing webview resources for sidebar view")
this.clearWebviewResources()
// Reset current workspace manager reference when view is disposed
this.currentWorkspaceManager = undefined
this.codeIndexManager = undefined
}
},
null,
@ -880,6 +882,7 @@ export class ClineProvider
rootTask: historyItem.rootTask,
parentTask: historyItem.parentTask,
taskNumber: historyItem.number,
workspacePath: historyItem.workspace,
onCreated: this.taskCreationCallback,
enableBridge: BridgeOrchestrator.isEnabled(cloudUserInfo, remoteControlEnabled),
})
@ -1562,6 +1565,11 @@ export class ClineProvider
await this.postStateToWebview()
}
async refreshWorkspace() {
this.currentWorkspacePath = getWorkspacePath()
await this.postStateToWebview()
}
async postStateToWebview() {
const state = await this.getStateToPostToWebview()
this.postMessageToWebview({ type: "state", state })
@ -2287,7 +2295,7 @@ export class ClineProvider
const currentManager = this.getCurrentWorkspaceCodeIndexManager()
// If the manager hasn't changed, no need to update subscription
if (currentManager === this.currentWorkspaceManager) {
if (currentManager === this.codeIndexManager) {
return
}
@ -2298,7 +2306,7 @@ export class ClineProvider
}
// Update the current workspace manager reference
this.currentWorkspaceManager = currentManager
this.codeIndexManager = currentManager
// Subscribe to the new manager's progress updates if it exists
if (currentManager) {
@ -2659,7 +2667,7 @@ export class ClineProvider
}
public get cwd() {
return getWorkspacePath()
return this.currentWorkspacePath || getWorkspacePath()
}
/**

View file

@ -69,6 +69,9 @@ export const webviewMessageHandler = async (
const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
await provider.contextProxy.setValue(key, value)
const getCurrentCwd = () => {
return provider.getCurrentTask()?.cwd || provider.cwd
}
/**
* Shared utility to find message indices based on timestamp
*/
@ -912,10 +915,14 @@ export const webviewMessageHandler = async (
saveImage(message.dataUri!)
break
case "openFile":
openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number })
let filePath: string = message.text!
if (!path.isAbsolute(filePath)) {
filePath = path.join(getCurrentCwd(), filePath)
}
openFile(filePath, message.values as { create?: boolean; content?: string; line?: number })
break
case "openMention":
openMention(message.text)
openMention(getCurrentCwd(), message.text)
break
case "openExternal":
if (message.url) {
@ -1010,8 +1017,8 @@ export const webviewMessageHandler = async (
return
}
const workspaceFolder = vscode.workspace.workspaceFolders[0]
const rooDir = path.join(workspaceFolder.uri.fsPath, ".roo")
const workspaceFolder = getCurrentCwd()
const rooDir = path.join(workspaceFolder, ".roo")
const mcpPath = path.join(rooDir, "mcp.json")
try {
@ -1660,7 +1667,7 @@ export const webviewMessageHandler = async (
}
break
case "searchCommits": {
const cwd = provider.cwd
const cwd = getCurrentCwd()
if (cwd) {
try {
const commits = await searchCommits(message.query || "", cwd)
@ -1678,7 +1685,7 @@ export const webviewMessageHandler = async (
break
}
case "searchFiles": {
const workspacePath = getWorkspacePath()
const workspacePath = getCurrentCwd()
if (!workspacePath) {
// Handle case where workspace path is not available
@ -2662,7 +2669,7 @@ export const webviewMessageHandler = async (
case "requestCommands": {
try {
const { getCommands } = await import("../../services/command/commands")
const commands = await getCommands(provider.cwd || "")
const commands = await getCommands(getCurrentCwd())
// Convert to the format expected by the frontend
const commandList = commands.map((command) => ({
@ -2691,7 +2698,7 @@ export const webviewMessageHandler = async (
try {
if (message.text) {
const { getCommand } = await import("../../services/command/commands")
const command = await getCommand(provider.cwd || "", message.text)
const command = await getCommand(getCurrentCwd(), message.text)
if (command && command.filePath) {
openFile(command.filePath)
@ -2711,7 +2718,7 @@ export const webviewMessageHandler = async (
try {
if (message.text && message.values?.source) {
const { getCommand } = await import("../../services/command/commands")
const command = await getCommand(provider.cwd || "", message.text)
const command = await getCommand(getCurrentCwd(), message.text)
if (command && command.filePath) {
// Delete the command file
@ -2743,8 +2750,12 @@ export const webviewMessageHandler = async (
const globalConfigDir = path.join(os.homedir(), ".roo")
commandsDir = path.join(globalConfigDir, "commands")
} else {
if (!vscode.workspace.workspaceFolders?.length) {
vscode.window.showErrorMessage(t("common:errors.no_workspace"))
return
}
// Project commands
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
const workspaceRoot = getCurrentCwd()
if (!workspaceRoot) {
vscode.window.showErrorMessage(t("common:errors.no_workspace_for_project_command"))
break
@ -2824,7 +2835,7 @@ export const webviewMessageHandler = async (
// Refresh commands list
const { getCommands } = await import("../../services/command/commands")
const commands = await getCommands(provider.cwd || "")
const commands = await getCommands(getCurrentCwd() || "")
const commandList = commands.map((command) => ({
name: command.name,
source: command.source,

View file

@ -17,7 +17,7 @@ class WorkspaceTracker {
private resetTimer: NodeJS.Timeout | null = null
get cwd() {
return getWorkspacePath()
return this.providerRef?.deref()?.cwd ?? getWorkspacePath()
}
constructor(provider: ClineProvider) {
this.providerRef = new WeakRef(provider)

View file

@ -1,5 +1,4 @@
import * as vscode from "vscode"
import { getWorkspacePath } from "../../utils/path"
import { ContextProxy } from "../../core/config/ContextProxy"
import { VectorStoreSearchResult } from "./interfaces"
import { IndexingState } from "./interfaces/manager"
@ -133,7 +132,7 @@ export class CodeIndexManager {
}
// 3. Check if workspace is available
const workspacePath = getWorkspacePath()
const workspacePath = this.workspacePath
if (!workspacePath) {
this._stateManager.setSystemState("Standby", "No workspace folder open")
return { requiresRestart }
@ -306,7 +305,7 @@ export class CodeIndexManager {
)
const ignoreInstance = ignore()
const workspacePath = getWorkspacePath()
const workspacePath = this.workspacePath
if (!workspacePath) {
this._stateManager.setSystemState("Standby", "")

View file

@ -17,6 +17,7 @@ export class QdrantVectorStore implements IVectorStore {
private client: QdrantClient
private readonly collectionName: string
private readonly qdrantUrl: string = "http://localhost:6333"
private readonly workspacePath: string
/**
* Creates a new Qdrant vector store
@ -29,6 +30,7 @@ export class QdrantVectorStore implements IVectorStore {
// Store the resolved URL for our property
this.qdrantUrl = parsedUrl
this.workspacePath = workspacePath
try {
const urlObj = new URL(parsedUrl)
@ -457,7 +459,7 @@ export class QdrantVectorStore implements IVectorStore {
return
}
const workspaceRoot = getWorkspacePath()
const workspaceRoot = this.workspacePath
// Build filters using pathSegments to match the indexed fields
const filters = filePaths.map((filePath) => {

View file

@ -30,7 +30,7 @@ import {
McpToolCallResponse,
} from "../../shared/mcp"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual } from "../../utils/path"
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
import { injectVariables } from "../../utils/config"
// Discriminated union for connection states
@ -349,7 +349,7 @@ export class McpHub {
return
}
const workspaceFolder = vscode.workspace.workspaceFolders[0]
const workspaceFolder = this.providerRef.deref()?.cwd ?? getWorkspacePath()
const projectMcpPattern = new vscode.RelativePattern(workspaceFolder, ".roo/mcp.json")
// Create a file system watcher for the project MCP file pattern
@ -551,12 +551,8 @@ export class McpHub {
// Get project-level MCP configuration path
private async getProjectMcpPath(): Promise<string | null> {
if (!vscode.workspace.workspaceFolders?.length) {
return null
}
const workspaceFolder = vscode.workspace.workspaceFolders[0]
const projectMcpDir = path.join(workspaceFolder.uri.fsPath, ".roo")
const workspacePath = this.providerRef.deref()?.cwd ?? getWorkspacePath()
const projectMcpDir = path.join(workspacePath, ".roo")
const projectMcpPath = path.join(projectMcpDir, "mcp.json")
try {