fix: terminal output not showing after command completion

Fix an issue where background running terminals that complete their
execution do not report the final output of their command. Previously,
output was reported while the command was active, but after termination
the remaining output was not provided within the 'inactive terminals'
section of environment details.

- Implement terminal process queue system to track completed processes
- Store command and output retrieval state per process
- Add helper methods to manage the process queue efficiently
- Update getEnvironmentDetails to properly display output from completed processes

Signed-off-by: Eric Wheeler <roo-code@z.ewheeler.org>
This commit is contained in:
Eric Wheeler 2025-03-07 11:51:32 -08:00
parent 24d8ef91ce
commit 25e46a244d
3 changed files with 84 additions and 20 deletions

View file

@ -3512,7 +3512,7 @@ export class Cline {
// terminals are cool, let's retrieve their output
terminalDetails += "\n\n# Actively Running Terminals"
for (const busyTerminal of busyTerminals) {
terminalDetails += `\n## Original command: \`${busyTerminal.lastCommand}\``
terminalDetails += `\n## Original command: \`${busyTerminal.getLastCommand()}\``
const newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id)
if (newOutput) {
terminalDetails += `\n### New Output\n${newOutput}`
@ -3521,24 +3521,40 @@ export class Cline {
}
}
}
// only show inactive terminals if there's output to show
if (inactiveTerminals.length > 0) {
const inactiveTerminalOutputs = new Map<number, string>()
for (const inactiveTerminal of inactiveTerminals) {
const newOutput = TerminalRegistry.getUnretrievedOutput(inactiveTerminal.id)
if (newOutput) {
inactiveTerminalOutputs.set(inactiveTerminal.id, newOutput)
}
}
if (inactiveTerminalOutputs.size > 0) {
terminalDetails += "\n\n# Inactive Terminals"
for (const [terminalId, newOutput] of inactiveTerminalOutputs) {
const inactiveTerminal = inactiveTerminals.find((t) => t.id === terminalId)
if (inactiveTerminal) {
terminalDetails += `\n## ${inactiveTerminal.lastCommand}`
terminalDetails += `\n### New Output\n${newOutput}`
// First check if any inactive terminals have completed processes with output
const terminalsWithOutput = inactiveTerminals.filter((terminal) => {
const completedProcesses = terminal.getProcessesWithOutput()
return completedProcesses.length > 0
})
// Only add the header if there are terminals with output
if (terminalsWithOutput.length > 0) {
terminalDetails += "\n\n# Inactive Terminals with Completed Process Output"
// Process each terminal with output
for (const inactiveTerminal of terminalsWithOutput) {
let terminalOutputs: string[] = []
// Get output from completed processes queue
const completedProcesses = inactiveTerminal.getProcessesWithOutput()
for (const process of completedProcesses) {
const output = process.getUnretrievedOutput()
if (output) {
terminalOutputs.push(`Command: \`${process.command}\`\n${output}`)
}
}
// Clean the queue after retrieving output
inactiveTerminal.cleanCompletedProcessQueue()
// Add this terminal's outputs to the details
if (terminalOutputs.length > 0) {
terminalDetails += `\n## Terminal ${inactiveTerminal.id}`
terminalOutputs.forEach((output, index) => {
terminalDetails += `\n### New Output\n${output}`
})
}
}
}

View file

@ -5,19 +5,18 @@ import { ExitCodeDetails, mergePromise, TerminalProcess, TerminalProcessResultPr
export class Terminal {
public terminal: vscode.Terminal
public busy: boolean
public lastCommand: string
public id: number
private stream?: AsyncIterable<string>
public running: boolean
private streamClosed: boolean
public process?: TerminalProcess
public taskId?: string
public completedProcesses: TerminalProcess[] = []
constructor(id: number, terminal: vscode.Terminal) {
this.id = id
this.terminal = terminal
this.busy = false
this.lastCommand = ""
this.running = false
this.streamClosed = false
}
@ -68,18 +67,56 @@ export class Terminal {
this.running = false
if (this.process) {
// Add to the front of the queue (most recent first)
if (this.process.hasUnretrievedOutput()) {
this.completedProcesses.unshift(this.process)
}
this.process.emit("shell_execution_complete", this.id, exitDetails)
this.process = undefined
}
}
/**
* Gets the last executed command
* @returns The last command string or empty string if none
*/
public getLastCommand(): string {
// Return the command from the active process or the most recent process in the queue
if (this.process) {
return this.process.command || ""
} else if (this.completedProcesses.length > 0) {
return this.completedProcesses[0].command || ""
}
return ""
}
/**
* Cleans the process queue by removing processes that no longer have unretrieved output
*/
public cleanCompletedProcessQueue(): void {
this.completedProcesses = this.completedProcesses.filter((process) => process.hasUnretrievedOutput())
}
/**
* Gets all processes with unretrieved output
* @returns Array of processes with unretrieved output
*/
public getProcessesWithOutput(): TerminalProcess[] {
// Clean the queue first to remove any processes without output
this.cleanCompletedProcessQueue()
return [...this.completedProcesses]
}
public runCommand(command: string): TerminalProcessResultPromise {
this.busy = true
this.lastCommand = command
// Create process immediately
const process = new TerminalProcess(this)
// Store the command on the process for reference
process.command = command
// Set process on terminal
this.process = process

View file

@ -38,6 +38,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
private fullOutput: string = ""
private lastRetrievedIndex: number = 0
isHot: boolean = false
command: string = ""
constructor(terminal: Terminal) {
super()
@ -155,6 +156,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
private hotTimer: NodeJS.Timeout | null = null
async run(command: string) {
this.command = command
const terminal = this.terminalInfo.terminal
if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
@ -332,6 +334,15 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
this.emit("continue")
}
/**
* Checks if this process has unretrieved output
* @returns true if there is output that hasn't been fully retrieved yet
*/
hasUnretrievedOutput(): boolean {
// If the process is still active or has unretrieved content, return true
return this.lastRetrievedIndex < this.fullOutput.length
}
// Returns complete lines with their carriage returns.
// The final line may lack a carriage return if the program didn't send one.
getUnretrievedOutput(): string {