fix: add includeVSCodeFileContext setting to control VSCode file context in API calls

- Add new global setting includeVSCodeFileContext (defaults to true)
- Update getEnvironmentDetails to conditionally include VSCode file context
- Add setting to ExtensionState and webview message handler
- Add comprehensive tests for the new functionality

This fixes issue #6194 where excessive VSCode file context was causing
connection errors with LLMs that have limited context windows.
This commit is contained in:
Roo Code 2025-07-25 02:03:24 +00:00
parent 2170c61e3c
commit d68eeb35c7
8 changed files with 123 additions and 34 deletions

View file

@ -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,

View file

@ -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")
})
})
})

View file

@ -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"
// Only include VSCode file context if enabled in settings or if this is the first request
const shouldIncludeVSCodeContext = includeFileDetails || includeVSCodeFileContext
const visibleFilePaths = vscode.window.visibleTextEditors
?.map((editor) => editor.document?.uri?.fsPath)
.filter(Boolean)
.map((absolutePath) => path.relative(cline.cwd, absolutePath))
.slice(0, maxWorkspaceFiles)
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"
// Filter paths through rooIgnoreController
const allowedVisibleFiles = cline.rooIgnoreController
? cline.rooIgnoreController.filterPaths(visibleFilePaths)
: visibleFilePaths.map((p) => p.toPosix()).join("\n")
const visibleFilePaths = vscode.window.visibleTextEditors
?.map((editor) => editor.document?.uri?.fsPath)
.filter(Boolean)
.map((absolutePath) => path.relative(cline.cwd, absolutePath))
.slice(0, maxWorkspaceFiles)
if (allowedVisibleFiles) {
details += `\n${allowedVisibleFiles}`
} else {
details += "\n(No visible files)"
}
// Filter paths through rooIgnoreController
const allowedVisibleFiles = cline.rooIgnoreController
? cline.rooIgnoreController.filterPaths(visibleFilePaths)
: visibleFilePaths.map((p) => p.toPosix()).join("\n")
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)
if (allowedVisibleFiles) {
details += `\n${allowedVisibleFiles}`
} else {
details += "\n(No visible files)"
}
// Filter paths through rooIgnoreController
const allowedOpenTabs = cline.rooIgnoreController
? cline.rooIgnoreController.filterPaths(openTabPaths)
: openTabPaths.map((p) => p.toPosix()).join("\n")
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)
if (allowedOpenTabs) {
details += `\n${allowedOpenTabs}`
} else {
details += "\n(No open tabs)"
// 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.

View file

@ -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",

View file

@ -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]

View file

@ -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()

View file

@ -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

View file

@ -151,6 +151,7 @@ export interface WebviewMessage {
| "deleteMcpServer"
| "maxOpenTabsContext"
| "maxWorkspaceFiles"
| "includeVSCodeFileContext"
| "humanRelayResponse"
| "humanRelayCancel"
| "browserToolEnabled"