Compare commits

...

2 Commits

Author SHA1 Message Date
Elephant Lumps 1e86882603 changeset 2025-04-30 16:30:25 -05:00
Elephant Lumps 44c560c6b6 add terminal connection timeout 2025-04-30 16:17:14 -05:00
11 changed files with 156 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
+26 -2
View File
@@ -136,8 +136,14 @@ export class Controller {
async initTask(task?: string, images?: string[], historyItem?: HistoryItem) {
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
await getAllExtensionState(this.context)
const {
apiConfiguration,
customInstructions,
autoApprovalSettings,
browserSettings,
chatSettings,
shellIntegrationTimeout,
} = await getAllExtensionState(this.context)
if (autoApprovalSettings) {
const updatedAutoApprovalSettings = {
@@ -159,6 +165,7 @@ export class Controller {
autoApprovalSettings,
browserSettings,
chatSettings,
shellIntegrationTimeout,
customInstructions,
task,
images,
@@ -784,6 +791,21 @@ export class Controller {
}
break
}
case "updateTerminalConnectionTimeout": {
if (message.shellIntegrationTimeout !== undefined) {
const timeout = message.shellIntegrationTimeout
if (typeof timeout === "number" && !isNaN(timeout) && timeout > 0) {
await updateGlobalState(this.context, "shellIntegrationTimeout", timeout)
await this.postStateToWebview()
} else {
console.warn(
`Invalid shell integration timeout value received: ${timeout}. ` + `Expected a positive number.`,
)
}
}
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1769,6 +1791,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
telemetrySetting,
planActSeparateModelsSetting,
globalClineRulesToggles,
shellIntegrationTimeout,
} = await getAllExtensionState(this.context)
const localClineRulesToggles =
@@ -1798,6 +1821,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
vscMachineId: vscode.env.machineId,
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},
shellIntegrationTimeout,
}
}
+1
View File
@@ -76,5 +76,6 @@ export type GlobalStateKey =
| "planActSeparateModelsSetting"
| "favoritedModelIds"
| "requestTimeoutMs"
| "shellIntegrationTimeout"
export type LocalStateKey = "localClineRulesToggles"
+3
View File
@@ -126,6 +126,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
favoritedModelIds,
globalClineRulesToggles,
requestTimeoutMs,
shellIntegrationTimeout,
] = await Promise.all([
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
@@ -200,6 +201,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
getGlobalState(context, "requestTimeoutMs") as Promise<number | undefined>,
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
])
let apiProvider: ApiProvider
@@ -319,6 +321,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
mcpMarketplaceEnabled,
telemetrySetting: telemetrySetting || "unset",
planActSeparateModelsSetting,
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
}
}
+2
View File
@@ -173,6 +173,7 @@ export class Task {
autoApprovalSettings: AutoApprovalSettings,
browserSettings: BrowserSettings,
chatSettings: ChatSettings,
shellIntegrationTimeout: number,
customInstructions?: string,
task?: string,
images?: string[],
@@ -191,6 +192,7 @@ export class Task {
console.error("Failed to initialize ClineIgnoreController:", error)
})
this.terminalManager = new TerminalManager()
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
this.urlContentFetcher = new UrlContentFetcher(context)
this.browserSession = new BrowserSession(context, browserSettings)
this.contextManager = new ContextManager()
+29 -7
View File
@@ -39,7 +39,7 @@ const terminalManager = new TerminalManager(context);
const process = terminalManager.runCommand('npm install', '/path/to/project');
process.on('line', (line) => {
console.log(line);
console.log(line);
});
// To wait for the process to complete naturally:
@@ -93,6 +93,7 @@ export class TerminalManager {
private terminalIds: Set<number> = new Set()
private processes: Map<number, TerminalProcess> = new Map()
private disposables: vscode.Disposable[] = []
private shellIntegrationTimeout: number = 4000
constructor() {
let disposable: vscode.Disposable | undefined
@@ -144,13 +145,30 @@ export class TerminalManager {
process.run(terminalInfo.terminal, command)
} else {
// docs recommend waiting 3s for shell integration to activate
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, { timeout: 4000 }).finally(() => {
const existingProcess = this.processes.get(terminalInfo.id)
if (existingProcess && existingProcess.waitForShellIntegration) {
existingProcess.waitForShellIntegration = false
existingProcess.run(terminalInfo.terminal, command)
}
console.log(
`[TerminalManager Test] Waiting for shell integration for terminal ${terminalInfo.id} with timeout ${this.shellIntegrationTimeout}ms`,
)
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, {
timeout: this.shellIntegrationTimeout,
})
.then(() => {
console.log(
`[TerminalManager Test] Shell integration activated for terminal ${terminalInfo.id} within timeout.`,
)
})
.catch((err) => {
console.warn(
`[TerminalManager Test] Shell integration timed out or failed for terminal ${terminalInfo.id}: ${err.message}`,
)
})
.finally(() => {
console.log(`[TerminalManager Test] Proceeding with command execution for terminal ${terminalInfo.id}.`)
const existingProcess = this.processes.get(terminalInfo.id)
if (existingProcess && existingProcess.waitForShellIntegration) {
existingProcess.waitForShellIntegration = false
existingProcess.run(terminalInfo.terminal, command)
}
})
}
return mergePromise(process, promise)
@@ -219,4 +237,8 @@ export class TerminalManager {
this.disposables.forEach((disposable) => disposable.dispose())
this.disposables = []
}
setShellIntegrationTimeout(timeout: number): void {
this.shellIntegrationTimeout = timeout
}
}
+1
View File
@@ -132,6 +132,7 @@ export interface ExtensionState {
shouldShowAnnouncement: boolean
taskHistory: HistoryItem[]
telemetrySetting: TelemetrySetting
shellIntegrationTimeout: number
uriScheme?: string
userInfo?: {
displayName: string | null
+2
View File
@@ -71,6 +71,7 @@ export interface WebviewMessage {
| "toggleClineRule"
| "deleteClineRule"
| "copyToClipboard"
| "updateTerminalConnectionTimeout"
// | "relaunchChromeDebugMode"
text?: string
@@ -121,6 +122,7 @@ export interface WebviewMessage {
filename?: string
offset?: number
shellIntegrationTimeout?: number
}
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
@@ -9,6 +9,7 @@ import { TabButton } from "../mcp/configuration/McpConfigurationView"
import { useEvent } from "react-use"
import { ExtensionMessage } from "@shared/ExtensionMessage"
import BrowserSettingsSection from "./BrowserSettingsSection"
import TerminalSettingsSection from "./TerminalSettingsSection"
const { IS_DEV } = process.env
@@ -240,6 +241,9 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
{/* Browser Settings Section */}
<BrowserSettingsSection />
{/* Terminal Settings Section */}
<TerminalSettingsSection />
<div className="mt-auto pr-2 flex justify-center">
<SettingsButton
onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}
@@ -0,0 +1,76 @@
import React, { useState } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
export const TerminalSettingsSection: React.FC = () => {
const { shellIntegrationTimeout, setShellIntegrationTimeout } = useExtensionState()
const [inputValue, setInputValue] = useState((shellIntegrationTimeout / 1000).toString())
const [inputError, setInputError] = useState<string | null>(null)
const handleTimeoutChange = (event: Event) => {
const target = event.target as HTMLInputElement
const value = target.value
setInputValue(value)
const seconds = parseFloat(value)
if (isNaN(seconds) || seconds <= 0) {
setInputError("Please enter a positive number")
return
}
setInputError(null)
const timeout = Math.round(seconds * 1000) // Convert to milliseconds
// Update local state
setShellIntegrationTimeout(timeout)
// Send to extension
vscode.postMessage({
type: "updateTerminalConnectionTimeout",
shellIntegrationTimeout: timeout,
})
}
const handleInputBlur = () => {
// If there was an error, reset the input to the current valid value
if (inputError) {
setInputValue((shellIntegrationTimeout / 1000).toString())
setInputError(null)
}
}
return (
<div
id="terminal-settings-section"
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Terminal Settings</h3>
<div style={{ marginBottom: 15 }}>
<div style={{ marginBottom: 8 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Shell integration timeout (seconds)
</label>
<div style={{ display: "flex", alignItems: "center" }}>
<VSCodeTextField
style={{ width: "100%" }}
value={inputValue}
placeholder="Enter timeout in seconds"
onChange={(event) => handleTimeoutChange(event as Event)}
onBlur={handleInputBlur}
/>
</div>
{inputError && (
<div style={{ color: "var(--vscode-errorForeground)", fontSize: "12px", marginTop: 5 }}>{inputError}</div>
)}
</div>
<p style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)", margin: 0 }}>
Set how long Cline waits for shell integration to activate before executing commands. Increase this value if
you experience terminal connection timeouts.
</p>
</div>
</div>
)
}
export default TerminalSettingsSection
@@ -39,6 +39,7 @@ interface ExtensionStateContextType extends ExtensionState {
setTelemetrySetting: (value: TelemetrySetting) => void
setShowAnnouncement: (value: boolean) => void
setPlanActSeparateModelsSetting: (value: boolean) => void
setShellIntegrationTimeout: (value: number) => void
setMcpServers: (value: McpServer[]) => void
// Navigation
@@ -69,6 +70,7 @@ export const ExtensionStateContextProvider: React.FC<{
planActSeparateModelsSetting: true,
globalClineRulesToggles: {},
localClineRulesToggles: {},
shellIntegrationTimeout: 4000, // default timeout for shell integration
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
@@ -242,6 +244,11 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
shouldShowAnnouncement: value,
})),
setShellIntegrationTimeout: (value) =>
setState((prevState) => ({
...prevState,
shellIntegrationTimeout: value,
})),
setMcpServers: (mcpServers: McpServer[]) => setMcpServers(mcpServers),
setShowMcp,
setMcpTab,