Browse Source

fix: allow background terminals to broadcast output across tasks

Fix issue where background processes (like compilers) couldn't broadcast their
output to new tasks after the launching task was closed. Previously commit
851a4cd prevented terminals from responding to any task except the one that
started them.

The fix allows background terminals (taskId undefined) to act as broadcast
sources that can update any task through getEnvironmentDetails, while still
maintaining proper isolation for task-specific terminals. This enables common
workflows where:

1. A task launches a background compiler
2. That task is closed and a new task is started
3. The new task can still receive compiler errors when making changes

This gives us the best of both worlds:
- Task isolation: Active tasks only see their own terminal output
- Background broadcasting: Background processes can inform any task that needs
  their output

Signed-off-by: Eric Wheeler <[email protected]>
Eric Wheeler 11 months ago
parent
commit
e3adee4f15

+ 9 - 2
src/core/Cline.ts

@@ -3503,8 +3503,15 @@ export class Cline {
 			details += "\n(No open tabs)"
 		}
 
-		const busyTerminals = TerminalRegistry.getTerminals(true, this.taskId)
-		const inactiveTerminals = TerminalRegistry.getTerminals(false, this.taskId)
+		// Get task-specific and background terminals
+		const busyTerminals = [
+			...TerminalRegistry.getTerminals(true, this.taskId),
+			...TerminalRegistry.getBackgroundTerminals(true),
+		]
+		const inactiveTerminals = [
+			...TerminalRegistry.getTerminals(false, this.taskId),
+			...TerminalRegistry.getBackgroundTerminals(false),
+		]
 
 		if (busyTerminals.length > 0 && this.didEditFile) {
 			await delay(300) // delay after saving file to let terminals catch up

+ 28 - 10
src/integrations/terminal/Terminal.ts

@@ -75,6 +75,7 @@ export class Terminal {
 	 */
 	public shellExecutionComplete(exitDetails: ExitCodeDetails): void {
 		this.running = false
+		this.busy = false
 
 		if (this.process) {
 			// Add to the front of the queue (most recent first)
@@ -106,16 +107,7 @@ export class Terminal {
 	 * or don't belong to the current task
 	 */
 	public cleanCompletedProcessQueue(): void {
-		// If this terminal has no task ID, it's not associated with any active task
-		// In this case, we should remove all processes to prevent their output from appearing
-		// in any task's context
-		if (this.taskId === undefined) {
-			this.completedProcesses = []
-			return
-		}
-
-		// If the terminal is associated with a task, keep only processes with unretrieved output
-		// This ensures that when a task is active, it only sees output from its own processes
+		// Keep only processes with unretrieved output
 		this.completedProcesses = this.completedProcesses.filter((process) => process.hasUnretrievedOutput())
 	}
 
@@ -129,6 +121,32 @@ export class Terminal {
 		return [...this.completedProcesses]
 	}
 
+	/**
+	 * Gets all unretrieved output from both active and completed processes
+	 * @returns Combined unretrieved output from all processes
+	 */
+	public getUnretrievedOutput(): string {
+		let output = ""
+
+		// First check completed processes to maintain chronological order
+		for (const process of this.completedProcesses) {
+			const processOutput = process.getUnretrievedOutput()
+			if (processOutput) {
+				output += processOutput
+			}
+		}
+
+		// Then check active process for most recent output
+		const activeOutput = this.process?.getUnretrievedOutput()
+		if (activeOutput) {
+			output += activeOutput
+		}
+
+		this.cleanCompletedProcessQueue()
+
+		return output
+	}
+
 	public runCommand(command: string): TerminalProcessResultPromise {
 		this.busy = true
 

+ 28 - 1
src/integrations/terminal/TerminalRegistry.ts

@@ -153,7 +153,7 @@ export class TerminalRegistry {
 		if (!terminal) {
 			return ""
 		}
-		return terminal.process ? terminal.process.getUnretrievedOutput() : ""
+		return terminal.getUnretrievedOutput()
 	}
 
 	/**
@@ -190,6 +190,33 @@ export class TerminalRegistry {
 		})
 	}
 
+	/**
+	 * Gets background terminals (taskId undefined) that have unretrieved output or are still running
+	 * @param busy Whether to get busy or non-busy terminals
+	 * @returns Array of Terminal objects
+	 */
+	/**
+	 * Gets background terminals (taskId undefined) filtered by busy state
+	 * @param busy Whether to get busy or non-busy terminals
+	 * @returns Array of Terminal objects
+	 */
+	static getBackgroundTerminals(busy?: boolean): Terminal[] {
+		return this.getAllTerminals().filter((t) => {
+			// Only get background terminals (taskId undefined)
+			if (t.taskId !== undefined) {
+				return false
+			}
+
+			// If busy is undefined, return all background terminals
+			if (busy === undefined) {
+				return t.getProcessesWithOutput().length > 0 || t.process?.hasUnretrievedOutput()
+			} else {
+				// Filter by busy state
+				return t.busy === busy
+			}
+		})
+	}
+
 	static cleanup() {
 		this.disposables.forEach((disposable) => disposable.dispose())
 		this.disposables = []