diff --git a/src/integrations/terminal/Terminal.ts b/src/integrations/terminal/Terminal.ts index 8bf2072f3d49..6f2cf98cccc8 100644 --- a/src/integrations/terminal/Terminal.ts +++ b/src/integrations/terminal/Terminal.ts @@ -9,6 +9,8 @@ import { mergePromise } from "./mergePromise" export class Terminal extends BaseTerminal { public terminal: vscode.Terminal + private isNewlyCreated: boolean = false + private firstCommandExecuted: boolean = false public cmdCounter: number = 0 @@ -19,6 +21,9 @@ export class Terminal extends BaseTerminal { const iconPath = new vscode.ThemeIcon("rocket") this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Roo Code", iconPath, env }) + // Mark if this is a newly created terminal + this.isNewlyCreated = terminal === undefined + if (Terminal.getTerminalZdotdir()) { ShellIntegrationManager.terminalTmpDirs.set(id, env.ZDOTDIR) } @@ -71,10 +76,24 @@ export class Terminal extends BaseTerminal { pWaitFor(() => this.terminal.shellIntegration !== undefined, { timeout: Terminal.getShellIntegrationTimeout(), }) - .then(() => { + .then(async () => { // Clean up temporary directory if shell integration is available, zsh did its job: ShellIntegrationManager.zshCleanupTmpDir(this.id) + // For newly created terminals on the first command, add a small delay + // to ensure the shell integration stream is fully ready. + // This addresses a race condition where the first command's output might not + // be captured properly, especially with chained commands like "ENV=hello && echo $ENV". + // The 200ms delay was chosen empirically to balance between: + // - Giving enough time for shell integration to fully initialize + // - Not adding noticeable latency to command execution + // This may need adjustment for slower systems or different shells. + if (this.isNewlyCreated && !this.firstCommandExecuted) { + console.log(`[Terminal ${this.id}] Adding delay for first command in new terminal`) + await new Promise((resolve) => setTimeout(resolve, 200)) + this.firstCommandExecuted = true + } + // Run the command in the terminal process.run(command) }) diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index eb0424fe8df6..2d142286de12 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -158,6 +158,7 @@ export class TerminalProcess extends BaseTerminalProcess { let preOutput = "" let commandOutputStarted = false + let chunksReceived = 0 /* * Extract clean output from raw accumulated output. FYI: @@ -171,6 +172,8 @@ export class TerminalProcess extends BaseTerminalProcess { // Process stream data for await (let data of stream) { + chunksReceived++ + // Check for command output start marker if (!commandOutputStarted) { preOutput += data @@ -182,7 +185,26 @@ export class TerminalProcess extends BaseTerminalProcess { this.fullOutput = "" // Reset fullOutput when command actually starts this.emit("line", "") // Trigger UI to proceed } else { - continue + // For the first few chunks, wait to see if markers arrive + // This handles cases where markers might be split across chunks + if (chunksReceived < 3 && preOutput.length < 100) { + continue + } + // If we have accumulated enough preOutput without finding markers, + // treat it as command output to avoid losing data + // 500 chars threshold chosen to balance between waiting for markers + // and not losing legitimate output from fast commands + if (preOutput.length > 500) { + console.warn( + `[Terminal Process] No start markers found after ${preOutput.length} chars, treating as output`, + ) + commandOutputStarted = true + data = preOutput + this.fullOutput = "" + this.emit("line", "") + } else { + continue + } } }