From 25e46a244d5d2bf552f2fac2221532e7e998738f Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Fri, 7 Mar 2025 11:51:32 -0800 Subject: [PATCH] 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 --- src/core/Cline.ts | 50 +++++++++++++------- src/integrations/terminal/Terminal.ts | 43 +++++++++++++++-- src/integrations/terminal/TerminalProcess.ts | 11 +++++ 3 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 79faedb48a..15116387fa 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -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() - 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}` + }) + } } } diff --git a/src/integrations/terminal/Terminal.ts b/src/integrations/terminal/Terminal.ts index 1132eb55cc..a483fb1d2f 100644 --- a/src/integrations/terminal/Terminal.ts +++ b/src/integrations/terminal/Terminal.ts @@ -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 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 diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index cc36b08b78..a0f6928e0d 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -38,6 +38,7 @@ export class TerminalProcess extends EventEmitter { 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 { 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 { 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 {