diff --git a/.changeset/early-dodos-know.md b/.changeset/early-dodos-know.md new file mode 100644 index 0000000..3bd8d11 --- /dev/null +++ b/.changeset/early-dodos-know.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Add a setting to control the number of terminal output lines to pass to the model when executing commands diff --git a/README.md b/README.md index bcf8ff2..5d1cd7b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ A fork of Cline, an autonomous coding agent, with some additional experimental f - Per-tool MCP auto-approval - Enable/disable MCP servers - Configurable delay after auto-writes to allow diagnostics to detect potential problems +- Control the number of terminal output lines to pass to the model when executing commands - Runs alongside the original Cline ## Disclaimer diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 0970e04..5a2a9de 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -721,9 +721,9 @@ export class Cline { } } - let result = "" + let lines: string[] = [] process.on("line", (line) => { - result += line + "\n" + lines.push(line) if (!didContinue) { sendCommandOutput(line) } else { @@ -731,6 +731,22 @@ export class Cline { } }) + const getFormattedOutput = async () => { + const { terminalOutputLineLimit } = await this.providerRef.deref()?.getState() ?? {} + const limit = terminalOutputLineLimit ?? 0 + + if (limit > 0 && lines.length > limit) { + const beforeLimit = Math.floor(limit * 0.2) // 20% of lines before + const afterLimit = limit - beforeLimit // remaining 80% after + return [ + ...lines.slice(0, beforeLimit), + `\n[...${lines.length - limit} lines omitted...]\n`, + ...lines.slice(-afterLimit) + ].join('\n') + } + return lines.join('\n') + } + let completed = false process.once("completed", () => { completed = true @@ -749,7 +765,8 @@ export class Cline { // grouping command_output messages despite any gaps anyways) await delay(50) - result = result.trim() + const output = await getFormattedOutput() + const result = output.trim() if (userFeedback) { await this.say("user_feedback", userFeedback.text, userFeedback.images) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 2d584eb..6289a3b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -76,6 +76,7 @@ type GlobalStateKey = | "fuzzyMatchThreshold" | "preferredLanguage" // Language setting for Cline's communication | "writeDelayMs" + | "terminalOutputLineLimit" export const GlobalFileNames = { apiConversationHistory: "api_conversation_history.json", @@ -642,6 +643,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("writeDelayMs", message.value) await this.postStateToWebview() break + case "terminalOutputLineLimit": + await this.updateGlobalState("terminalOutputLineLimit", message.value) + await this.postStateToWebview() + break case "deleteMessage": { const answer = await vscode.window.showInformationMessage( "Are you sure you want to delete this message and all subsequent messages?", @@ -1046,6 +1051,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { screenshotQuality, preferredLanguage, writeDelayMs, + terminalOutputLineLimit, } = await this.getState() const allowedCommands = vscode.workspace @@ -1075,6 +1081,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { screenshotQuality: screenshotQuality ?? 75, preferredLanguage: preferredLanguage ?? 'English', writeDelayMs: writeDelayMs ?? 1000, + terminalOutputLineLimit: terminalOutputLineLimit ?? 500, } } @@ -1174,6 +1181,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { preferredLanguage, writeDelayMs, screenshotQuality, + terminalOutputLineLimit, ] = await Promise.all([ this.getGlobalState("apiProvider") as Promise, this.getGlobalState("apiModelId") as Promise, @@ -1218,6 +1226,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("preferredLanguage") as Promise, this.getGlobalState("writeDelayMs") as Promise, this.getGlobalState("screenshotQuality") as Promise, + this.getGlobalState("terminalOutputLineLimit") as Promise, ]) let apiProvider: ApiProvider @@ -1279,6 +1288,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { screenshotQuality: screenshotQuality ?? 75, fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0, writeDelayMs: writeDelayMs ?? 1000, + terminalOutputLineLimit: terminalOutputLineLimit ?? 500, preferredLanguage: preferredLanguage ?? (() => { // Get VSCode's locale setting const vscodeLang = vscode.env.language; diff --git a/src/integrations/terminal/TerminalRegistry.ts b/src/integrations/terminal/TerminalRegistry.ts index ac0ed30..5937047 100644 --- a/src/integrations/terminal/TerminalRegistry.ts +++ b/src/integrations/terminal/TerminalRegistry.ts @@ -16,8 +16,11 @@ export class TerminalRegistry { static createTerminal(cwd?: string | vscode.Uri | undefined): TerminalInfo { const terminal = vscode.window.createTerminal({ cwd, - name: "Cline", - iconPath: new vscode.ThemeIcon("robot"), + name: "Roo Cline", + iconPath: new vscode.ThemeIcon("rocket"), + env: { + PAGER: "cat" + } }) const newInfo: TerminalInfo = { terminal, diff --git a/src/integrations/terminal/__tests__/TerminalRegistry.test.ts b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts new file mode 100644 index 0000000..9aa2483 --- /dev/null +++ b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts @@ -0,0 +1,37 @@ +import * as vscode from "vscode" +import { TerminalRegistry } from "../TerminalRegistry" + +// Mock vscode.window.createTerminal +const mockCreateTerminal = jest.fn() +jest.mock("vscode", () => ({ + window: { + createTerminal: (...args: any[]) => { + mockCreateTerminal(...args) + return { + exitStatus: undefined, + } + }, + }, + ThemeIcon: jest.fn(), +})) + +describe("TerminalRegistry", () => { + beforeEach(() => { + mockCreateTerminal.mockClear() + }) + + describe("createTerminal", () => { + it("creates terminal with PAGER set to cat", () => { + TerminalRegistry.createTerminal("/test/path") + + expect(mockCreateTerminal).toHaveBeenCalledWith({ + cwd: "/test/path", + name: "Roo Cline", + iconPath: expect.any(Object), + env: { + PAGER: "cat" + } + }) + }) + }) +}) \ No newline at end of file diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index cc46135..3acc740 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -61,6 +61,7 @@ export interface ExtensionState { fuzzyMatchThreshold?: number preferredLanguage: string writeDelayMs: number + terminalOutputLineLimit?: number } export interface ClineMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 6d4e4c0..bfa659a 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -48,6 +48,7 @@ export interface WebviewMessage { | "enhancedPrompt" | "draggedImages" | "deleteMessage" + | "terminalOutputLineLimit" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 6952480..5442bd0 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -46,6 +46,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { setWriteDelayMs, screenshotQuality, setScreenshotQuality, + terminalOutputLineLimit, + setTerminalOutputLineLimit, } = useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined) @@ -76,6 +78,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage }) vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs }) vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 }) + vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 }) onDone() } } @@ -210,6 +213,31 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {

+
+
+ Terminal output limit + setTerminalOutputLineLimit(parseInt(e.target.value))} + style={{ + flexGrow: 1, + accentColor: 'var(--vscode-button-background)', + height: '2px' + }} + /> + + {terminalOutputLineLimit ?? 500} + +
+

+ Maximum number of lines to include in terminal output when executing commands. When exceeded lines will be removed from the middle, saving tokens. +

+
+
setDiffEnabled(e.target.checked)}> Enable editing through diffs @@ -431,7 +459,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {

Browser Settings

- + void screenshotQuality?: number setScreenshotQuality: (value: number) => void + terminalOutputLineLimit?: number + setTerminalOutputLineLimit: (value: number) => void } export const ExtensionStateContext = createContext(undefined) @@ -58,6 +60,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode writeDelayMs: 1000, browserViewportSize: "900x600", screenshotQuality: 75, + terminalOutputLineLimit: 500, }) const [didHydrateState, setDidHydrateState] = useState(false) const [showWelcome, setShowWelcome] = useState(false) @@ -176,6 +179,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })), setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })), setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })), + setTerminalOutputLineLimit: (value) => setState((prevState) => ({ ...prevState, terminalOutputLineLimit: value })), } return {children}