diff --git a/src/core/Cline.ts b/src/core/Cline.ts index c50d48b6cc..8b5afe9806 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3501,7 +3501,7 @@ export class Cline extends EventEmitter { async getEnvironmentDetails(includeFileDetails: boolean = false) { let details = "" - const { terminalOutputLineLimit } = (await this.providerRef.deref()?.getState()) ?? {} + const { terminalOutputLineLimit, maxWorkspaceFiles } = (await this.providerRef.deref()?.getState()) ?? {} // 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" @@ -3509,6 +3509,7 @@ export class Cline extends EventEmitter { ?.map((editor) => editor.document?.uri?.fsPath) .filter(Boolean) .map((absolutePath) => path.relative(cwd, absolutePath)) + .slice(0, maxWorkspaceFiles ?? 200) // Filter paths through rooIgnoreController const allowedVisibleFiles = this.rooIgnoreController @@ -3715,7 +3716,8 @@ export class Cline extends EventEmitter { // don't want to immediately access desktop since it would show permission popup details += "(Desktop files not shown automatically. Use list_files to explore if needed.)" } else { - const [files, didHitLimit] = await listFiles(cwd, true, 200) + const maxFiles = maxWorkspaceFiles ?? 200 + const [files, didHitLimit] = await listFiles(cwd, true, maxFiles) const { showRooIgnoredFiles } = (await this.providerRef.deref()?.getState()) ?? {} const result = formatResponse.formatFilesList( cwd, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b98934400d..87f33dc418 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1518,6 +1518,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("maxOpenTabsContext", tabCount) await this.postStateToWebview() break + case "maxWorkspaceFiles": + const fileCount = Math.min(Math.max(0, message.value ?? 200), 500) + await this.updateGlobalState("maxWorkspaceFiles", fileCount) + await this.postStateToWebview() + break case "browserToolEnabled": await this.updateGlobalState("browserToolEnabled", message.bool ?? true) await this.postStateToWebview() @@ -2297,6 +2302,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { autoApprovalEnabled, experiments, maxOpenTabsContext, + maxWorkspaceFiles, browserToolEnabled, telemetrySetting, showRooIgnoredFiles, @@ -2359,6 +2365,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { experiments: experiments ?? experimentDefault, mcpServers: this.mcpHub?.getAllServers() ?? [], maxOpenTabsContext: maxOpenTabsContext ?? 20, + maxWorkspaceFiles: maxWorkspaceFiles ?? 200, cwd, browserToolEnabled: browserToolEnabled ?? true, telemetrySetting, @@ -2516,6 +2523,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false, customModes, maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20, + maxWorkspaceFiles: stateValues.maxWorkspaceFiles ?? 200, openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true, browserToolEnabled: stateValues.browserToolEnabled ?? true, telemetrySetting: stateValues.telemetrySetting || "unset", diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index abe7a8475a..7530052b85 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -448,6 +448,7 @@ describe("ClineProvider", () => { customModes: [], experiments: experimentDefault, maxOpenTabsContext: 20, + maxWorkspaceFiles: 200, browserToolEnabled: true, telemetrySetting: "unset", showRooIgnoredFiles: true, @@ -794,6 +795,17 @@ describe("ClineProvider", () => { expect(state.customModePrompts).toEqual({}) }) + test("handles maxWorkspaceFiles message", async () => { + await provider.resolveWebviewView(mockWebviewView) + const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] + + await messageHandler({ type: "maxWorkspaceFiles", value: 300 }) + + expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("maxWorkspaceFiles", 300) + expect(mockContext.globalState.update).toHaveBeenCalledWith("maxWorkspaceFiles", 300) + expect(mockPostMessage).toHaveBeenCalled() + }) + test.only("uses mode-specific custom instructions in Cline initialization", async () => { // Setup mock state const modeCustomInstructions = "Code mode instructions" diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index e5338bd737..1c45dec84b 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -211,6 +211,7 @@ export type GlobalStateKey = | "modelMaxTokens" | "mistralCodestralUrl" | "maxOpenTabsContext" + | "maxWorkspaceFiles" | "browserToolEnabled" | "lmStudioSpeculativeDecodingEnabled" | "lmStudioDraftModelId" diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index c0bcb4ed65..5b018ec22e 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -147,6 +147,7 @@ export interface ExtensionState { customModes: ModeConfig[] toolRequirements?: Record // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled) 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) cwd?: string // Current working directory telemetrySetting: TelemetrySetting telemetryKey?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index e9a64d891b..bb77ea7203 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -97,6 +97,7 @@ export interface WebviewMessage { | "checkpointRestore" | "deleteMcpServer" | "maxOpenTabsContext" + | "maxWorkspaceFiles" | "humanRelayResponse" | "humanRelayCancel" | "browserToolEnabled" diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index 9896c37697..cec7bdf9ce 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -116,6 +116,7 @@ export const GLOBAL_STATE_KEYS = [ "telemetrySetting", "showRooIgnoredFiles", "remoteBrowserEnabled", + "maxWorkspaceFiles", ] as const type CheckGlobalStateKeysExhaustiveness = diff --git a/webview-ui/src/__mocks__/lucide-react.ts b/webview-ui/src/__mocks__/lucide-react.ts index d85cd25d6a..64ab05eb34 100644 --- a/webview-ui/src/__mocks__/lucide-react.ts +++ b/webview-ui/src/__mocks__/lucide-react.ts @@ -4,3 +4,4 @@ export const Check = () => React.createElement("div") export const ChevronsUpDown = () => React.createElement("div") export const Loader = () => React.createElement("div") export const X = () => React.createElement("div") +export const Database = (props: any) => React.createElement("span", { "data-testid": "database-icon", ...props }) diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index f8563071f2..d5f4c33f9a 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -12,13 +12,17 @@ import { Section } from "./Section" type ContextManagementSettingsProps = HTMLAttributes & { terminalOutputLineLimit?: number maxOpenTabsContext: number + maxWorkspaceFiles: number showRooIgnoredFiles?: boolean - setCachedStateField: SetCachedStateField<"terminalOutputLineLimit" | "maxOpenTabsContext" | "showRooIgnoredFiles"> + setCachedStateField: SetCachedStateField< + "terminalOutputLineLimit" | "maxOpenTabsContext" | "maxWorkspaceFiles" | "showRooIgnoredFiles" + > } export const ContextManagementSettings = ({ terminalOutputLineLimit, maxOpenTabsContext, + maxWorkspaceFiles, showRooIgnoredFiles, setCachedStateField, className, @@ -26,7 +30,7 @@ export const ContextManagementSettings = ({ }: ContextManagementSettingsProps) => { return (
- +
Context Management
@@ -48,6 +52,7 @@ export const ContextManagementSettings = ({ setCachedStateField("terminalOutputLineLimit", parseInt(e.target.value)) } className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" + data-testid="terminal-output-limit-slider" /> {terminalOutputLineLimit ?? 500}
@@ -70,6 +75,7 @@ export const ContextManagementSettings = ({ value={maxOpenTabsContext ?? 20} onChange={(e) => setCachedStateField("maxOpenTabsContext", parseInt(e.target.value))} className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" + data-testid="open-tabs-limit-slider" /> {maxOpenTabsContext ?? 20}
@@ -80,12 +86,36 @@ export const ContextManagementSettings = ({

+
+
+ Workspace files context limit +
+ setCachedStateField("maxWorkspaceFiles", parseInt(e.target.value))} + className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" + data-testid="workspace-files-limit-slider" + /> + {maxWorkspaceFiles ?? 200} +
+
+

+ Maximum number of files to include in current working directory details. Higher values provide + more context but increase token usage. +

+
+
{ setCachedStateField("showRooIgnoredFiles", e.target.checked) - }}> + }} + data-testid="show-rooignored-files-checkbox"> Show .rooignore'd files in lists and searches

diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index c267acfee1..1cdc1d00fa 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -85,6 +85,7 @@ const SettingsView = forwardRef(({ onDone }, experiments, fuzzyMatchThreshold, maxOpenTabsContext, + maxWorkspaceFiles, mcpEnabled, rateLimitSeconds, requestDelaySeconds, @@ -196,6 +197,7 @@ const SettingsView = forwardRef(({ onDone }, vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds }) vscode.postMessage({ type: "rateLimitSeconds", value: rateLimitSeconds }) vscode.postMessage({ type: "maxOpenTabsContext", value: maxOpenTabsContext }) + vscode.postMessage({ type: "maxWorkspaceFiles", value: maxWorkspaceFiles ?? 200 }) vscode.postMessage({ type: "showRooIgnoredFiles", bool: showRooIgnoredFiles }) vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName }) vscode.postMessage({ type: "updateExperimental", values: experiments }) @@ -410,6 +412,7 @@ const SettingsView = forwardRef(({ onDone }, diff --git a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx index 98ea4f78f5..25e7fa3e50 100644 --- a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx @@ -5,6 +5,7 @@ describe("ContextManagementSettings", () => { const defaultProps = { terminalOutputLineLimit: 500, maxOpenTabsContext: 20, + maxWorkspaceFiles: 200, showRooIgnoredFiles: false, setCachedStateField: jest.fn(), } @@ -18,21 +19,25 @@ describe("ContextManagementSettings", () => { // Terminal output limit expect(screen.getByText("Terminal output limit")).toBeInTheDocument() - expect(screen.getByRole("slider", { name: /Terminal output limit/i })).toHaveValue("500") + expect(screen.getByTestId("terminal-output-limit-slider")).toHaveValue("500") // Open tabs context limit expect(screen.getByText("Open tabs context limit")).toBeInTheDocument() - expect(screen.getByRole("slider", { name: /Open tabs context limit/i })).toHaveValue("20") + expect(screen.getByTestId("open-tabs-limit-slider")).toHaveValue("20") + + // Workspace files limit + expect(screen.getByText("Workspace files context limit")).toBeInTheDocument() + expect(screen.getByTestId("workspace-files-limit-slider")).toHaveValue("200") // Show .rooignore'd files expect(screen.getByText("Show .rooignore'd files in lists and searches")).toBeInTheDocument() - expect(screen.getByRole("checkbox")).not.toBeChecked() + expect(screen.getByTestId("show-rooignored-files-checkbox")).not.toBeChecked() }) it("updates terminal output limit", () => { render() - const slider = screen.getByRole("slider", { name: /Terminal output limit/i }) + const slider = screen.getByTestId("terminal-output-limit-slider") fireEvent.change(slider, { target: { value: "1000" } }) expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("terminalOutputLineLimit", 1000) @@ -41,16 +46,25 @@ describe("ContextManagementSettings", () => { it("updates open tabs context limit", () => { render() - const slider = screen.getByRole("slider", { name: /Open tabs context limit/i }) + const slider = screen.getByTestId("open-tabs-limit-slider") fireEvent.change(slider, { target: { value: "50" } }) expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("maxOpenTabsContext", 50) }) + it("updates workspace files contextlimit", () => { + render() + + const slider = screen.getByTestId("workspace-files-limit-slider") + fireEvent.change(slider, { target: { value: "50" } }) + + expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("maxWorkspaceFiles", 50) + }) + it("updates show rooignored files setting", () => { render() - const checkbox = screen.getByRole("checkbox") + const checkbox = screen.getByTestId("show-rooignored-files-checkbox") fireEvent.click(checkbox) expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("showRooIgnoredFiles", true) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index d79f263e83..b05a1a9f24 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -69,6 +69,8 @@ export interface ExtensionStateContextType extends ExtensionState { customModes: ModeConfig[] setCustomModes: (value: ModeConfig[]) => void setMaxOpenTabsContext: (value: number) => void + maxWorkspaceFiles: number + setMaxWorkspaceFiles: (value: number) => void setTelemetrySetting: (value: TelemetrySetting) => void remoteBrowserEnabled?: boolean setRemoteBrowserEnabled: (value: boolean) => void @@ -137,6 +139,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode autoApprovalEnabled: false, customModes: [], maxOpenTabsContext: 20, + maxWorkspaceFiles: 200, cwd: "", browserToolEnabled: true, telemetrySetting: "unset", @@ -280,6 +283,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })), setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })), setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })), + setMaxWorkspaceFiles: (value) => setState((prevState) => ({ ...prevState, maxWorkspaceFiles: value })), setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })), setTelemetrySetting: (value) => setState((prevState) => ({ ...prevState, telemetrySetting: value })), setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })), diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index f8acf4f4d3..6b7742ba06 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -116,6 +116,7 @@ describe("mergeExtensionState", () => { experiments: {} as Record, customModes: [], maxOpenTabsContext: 20, + maxWorkspaceFiles: 100, apiConfiguration: { providerId: "openrouter" } as ApiConfiguration, telemetrySetting: "unset", showRooIgnoredFiles: true,