diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a7732f80266..644fd94b35c 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -101,6 +101,7 @@ export const globalSettingsSchema = z.object({ maxWorkspaceFiles: z.number().optional(), showRooIgnoredFiles: z.boolean().optional(), maxReadFileLine: z.number().optional(), + includeVSCodeFileContext: z.boolean().optional(), terminalOutputLineLimit: z.number().optional(), terminalOutputCharacterLimit: z.number().optional(), @@ -272,6 +273,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { maxWorkspaceFiles: 200, showRooIgnoredFiles: true, maxReadFileLine: -1, // -1 to enable full file reading. + includeVSCodeFileContext: true, // Include VSCode file context in all API calls by default includeDiagnosticMessages: true, maxDiagnosticMessages: 50, diff --git a/src/core/environment/__tests__/getEnvironmentDetails.spec.ts b/src/core/environment/__tests__/getEnvironmentDetails.spec.ts index a1b8691e703..8ad12bc5ee3 100644 --- a/src/core/environment/__tests__/getEnvironmentDetails.spec.ts +++ b/src/core/environment/__tests__/getEnvironmentDetails.spec.ts @@ -73,6 +73,7 @@ describe("getEnvironmentDetails", () => { terminalOutputLineLimit: 100, maxWorkspaceFiles: 50, maxOpenTabsContext: 10, + includeVSCodeFileContext: true, mode: "code", customModes: [], experiments: {}, @@ -361,4 +362,54 @@ describe("getEnvironmentDetails", () => { await expect(getEnvironmentDetails(mockCline as Task)).resolves.not.toThrow() }) + + describe("VSCode file context inclusion", () => { + it("should include VSCode file context when includeVSCodeFileContext is true", async () => { + mockProvider.getState.mockResolvedValue({ + ...mockState, + includeVSCodeFileContext: true, + }) + + const result = await getEnvironmentDetails(mockCline as Task, false) + + expect(result).toContain("# VSCode Visible Files") + expect(result).toContain("# VSCode Open Tabs") + }) + + it("should exclude VSCode file context when includeVSCodeFileContext is false and includeFileDetails is false", async () => { + mockProvider.getState.mockResolvedValue({ + ...mockState, + includeVSCodeFileContext: false, + }) + + const result = await getEnvironmentDetails(mockCline as Task, false) + + expect(result).not.toContain("# VSCode Visible Files") + expect(result).not.toContain("# VSCode Open Tabs") + }) + + it("should always include VSCode file context when includeFileDetails is true regardless of includeVSCodeFileContext", async () => { + mockProvider.getState.mockResolvedValue({ + ...mockState, + includeVSCodeFileContext: false, + }) + + const result = await getEnvironmentDetails(mockCline as Task, true) + + expect(result).toContain("# VSCode Visible Files") + expect(result).toContain("# VSCode Open Tabs") + }) + + it("should default to including VSCode file context when includeVSCodeFileContext is undefined", async () => { + mockProvider.getState.mockResolvedValue({ + ...mockState, + includeVSCodeFileContext: undefined, + }) + + const result = await getEnvironmentDetails(mockCline as Task, false) + + expect(result).toContain("# VSCode Visible Files") + expect(result).toContain("# VSCode Open Tabs") + }) + }) }) diff --git a/src/core/environment/getEnvironmentDetails.ts b/src/core/environment/getEnvironmentDetails.ts index b83b37c75bb..7be2fd215c3 100644 --- a/src/core/environment/getEnvironmentDetails.ts +++ b/src/core/environment/getEnvironmentDetails.ts @@ -30,48 +30,54 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo terminalOutputLineLimit = 500, terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT, maxWorkspaceFiles = 200, + includeVSCodeFileContext = true, } = state ?? {} - // It could be useful for cline to know if the user went from one or no - // file to another between messages, so we always include this context. - details += "\n\n# VSCode Visible Files" - - const visibleFilePaths = vscode.window.visibleTextEditors - ?.map((editor) => editor.document?.uri?.fsPath) - .filter(Boolean) - .map((absolutePath) => path.relative(cline.cwd, absolutePath)) - .slice(0, maxWorkspaceFiles) - - // Filter paths through rooIgnoreController - const allowedVisibleFiles = cline.rooIgnoreController - ? cline.rooIgnoreController.filterPaths(visibleFilePaths) - : visibleFilePaths.map((p) => p.toPosix()).join("\n") - - if (allowedVisibleFiles) { - details += `\n${allowedVisibleFiles}` - } else { - details += "\n(No visible files)" - } + // Only include VSCode file context if enabled in settings or if this is the first request + const shouldIncludeVSCodeContext = includeFileDetails || includeVSCodeFileContext + + if (shouldIncludeVSCodeContext) { + // It could be useful for cline to know if the user went from one or no + // file to another between messages, so we always include this context. + details += "\n\n# VSCode Visible Files" + + const visibleFilePaths = vscode.window.visibleTextEditors + ?.map((editor) => editor.document?.uri?.fsPath) + .filter(Boolean) + .map((absolutePath) => path.relative(cline.cwd, absolutePath)) + .slice(0, maxWorkspaceFiles) - details += "\n\n# VSCode Open Tabs" - const { maxOpenTabsContext } = state ?? {} - const maxTabs = maxOpenTabsContext ?? 20 - const openTabPaths = vscode.window.tabGroups.all - .flatMap((group) => group.tabs) - .map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath) - .filter(Boolean) - .map((absolutePath) => path.relative(cline.cwd, absolutePath).toPosix()) - .slice(0, maxTabs) - - // Filter paths through rooIgnoreController - const allowedOpenTabs = cline.rooIgnoreController - ? cline.rooIgnoreController.filterPaths(openTabPaths) - : openTabPaths.map((p) => p.toPosix()).join("\n") - - if (allowedOpenTabs) { - details += `\n${allowedOpenTabs}` - } else { - details += "\n(No open tabs)" + // Filter paths through rooIgnoreController + const allowedVisibleFiles = cline.rooIgnoreController + ? cline.rooIgnoreController.filterPaths(visibleFilePaths) + : visibleFilePaths.map((p) => p.toPosix()).join("\n") + + if (allowedVisibleFiles) { + details += `\n${allowedVisibleFiles}` + } else { + details += "\n(No visible files)" + } + + details += "\n\n# VSCode Open Tabs" + const { maxOpenTabsContext } = state ?? {} + const maxTabs = maxOpenTabsContext ?? 20 + const openTabPaths = vscode.window.tabGroups.all + .flatMap((group) => group.tabs) + .map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath) + .filter(Boolean) + .map((absolutePath) => path.relative(cline.cwd, absolutePath).toPosix()) + .slice(0, maxTabs) + + // Filter paths through rooIgnoreController + const allowedOpenTabs = cline.rooIgnoreController + ? cline.rooIgnoreController.filterPaths(openTabPaths) + : openTabPaths.map((p) => p.toPosix()).join("\n") + + if (allowedOpenTabs) { + details += `\n${allowedOpenTabs}` + } else { + details += "\n(No open tabs)" + } } // Get task-specific and background terminals. diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 905e657b37e..0d6541f2bae 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1420,6 +1420,7 @@ export class ClineProvider experiments, maxOpenTabsContext, maxWorkspaceFiles, + includeVSCodeFileContext, browserToolEnabled, telemetrySetting, showRooIgnoredFiles, @@ -1523,6 +1524,7 @@ export class ClineProvider mcpServers: this.mcpHub?.getAllServers() ?? [], maxOpenTabsContext: maxOpenTabsContext ?? 20, maxWorkspaceFiles: maxWorkspaceFiles ?? 200, + includeVSCodeFileContext: includeVSCodeFileContext ?? true, cwd, browserToolEnabled: browserToolEnabled ?? true, telemetrySetting, @@ -1697,6 +1699,7 @@ export class ClineProvider customModes, maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20, maxWorkspaceFiles: stateValues.maxWorkspaceFiles ?? 200, + includeVSCodeFileContext: stateValues.includeVSCodeFileContext ?? true, openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true, browserToolEnabled: stateValues.browserToolEnabled ?? true, telemetrySetting: stateValues.telemetrySetting || "unset", diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 344b0988165..5013864fda5 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -528,6 +528,7 @@ describe("ClineProvider", () => { experiments: experimentDefault, maxOpenTabsContext: 20, maxWorkspaceFiles: 200, + includeVSCodeFileContext: true, browserToolEnabled: true, telemetrySetting: "unset", showRooIgnoredFiles: true, @@ -1079,6 +1080,25 @@ describe("ClineProvider", () => { expect(mockPostMessage).toHaveBeenCalled() }) + test("handles includeVSCodeFileContext message", async () => { + await provider.resolveWebviewView(mockWebviewView) + const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] + + // Test setting to false + await messageHandler({ type: "includeVSCodeFileContext", bool: false }) + + expect(updateGlobalStateSpy).toHaveBeenCalledWith("includeVSCodeFileContext", false) + expect(mockContext.globalState.update).toHaveBeenCalledWith("includeVSCodeFileContext", false) + expect(mockPostMessage).toHaveBeenCalled() + + // Test setting to true + await messageHandler({ type: "includeVSCodeFileContext", bool: true }) + + expect(updateGlobalStateSpy).toHaveBeenCalledWith("includeVSCodeFileContext", true) + expect(mockContext.globalState.update).toHaveBeenCalledWith("includeVSCodeFileContext", true) + expect(mockPostMessage).toHaveBeenCalled() + }) + test("handles mode-specific custom instructions updates", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c739c2ade8d..8fff4aeb0b6 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1236,6 +1236,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("maxWorkspaceFiles", fileCount) await provider.postStateToWebview() break + case "includeVSCodeFileContext": + await updateGlobalState("includeVSCodeFileContext", message.bool ?? true) + await provider.postStateToWebview() + break case "alwaysAllowFollowupQuestions": await updateGlobalState("alwaysAllowFollowupQuestions", message.bool ?? false) await provider.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 000762e317a..f0c0e8241d2 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -226,6 +226,7 @@ export type ExtensionState = Pick< // | "maxOpenTabsContext" // Optional in GlobalSettings, required here. // | "maxWorkspaceFiles" // Optional in GlobalSettings, required here. // | "showRooIgnoredFiles" // Optional in GlobalSettings, required here. + // | "includeVSCodeFileContext" // Optional in GlobalSettings, required here. // | "maxReadFileLine" // Optional in GlobalSettings, required here. | "maxConcurrentFileReads" // Optional in GlobalSettings, required here. | "terminalOutputLineLimit" @@ -277,6 +278,7 @@ export type ExtensionState = Pick< maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500) maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500) showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings + includeVSCodeFileContext: boolean // Whether to include VSCode file context (visible files and open tabs) in API calls after the first one maxReadFileLine: number // Maximum number of lines to read from a file before truncating experiments: Experiments // Map of experiment IDs to their enabled state diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 795e2765222..c30d40ef8f6 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -151,6 +151,7 @@ export interface WebviewMessage { | "deleteMcpServer" | "maxOpenTabsContext" | "maxWorkspaceFiles" + | "includeVSCodeFileContext" | "humanRelayResponse" | "humanRelayCancel" | "browserToolEnabled" diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index ff1ce31c53c..8e187461f84 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -202,6 +202,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode customModes: [], maxOpenTabsContext: 20, maxWorkspaceFiles: 200, + includeVSCodeFileContext: true, cwd: "", browserToolEnabled: true, telemetrySetting: "unset", diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 1e5867d3fc3..90503083472 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -209,6 +209,7 @@ describe("mergeExtensionState", () => { sharingEnabled: false, profileThresholds: {}, hasOpenedModeSelector: false, // Add the new required property + includeVSCodeFileContext: true, } const prevState: ExtensionState = {