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 <roo-code@z.ewheeler.org>
This commit is contained in:
Eric Wheeler 2025-03-09 20:38:31 -07:00
parent 0b4fa0b1d6
commit e3adee4f15
3 changed files with 65 additions and 13 deletions

View file

@ -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

View file

@ -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

View file

@ -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 = []