From e3adee4f158f44a0d6ae6a81d586caf7308ee44f Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Sun, 9 Mar 2025 20:38:31 -0700 Subject: [PATCH] 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 --- src/core/Cline.ts | 11 +++++- src/integrations/terminal/Terminal.ts | 38 ++++++++++++++----- src/integrations/terminal/TerminalRegistry.ts | 29 +++++++++++++- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 4a93acec5c..08831e2d66 100644 --- a/src/core/Cline.ts +++ b/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 diff --git a/src/integrations/terminal/Terminal.ts b/src/integrations/terminal/Terminal.ts index 40e2f8cca5..e768d79397 100644 --- a/src/integrations/terminal/Terminal.ts +++ b/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 diff --git a/src/integrations/terminal/TerminalRegistry.ts b/src/integrations/terminal/TerminalRegistry.ts index fd574e43fd..589c3cc345 100644 --- a/src/integrations/terminal/TerminalRegistry.ts +++ b/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 = []