Skip to content

Commit a6c33af

Browse files
authored
add terminal setting to allow users to constrain terminal output (RooCodeInc#4150)
* base * grpc * grpc * base 3 * settings stuff * changeset * nit * format * smol
1 parent ec26a91 commit a6c33af

File tree

13 files changed

+98
-3
lines changed

13 files changed

+98
-3
lines changed

.changeset/clean-beers-clean.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
add ability to constrain size of terminal output

proto/state.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ message UpdateSettingsRequest {
9696
optional bool terminal_reuse_enabled = 9;
9797
optional bool mcp_responses_collapsed = 10;
9898
optional bool mcp_rich_display_enabled = 11;
99+
optional int64 terminal_output_line_limit = 12;
99100
}
100101

101102
// Complete API Configuration message

src/core/controller/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class Controller {
135135
chatSettings,
136136
shellIntegrationTimeout,
137137
terminalReuseEnabled,
138+
terminalOutputLineLimit,
138139
defaultTerminalProfile,
139140
enableCheckpointsSetting,
140141
isNewUser,
@@ -171,6 +172,7 @@ export class Controller {
171172
chatSettings,
172173
shellIntegrationTimeout,
173174
terminalReuseEnabled ?? true,
175+
terminalOutputLineLimit ?? 500,
174176
defaultTerminalProfile ?? "default",
175177
enableCheckpointsSetting ?? true,
176178
task,
@@ -959,6 +961,7 @@ export class Controller {
959961
defaultTerminalProfile,
960962
isNewUser,
961963
mcpResponsesCollapsed,
964+
terminalOutputLineLimit,
962965
} = await getAllExtensionState(this.context)
963966

964967
const localClineRulesToggles =
@@ -1006,6 +1009,7 @@ export class Controller {
10061009
defaultTerminalProfile,
10071010
isNewUser,
10081011
mcpResponsesCollapsed,
1012+
terminalOutputLineLimit,
10091013
}
10101014
}
10111015

src/core/controller/state/updateSettings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ export async function updateSettings(controller: Controller, request: UpdateSett
7474
await controller.context.globalState.update("terminalReuseEnabled", request.terminalReuseEnabled)
7575
}
7676

77+
// Update terminal output line limit
78+
if (request.terminalOutputLineLimit !== undefined) {
79+
await controller.context.globalState.update("terminalOutputLineLimit", Number(request.terminalOutputLineLimit))
80+
}
81+
7782
// Post updated state to webview
7883
await controller.postStateToWebview()
7984

src/core/storage/state-keys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export type GlobalStateKey =
6969
| "terminalReuseEnabled"
7070
| "defaultTerminalProfile"
7171
| "isNewUser"
72+
| "terminalOutputLineLimit"
7273
| "mcpRichDisplayEnabled"
7374

7475
export type LocalStateKey =

src/core/storage/state.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
238238
mcpResponsesCollapsedRaw,
239239
globalWorkflowToggles,
240240
terminalReuseEnabled,
241+
terminalOutputLineLimit,
241242
defaultTerminalProfile,
242243
] = await Promise.all([
243244
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
@@ -303,6 +304,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
303304
getGlobalState(context, "mcpResponsesCollapsed") as Promise<boolean | undefined>,
304305
getGlobalState(context, "globalWorkflowToggles") as Promise<ClineRulesToggles | undefined>,
305306
getGlobalState(context, "terminalReuseEnabled") as Promise<boolean | undefined>,
307+
getGlobalState(context, "terminalOutputLineLimit") as Promise<number | undefined>,
306308
getGlobalState(context, "defaultTerminalProfile") as Promise<string | undefined>,
307309
])
308310

@@ -501,6 +503,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
501503
enableCheckpointsSetting: enableCheckpointsSetting,
502504
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
503505
terminalReuseEnabled: terminalReuseEnabled ?? true,
506+
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
504507
defaultTerminalProfile: defaultTerminalProfile ?? "default",
505508
globalWorkflowToggles: globalWorkflowToggles || {},
506509
}

src/core/task/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export class Task {
204204
chatSettings: ChatSettings,
205205
shellIntegrationTimeout: number,
206206
terminalReuseEnabled: boolean,
207+
terminalOutputLineLimit: number,
207208
defaultTerminalProfile: string,
208209
enableCheckpointsSetting: boolean,
209210
task?: string,
@@ -224,6 +225,7 @@ export class Task {
224225
this.terminalManager = new TerminalManager()
225226
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
226227
this.terminalManager.setTerminalReuseEnabled(terminalReuseEnabled ?? true)
228+
this.terminalManager.setTerminalOutputLineLimit(terminalOutputLineLimit)
227229
this.terminalManager.setDefaultTerminalProfile(defaultTerminalProfile)
228230
this.urlContentFetcher = new UrlContentFetcher(context)
229231
this.browserSession = new BrowserSession(context, browserSettings)
@@ -1477,9 +1479,9 @@ export class Task {
14771479
chunkTimer = setTimeout(async () => await flushBuffer(), CHUNK_DEBOUNCE_MS)
14781480
}
14791481

1480-
let result = ""
1482+
const outputLines: string[] = []
14811483
process.on("line", async (line) => {
1482-
result += line + "\n"
1484+
outputLines.push(line)
14831485

14841486
if (!didContinue) {
14851487
outputBuffer.push(line)
@@ -1521,7 +1523,7 @@ export class Task {
15211523
// grouping command_output messages despite any gaps anyways)
15221524
await setTimeoutPromise(50)
15231525

1524-
result = result.trim()
1526+
let result = this.terminalManager.processOutput(outputLines)
15251527

15261528
if (userFeedback) {
15271529
await this.say("user_feedback", userFeedback.text, userFeedback.images, userFeedback.files)

src/integrations/terminal/TerminalManager.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class TerminalManager {
9696
private disposables: vscode.Disposable[] = []
9797
private shellIntegrationTimeout: number = 4000
9898
private terminalReuseEnabled: boolean = true
99+
private terminalOutputLineLimit: number = 500
99100
private defaultTerminalProfile: string = "default"
100101

101102
constructor() {
@@ -327,6 +328,20 @@ export class TerminalManager {
327328
this.terminalReuseEnabled = enabled
328329
}
329330

331+
setTerminalOutputLineLimit(limit: number): void {
332+
this.terminalOutputLineLimit = limit
333+
}
334+
335+
public processOutput(outputLines: string[]): string {
336+
if (outputLines.length > this.terminalOutputLineLimit) {
337+
const halfLimit = Math.floor(this.terminalOutputLineLimit / 2)
338+
const start = outputLines.slice(0, halfLimit)
339+
const end = outputLines.slice(outputLines.length - halfLimit)
340+
return `${start.join("\n")}\n... (output truncated) ...\n${end.join("\n")}`.trim()
341+
}
342+
return outputLines.join("\n").trim()
343+
}
344+
330345
setDefaultTerminalProfile(profileId: string): { closedCount: number; busyTerminals: TerminalInfo[] } {
331346
// Only handle terminal change if profile actually changed
332347
if (this.defaultTerminalProfile === profileId) {

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface ExtensionState {
9191
telemetrySetting: TelemetrySetting
9292
shellIntegrationTimeout: number
9393
terminalReuseEnabled?: boolean
94+
terminalOutputLineLimit: number
9495
defaultTerminalProfile?: string
9596
uriScheme?: string
9697
userInfo?: {

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
130130
setMcpRichDisplayEnabled,
131131
shellIntegrationTimeout,
132132
setShellIntegrationTimeout,
133+
terminalOutputLineLimit,
134+
setTerminalOutputLineLimit,
133135
terminalReuseEnabled,
134136
setTerminalReuseEnabled,
135137
defaultTerminalProfile,
@@ -151,6 +153,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
151153
chatSettings,
152154
shellIntegrationTimeout,
153155
terminalReuseEnabled,
156+
terminalOutputLineLimit,
154157
defaultTerminalProfile,
155158
})
156159
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
@@ -194,6 +197,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
194197
? convertApiConfigurationToProtoApiConfiguration(apiConfigurationToSubmit)
195198
: undefined,
196199
chatSettings: chatSettings ? convertChatSettingsToProtoChatSettings(chatSettings) : undefined,
200+
terminalOutputLineLimit,
197201
}),
198202
)
199203

@@ -211,10 +215,12 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
211215
planActSeparateModelsSetting,
212216
enableCheckpointsSetting,
213217
mcpMarketplaceEnabled,
218+
mcpRichDisplayEnabled,
214219
mcpResponsesCollapsed,
215220
chatSettings,
216221
shellIntegrationTimeout,
217222
terminalReuseEnabled,
223+
terminalOutputLineLimit,
218224
defaultTerminalProfile,
219225
}
220226
} catch (error) {
@@ -244,6 +250,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
244250
mcpResponsesCollapsed !== originalState.current.mcpResponsesCollapsed ||
245251
JSON.stringify(chatSettings) !== JSON.stringify(originalState.current.chatSettings) ||
246252
shellIntegrationTimeout !== originalState.current.shellIntegrationTimeout ||
253+
terminalOutputLineLimit !== originalState.current.terminalOutputLineLimit ||
247254
terminalReuseEnabled !== originalState.current.terminalReuseEnabled ||
248255
defaultTerminalProfile !== originalState.current.defaultTerminalProfile
249256

@@ -259,6 +266,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
259266
chatSettings,
260267
shellIntegrationTimeout,
261268
terminalReuseEnabled,
269+
terminalOutputLineLimit,
262270
defaultTerminalProfile,
263271
])
264272

@@ -300,6 +308,9 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
300308
if (typeof setShellIntegrationTimeout === "function") {
301309
setShellIntegrationTimeout(originalState.current.shellIntegrationTimeout)
302310
}
311+
if (typeof setTerminalOutputLineLimit === "function") {
312+
setTerminalOutputLineLimit(originalState.current.terminalOutputLineLimit)
313+
}
303314
if (typeof setTerminalReuseEnabled === "function") {
304315
setTerminalReuseEnabled(originalState.current.terminalReuseEnabled ?? true)
305316
}

0 commit comments

Comments
 (0)