fix: improve terminal output capture for first command in new terminals

- Add 200ms delay for first command in newly created terminals to ensure shell integration is ready
- Improve stream data handling to recover from missing shell integration markers
- Track whether terminal is newly created and if first command has been executed
- Add fallback logic to treat accumulated data as output if markers aren't found after 500 chars

This fixes the race condition where the first command's output might not be captured
properly in freshly created terminals, especially with chained commands.

Fixes #9019
This commit is contained in:
Roo Code 2025-11-04 13:25:20 +00:00
parent 8e4b145681
commit 2962282b4f
2 changed files with 35 additions and 2 deletions

View file

@ -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,18 @@ 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
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)
})

View file

@ -158,6 +158,7 @@ export class TerminalProcess extends BaseTerminalProcess {
let preOutput = ""
let commandOutputStarted = false
let streamDataReceived = false
/*
* 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) {
streamDataReceived = true
// Check for command output start marker
if (!commandOutputStarted) {
preOutput += data
@ -182,7 +185,24 @@ 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 chunk of data, if we don't see markers yet,
// wait a bit more to see if they arrive in the next chunk
if (!streamDataReceived && preOutput.length < 100) {
continue
}
// If we have accumulated enough preOutput without finding markers,
// treat it as command output to avoid losing data
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
}
}
}