Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/integrations/terminal/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
})
Expand Down
24 changes: 23 additions & 1 deletion src/integrations/terminal/TerminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
}
}
}

Expand Down