mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Try to prevent additional cases in which terminal commands lock the task UI
This commit is contained in:
parent
0a19fb547e
commit
eee7bbe104
2 changed files with 79 additions and 28 deletions
|
|
@ -82,7 +82,10 @@ declare module "vscode" {
|
|||
// https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794
|
||||
interface Window {
|
||||
onDidStartTerminalShellExecution?: (
|
||||
listener: (e: any) => any,
|
||||
listener: (e: {
|
||||
terminal: vscode.Terminal
|
||||
execution: { read(): AsyncIterable<string>; commandLine: { value: string } }
|
||||
}) => any,
|
||||
thisArgs?: any,
|
||||
disposables?: vscode.Disposable[],
|
||||
) => vscode.Disposable
|
||||
|
|
@ -203,57 +206,77 @@ export class TerminalManager {
|
|||
constructor() {
|
||||
let startDisposable: vscode.Disposable | undefined
|
||||
let endDisposable: vscode.Disposable | undefined
|
||||
|
||||
try {
|
||||
// onDidStartTerminalShellExecution
|
||||
startDisposable = (vscode.window as vscode.Window).onDidStartTerminalShellExecution?.(async (e) => {
|
||||
// Get a handle to the stream as early as possible:
|
||||
const stream = e?.execution.read()
|
||||
const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(e.terminal)
|
||||
if (stream && terminalInfo) {
|
||||
const process = this.processes.get(terminalInfo.id)
|
||||
if (process) {
|
||||
terminalInfo.stream = stream
|
||||
terminalInfo.running = true
|
||||
terminalInfo.streamClosed = false
|
||||
process.emit("stream_available", terminalInfo.id, stream)
|
||||
}
|
||||
} else {
|
||||
console.error("[TerminalManager] Stream failed, not registered for terminal")
|
||||
}
|
||||
|
||||
console.info("[TerminalManager] Shell execution started:", {
|
||||
console.info("[TerminalManager] shell execution started", {
|
||||
hasExecution: !!e?.execution,
|
||||
hasStream: !!stream,
|
||||
command: e?.execution?.commandLine?.value,
|
||||
terminalId: terminalInfo?.id,
|
||||
})
|
||||
|
||||
if (terminalInfo) {
|
||||
const process = this.processes.get(terminalInfo.id)
|
||||
|
||||
if (process) {
|
||||
if (stream) {
|
||||
terminalInfo.stream = stream
|
||||
terminalInfo.running = true
|
||||
terminalInfo.streamClosed = false
|
||||
console.log(`[TerminalManager] stream_available -> ${terminalInfo.id}`)
|
||||
process.emit("stream_available", terminalInfo.id, stream)
|
||||
} else {
|
||||
process.emit("stream_unavailable", terminalInfo.id)
|
||||
console.error(`[TerminalManager] stream_unavailable -> ${terminalInfo.id}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("[TerminalManager] terminalInfo not available")
|
||||
}
|
||||
})
|
||||
|
||||
// onDidEndTerminalShellExecution
|
||||
endDisposable = (vscode.window as vscode.Window).onDidEndTerminalShellExecution?.(async (e) => {
|
||||
const exitDetails = this.interpretExitCode(e?.exitCode)
|
||||
console.info("[TerminalManager] Shell execution ended:", {
|
||||
...exitDetails,
|
||||
})
|
||||
console.info("[TerminalManager] Shell execution ended:", { ...exitDetails })
|
||||
let emitted = false
|
||||
|
||||
// Signal completion to any waiting processes
|
||||
// Signal completion to any waiting processes.
|
||||
for (const id of this.terminalIds) {
|
||||
const info = TerminalRegistry.getTerminal(id)
|
||||
|
||||
if (info && info.terminal === e.terminal) {
|
||||
info.running = false
|
||||
const process = this.processes.get(id)
|
||||
|
||||
if (process) {
|
||||
console.log(`[TerminalManager] emitting shell_execution_complete -> ${id}`)
|
||||
emitted = true
|
||||
process.emit("shell_execution_complete", id, exitDetails)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!emitted) {
|
||||
console.log(`[TerminalManager#onDidStartTerminalShellExecution] no terminal found`)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[TerminalManager] Error setting up shell execution handlers:", error)
|
||||
console.error("[TerminalManager] failed to configure shell execution handlers", error)
|
||||
}
|
||||
|
||||
if (startDisposable) {
|
||||
this.disposables.push(startDisposable)
|
||||
}
|
||||
|
||||
if (endDisposable) {
|
||||
this.disposables.push(endDisposable)
|
||||
}
|
||||
|
|
@ -366,9 +389,6 @@ export class TerminalManager {
|
|||
}
|
||||
|
||||
disposeAll() {
|
||||
// for (const info of this.terminals) {
|
||||
// //info.terminal.dispose() // dont want to dispose terminals when task is aborted
|
||||
// }
|
||||
this.terminalIds.clear()
|
||||
this.processes.clear()
|
||||
this.disposables.forEach((disposable) => disposable.dispose())
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export interface TerminalProcessEvents {
|
|||
*/
|
||||
shell_execution_complete: [id: number, exitDetails: ExitCodeDetails]
|
||||
stream_available: [id: number, stream: AsyncIterable<string>]
|
||||
stream_unavailable: [id: number]
|
||||
/**
|
||||
* Emitted when an execution fails to emit a "line" event for a given period of time.
|
||||
* @param id The terminal ID
|
||||
|
|
@ -85,15 +86,24 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
|||
const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(terminal)
|
||||
|
||||
if (!terminalInfo) {
|
||||
console.error("[TerminalProcess] Terminal not found in registry")
|
||||
console.error("[TerminalProcess#run] terminal not found in registry")
|
||||
this.emit("no_shell_integration")
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
return
|
||||
}
|
||||
|
||||
// When executeCommand() is called, onDidStartTerminalShellExecution will fire in TerminalManager
|
||||
// which creates a new stream via execution.read() and emits 'stream_available'
|
||||
this.once("stream_unavailable", (id: number) => {
|
||||
if (id === terminalInfo.id) {
|
||||
console.error(`[TerminalProcess#run] stream_unavailable`)
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
}
|
||||
})
|
||||
|
||||
// When `executeCommand()` is called, `onDidStartTerminalShellExecution`
|
||||
// will fire in `TerminalManager` which creates a new stream via
|
||||
// `execution.read()` and emits `stream_available`.
|
||||
const streamAvailable = new Promise<AsyncIterable<string>>((resolve) => {
|
||||
this.once("stream_available", (id: number, stream: AsyncIterable<string>) => {
|
||||
if (id === terminalInfo.id) {
|
||||
|
|
@ -111,15 +121,35 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
|||
})
|
||||
})
|
||||
|
||||
// readLine needs to know if streamClosed, so store this for later
|
||||
// `readLine()` needs to know if streamClosed, so store this for later.
|
||||
// NOTE: This doesn't seem to be used anywhere.
|
||||
this.terminalInfo = terminalInfo
|
||||
|
||||
// Execute command
|
||||
// Execute command.
|
||||
terminal.shellIntegration.executeCommand(command)
|
||||
this.isHot = true
|
||||
|
||||
// Wait for stream to be available
|
||||
const stream = await streamAvailable
|
||||
// Wait for stream to be available.
|
||||
// const stream = await streamAvailable
|
||||
|
||||
// Wait for stream to be available.
|
||||
let stream: AsyncIterable<string>
|
||||
|
||||
try {
|
||||
stream = await Promise.race([
|
||||
streamAvailable,
|
||||
new Promise<never>((_, reject) => {
|
||||
setTimeout(
|
||||
() => reject(new Error("Timeout waiting for terminal stream to become available")),
|
||||
10_000,
|
||||
)
|
||||
}),
|
||||
])
|
||||
} catch (error) {
|
||||
console.error(`[TerminalProcess#run] timed out waiting for stream`)
|
||||
this.emit("stream_stalled", terminalInfo.id)
|
||||
stream = await streamAvailable
|
||||
}
|
||||
|
||||
let preOutput = ""
|
||||
let commandOutputStarted = false
|
||||
|
|
@ -256,6 +286,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
|||
}
|
||||
|
||||
public continue() {
|
||||
console.log(`[TerminalProcess#continue] flushing all`)
|
||||
this.flushAll()
|
||||
this.isListening = false
|
||||
this.removeAllListeners("line")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue