Compare commits

...

11 Commits

Author SHA1 Message Date
0xtoshii 3e953e9196 smol 2025-06-13 15:43:49 -07:00
0xtoshii 29e2f716da format 2025-06-13 14:56:43 -07:00
Toshii 29e62c8b78 Merge branch 'main' into to/terminal-setting 2025-06-13 14:45:15 -07:00
Toshii 1219ad945b Merge branch 'main' into to/terminal-setting 2025-06-11 11:16:40 -07:00
0xtoshii 61e4fa229b nit 2025-06-10 18:55:10 -07:00
0xtoshii 955042fc78 changeset 2025-06-10 18:44:11 -07:00
0xtoshii 5da843ac8e settings stuff 2025-06-10 18:17:15 -07:00
0xtoshii 505ce650ce base 3 2025-06-10 18:00:02 -07:00
0xtoshii bd49f142c1 grpc 2025-06-10 16:06:52 -07:00
0xtoshii f58844ce13 grpc 2025-06-10 15:33:17 -07:00
0xtoshii 61d6f49110 base 2025-06-10 15:04:40 -07:00
13 changed files with 98 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
add ability to constrain size of terminal output
+1
View File
@@ -96,6 +96,7 @@ message UpdateSettingsRequest {
optional bool terminal_reuse_enabled = 9;
optional bool mcp_responses_collapsed = 10;
optional bool mcp_rich_display_enabled = 11;
optional int64 terminal_output_line_limit = 12;
}
// Complete API Configuration message
+4
View File
@@ -135,6 +135,7 @@ export class Controller {
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled,
terminalOutputLineLimit,
defaultTerminalProfile,
enableCheckpointsSetting,
isNewUser,
@@ -171,6 +172,7 @@ export class Controller {
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled ?? true,
terminalOutputLineLimit ?? 500,
defaultTerminalProfile ?? "default",
enableCheckpointsSetting ?? true,
task,
@@ -959,6 +961,7 @@ export class Controller {
defaultTerminalProfile,
isNewUser,
mcpResponsesCollapsed,
terminalOutputLineLimit,
} = await getAllExtensionState(this.context)
const localClineRulesToggles =
@@ -1006,6 +1009,7 @@ export class Controller {
defaultTerminalProfile,
isNewUser,
mcpResponsesCollapsed,
terminalOutputLineLimit,
}
}
@@ -74,6 +74,11 @@ export async function updateSettings(controller: Controller, request: UpdateSett
await controller.context.globalState.update("terminalReuseEnabled", request.terminalReuseEnabled)
}
// Update terminal output line limit
if (request.terminalOutputLineLimit !== undefined) {
await controller.context.globalState.update("terminalOutputLineLimit", Number(request.terminalOutputLineLimit))
}
// Post updated state to webview
await controller.postStateToWebview()
+1
View File
@@ -69,6 +69,7 @@ export type GlobalStateKey =
| "terminalReuseEnabled"
| "defaultTerminalProfile"
| "isNewUser"
| "terminalOutputLineLimit"
| "mcpRichDisplayEnabled"
export type LocalStateKey =
+3
View File
@@ -238,6 +238,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
mcpResponsesCollapsedRaw,
globalWorkflowToggles,
terminalReuseEnabled,
terminalOutputLineLimit,
defaultTerminalProfile,
] = await Promise.all([
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
@@ -303,6 +304,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "mcpResponsesCollapsed") as Promise<boolean | undefined>,
getGlobalState(context, "globalWorkflowToggles") as Promise<ClineRulesToggles | undefined>,
getGlobalState(context, "terminalReuseEnabled") as Promise<boolean | undefined>,
getGlobalState(context, "terminalOutputLineLimit") as Promise<number | undefined>,
getGlobalState(context, "defaultTerminalProfile") as Promise<string | undefined>,
])
@@ -501,6 +503,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
enableCheckpointsSetting: enableCheckpointsSetting,
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
terminalReuseEnabled: terminalReuseEnabled ?? true,
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
defaultTerminalProfile: defaultTerminalProfile ?? "default",
globalWorkflowToggles: globalWorkflowToggles || {},
}
+5 -3
View File
@@ -204,6 +204,7 @@ export class Task {
chatSettings: ChatSettings,
shellIntegrationTimeout: number,
terminalReuseEnabled: boolean,
terminalOutputLineLimit: number,
defaultTerminalProfile: string,
enableCheckpointsSetting: boolean,
task?: string,
@@ -224,6 +225,7 @@ export class Task {
this.terminalManager = new TerminalManager()
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
this.terminalManager.setTerminalReuseEnabled(terminalReuseEnabled ?? true)
this.terminalManager.setTerminalOutputLineLimit(terminalOutputLineLimit)
this.terminalManager.setDefaultTerminalProfile(defaultTerminalProfile)
this.urlContentFetcher = new UrlContentFetcher(context)
this.browserSession = new BrowserSession(context, browserSettings)
@@ -1477,9 +1479,9 @@ export class Task {
chunkTimer = setTimeout(async () => await flushBuffer(), CHUNK_DEBOUNCE_MS)
}
let result = ""
const outputLines: string[] = []
process.on("line", async (line) => {
result += line + "\n"
outputLines.push(line)
if (!didContinue) {
outputBuffer.push(line)
@@ -1521,7 +1523,7 @@ export class Task {
// grouping command_output messages despite any gaps anyways)
await setTimeoutPromise(50)
result = result.trim()
let result = this.terminalManager.processOutput(outputLines)
if (userFeedback) {
await this.say("user_feedback", userFeedback.text, userFeedback.images, userFeedback.files)
@@ -96,6 +96,7 @@ export class TerminalManager {
private disposables: vscode.Disposable[] = []
private shellIntegrationTimeout: number = 4000
private terminalReuseEnabled: boolean = true
private terminalOutputLineLimit: number = 500
private defaultTerminalProfile: string = "default"
constructor() {
@@ -327,6 +328,20 @@ export class TerminalManager {
this.terminalReuseEnabled = enabled
}
setTerminalOutputLineLimit(limit: number): void {
this.terminalOutputLineLimit = limit
}
public processOutput(outputLines: string[]): string {
if (outputLines.length > this.terminalOutputLineLimit) {
const halfLimit = Math.floor(this.terminalOutputLineLimit / 2)
const start = outputLines.slice(0, halfLimit)
const end = outputLines.slice(outputLines.length - halfLimit)
return `${start.join("\n")}\n... (output truncated) ...\n${end.join("\n")}`.trim()
}
return outputLines.join("\n").trim()
}
setDefaultTerminalProfile(profileId: string): { closedCount: number; busyTerminals: TerminalInfo[] } {
// Only handle terminal change if profile actually changed
if (this.defaultTerminalProfile === profileId) {
+1
View File
@@ -91,6 +91,7 @@ export interface ExtensionState {
telemetrySetting: TelemetrySetting
shellIntegrationTimeout: number
terminalReuseEnabled?: boolean
terminalOutputLineLimit: number
defaultTerminalProfile?: string
uriScheme?: string
userInfo?: {
@@ -130,6 +130,8 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
setMcpRichDisplayEnabled,
shellIntegrationTimeout,
setShellIntegrationTimeout,
terminalOutputLineLimit,
setTerminalOutputLineLimit,
terminalReuseEnabled,
setTerminalReuseEnabled,
defaultTerminalProfile,
@@ -151,6 +153,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled,
terminalOutputLineLimit,
defaultTerminalProfile,
})
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
@@ -194,6 +197,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
? convertApiConfigurationToProtoApiConfiguration(apiConfigurationToSubmit)
: undefined,
chatSettings: chatSettings ? convertChatSettingsToProtoChatSettings(chatSettings) : undefined,
terminalOutputLineLimit,
}),
)
@@ -211,10 +215,12 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
planActSeparateModelsSetting,
enableCheckpointsSetting,
mcpMarketplaceEnabled,
mcpRichDisplayEnabled,
mcpResponsesCollapsed,
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled,
terminalOutputLineLimit,
defaultTerminalProfile,
}
} catch (error) {
@@ -244,6 +250,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
mcpResponsesCollapsed !== originalState.current.mcpResponsesCollapsed ||
JSON.stringify(chatSettings) !== JSON.stringify(originalState.current.chatSettings) ||
shellIntegrationTimeout !== originalState.current.shellIntegrationTimeout ||
terminalOutputLineLimit !== originalState.current.terminalOutputLineLimit ||
terminalReuseEnabled !== originalState.current.terminalReuseEnabled ||
defaultTerminalProfile !== originalState.current.defaultTerminalProfile
@@ -259,6 +266,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled,
terminalOutputLineLimit,
defaultTerminalProfile,
])
@@ -300,6 +308,9 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
if (typeof setShellIntegrationTimeout === "function") {
setShellIntegrationTimeout(originalState.current.shellIntegrationTimeout)
}
if (typeof setTerminalOutputLineLimit === "function") {
setTerminalOutputLineLimit(originalState.current.terminalOutputLineLimit)
}
if (typeof setTerminalReuseEnabled === "function") {
setTerminalReuseEnabled(originalState.current.terminalReuseEnabled ?? true)
}
@@ -0,0 +1,38 @@
import React from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
const TerminalOutputLineLimitSlider: React.FC = () => {
const { terminalOutputLineLimit, setTerminalOutputLineLimit } = useExtensionState()
const handleSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(event.target.value, 10)
setTerminalOutputLineLimit(value)
}
return (
<div style={{ marginBottom: 15 }}>
<label htmlFor="terminal-output-limit" style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Terminal output limit
</label>
<div style={{ display: "flex", alignItems: "center" }}>
<input
type="range"
id="terminal-output-limit"
min="100"
max="5000"
step="100"
value={terminalOutputLineLimit ?? 500}
onChange={handleSliderChange}
style={{ flexGrow: 1, marginRight: "1rem" }}
/>
<span>{terminalOutputLineLimit ?? 500}</span>
</div>
<p style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)", margin: "5px 0 0 0" }}>
Maximum number of lines to include in terminal output when executing commands. When exceeded, lines will be
removed from the middle, saving tokens.
</p>
</div>
)
}
export default TerminalOutputLineLimitSlider
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react"
import { VSCodeTextField, VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import TerminalOutputLineLimitSlider from "./TerminalOutputLineLimitSlider"
import { StateServiceClient } from "../../services/grpc-client"
import { Int64, Int64Request } from "@shared/proto/common"
@@ -133,6 +134,7 @@ export const TerminalSettingsSection: React.FC = () => {
this if you experience issues with task lockout after a terminal command.
</p>
</div>
<TerminalOutputLineLimitSlider />
</div>
)
}
@@ -64,6 +64,7 @@ interface ExtensionStateContextType extends ExtensionState {
setMcpResponsesCollapsed: (value: boolean) => void
setShellIntegrationTimeout: (value: number) => void
setTerminalReuseEnabled: (value: boolean) => void
setTerminalOutputLineLimit: (value: number) => void
setDefaultTerminalProfile: (value: string) => void
setChatSettings: (value: ChatSettings) => void
setMcpServers: (value: McpServer[]) => void
@@ -194,6 +195,7 @@ export const ExtensionStateContextProvider: React.FC<{
globalWorkflowToggles: {},
shellIntegrationTimeout: 4000,
terminalReuseEnabled: true,
terminalOutputLineLimit: 500,
defaultTerminalProfile: "default",
isNewUser: false,
mcpResponsesCollapsed: false, // Default value (expanded), will be overwritten by extension state
@@ -752,6 +754,11 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
terminalReuseEnabled: value,
})),
setTerminalOutputLineLimit: (value) =>
setState((prevState) => ({
...prevState,
terminalOutputLineLimit: value,
})),
setDefaultTerminalProfile: (value) =>
setState((prevState) => ({
...prevState,