mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: prevent command_output ask from blocking in cloud/headless environments (#9152)
This commit is contained in:
parent
b09d0a7640
commit
4cd5c9022e
3 changed files with 35 additions and 9 deletions
|
|
@ -46,7 +46,21 @@ export type ClineAsk = z.infer<typeof clineAskSchema>
|
|||
|
||||
// Needs classification:
|
||||
// - `followup`
|
||||
// - `command_output
|
||||
|
||||
/**
|
||||
* NonBlockingAsk
|
||||
*
|
||||
* Asks that should not block task execution. These are informational or optional
|
||||
* asks where the task can proceed even without an immediate user response.
|
||||
*/
|
||||
|
||||
export const nonBlockingAsks = ["command_output"] as const satisfies readonly ClineAsk[]
|
||||
|
||||
export type NonBlockingAsk = (typeof nonBlockingAsks)[number]
|
||||
|
||||
export function isNonBlockingAsk(ask: ClineAsk): ask is NonBlockingAsk {
|
||||
return (nonBlockingAsks as readonly ClineAsk[]).includes(ask)
|
||||
}
|
||||
|
||||
/**
|
||||
* IdleAsk
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
isIdleAsk,
|
||||
isInteractiveAsk,
|
||||
isResumableAsk,
|
||||
isNonBlockingAsk,
|
||||
QueuedMessage,
|
||||
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
MAX_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
|
|
@ -821,13 +822,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
// block (via the `pWaitFor`).
|
||||
const isBlocking = !(this.askResponse !== undefined || this.lastMessageTs !== askTs)
|
||||
const isMessageQueued = !this.messageQueueService.isEmpty()
|
||||
const isStatusMutable = !partial && isBlocking && !isMessageQueued
|
||||
// Non-blocking asks should not mutate task status since they don't actually block execution
|
||||
const isStatusMutable = !partial && isBlocking && !isMessageQueued && !isNonBlockingAsk(type)
|
||||
let statusMutationTimeouts: NodeJS.Timeout[] = []
|
||||
const statusMutationTimeout = 5_000
|
||||
|
||||
if (isStatusMutable) {
|
||||
console.log(`Task#ask will block -> type: ${type}`)
|
||||
|
||||
if (isInteractiveAsk(type)) {
|
||||
statusMutationTimeouts.push(
|
||||
setTimeout(() => {
|
||||
|
|
@ -879,14 +879,21 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
// the message if there's text/images.
|
||||
this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images)
|
||||
} else {
|
||||
// For other ask types (like followup), fulfill the ask
|
||||
// For other ask types (like followup or command_output), fulfill the ask
|
||||
// directly.
|
||||
this.setMessageResponse(message.text, message.images)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for askResponse to be set.
|
||||
// Non-blocking asks return immediately without waiting
|
||||
// The ask message is created in the UI, but the task doesn't wait for a response
|
||||
// This prevents blocking in cloud/headless environments
|
||||
if (isNonBlockingAsk(type)) {
|
||||
return { response: "yesButtonClicked" as ClineAskResponse, text: undefined, images: undefined }
|
||||
}
|
||||
|
||||
// Wait for askResponse to be set
|
||||
await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
|
||||
|
||||
if (this.lastMessageTs !== askTs) {
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ export async function executeCommand(
|
|||
let result: string = ""
|
||||
let exitDetails: ExitCodeDetails | undefined
|
||||
let shellIntegrationError: string | undefined
|
||||
let hasAskedForCommandOutput = false
|
||||
|
||||
const terminalProvider = terminalShellIntegrationDisabled ? "execa" : "vscode"
|
||||
const provider = await task.providerRef.deref()
|
||||
|
|
@ -195,10 +196,13 @@ export async function executeCommand(
|
|||
const status: CommandExecutionStatus = { executionId, status: "output", output: compressedOutput }
|
||||
provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
|
||||
|
||||
if (runInBackground) {
|
||||
if (runInBackground || hasAskedForCommandOutput) {
|
||||
return
|
||||
}
|
||||
|
||||
// Mark that we've asked to prevent multiple concurrent asks
|
||||
hasAskedForCommandOutput = true
|
||||
|
||||
try {
|
||||
const { response, text, images } = await task.ask("command_output", "")
|
||||
runInBackground = true
|
||||
|
|
@ -207,7 +211,9 @@ export async function executeCommand(
|
|||
message = { text, images }
|
||||
process.continue()
|
||||
}
|
||||
} catch (_error) {}
|
||||
} catch (_error) {
|
||||
// Silently handle ask errors (e.g., "Current ask promise was ignored")
|
||||
}
|
||||
},
|
||||
onCompleted: (output: string | undefined) => {
|
||||
result = Terminal.compressTerminalOutput(
|
||||
|
|
@ -220,7 +226,6 @@ export async function executeCommand(
|
|||
completed = true
|
||||
},
|
||||
onShellExecutionStarted: (pid: number | undefined) => {
|
||||
console.log(`[executeCommand] onShellExecutionStarted: ${pid}`)
|
||||
const status: CommandExecutionStatus = { executionId, status: "started", pid, command }
|
||||
provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue