diff --git a/.tmp/Roo-Code b/.tmp/Roo-Code new file mode 160000 index 0000000000..86debeef43 --- /dev/null +++ b/.tmp/Roo-Code @@ -0,0 +1 @@ +Subproject commit 86debeef43acbea9bdc1aa4b38d514541e164c91 diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 2dd9e55c0b..2c698cbc64 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1935,6 +1935,16 @@ export class Task extends EventEmitter implements TaskLike { const iterator = stream[Symbol.asyncIterator]() let item = await iterator.next() while (!item.done) { + // Check if task is paused and wait if necessary + while (this.isPaused) { + // Wait for 100ms before checking again + await delay(100) + // Check if abort was requested while paused + if (this.abort) { + break + } + } + const chunk = item.value item = await iterator.next() if (!chunk) { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 119668d668..5ea63755fd 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2650,12 +2650,40 @@ export class ClineProvider } } - public resumeTask(taskId: string): void { - // Use the existing showTaskWithId method which handles both current and - // historical tasks. - this.showTaskWithId(taskId).catch((error) => { - this.log(`Failed to resume task ${taskId}: ${error.message}`) - }) + public async pauseTask(): Promise { + const task = this.getCurrentTask() + if (!task) { + return + } + + this.log(`[pauseTask] pausing task ${task.taskId}`) + + // Set the task as paused + task.isPaused = true + + // Emit pause event for tracking + task.emit(RooCodeEventName.TaskPaused, task.taskId) + + // Update the UI to reflect the paused state + await this.postStateToWebview() + } + + public async resumeTask(): Promise { + const task = this.getCurrentTask() + if (!task) { + return + } + + this.log(`[resumeTask] resuming task ${task.taskId}`) + + // Set the task as not paused + task.isPaused = false + + // Emit resume event for tracking + task.emit(RooCodeEventName.TaskUnpaused, task.taskId) + + // Update the UI to reflect the resumed state + await this.postStateToWebview() } // Modes diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c3e57c67a2..b6277995a9 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1003,6 +1003,12 @@ export const webviewMessageHandler = async ( case "cancelTask": await provider.cancelTask() break + case "pauseTask": + await provider.pauseTask() + break + case "resumeTask": + await provider.resumeTask() + break case "allowedCommands": { // Validate and sanitize the commands array const commands = message.commands ?? [] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d43a2fce04..59dc9770ce 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -76,6 +76,8 @@ export interface WebviewMessage { | "openFile" | "openMention" | "cancelTask" + | "pauseTask" + | "resumeTask" | "updateVSCodeSetting" | "getVSCodeSetting" | "vsCodeSetting" diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index d358c68f1c..5df28cf816 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -78,6 +78,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction { const isMountedRef = useRef(true) + const [isPaused, setIsPaused] = useState(false) const [audioBaseUri] = useState(() => { const w = window as any @@ -459,6 +460,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction )} {(secondaryButtonText || isStreaming) && ( - - handleSecondaryButtonClick(inputValue, selectedImages)}> - {isStreaming ? t("chat:cancel.title") : secondaryButtonText} - - + <> + {isStreaming && ( + + + handleSecondaryButtonClick(inputValue, selectedImages) + }> + {isPaused + ? t("chat:resume.title", { defaultValue: "Resume" }) + : t("chat:pause.title", { defaultValue: "Pause" })} + + + )} + {isStreaming && ( + + { + vscode.postMessage({ type: "cancelTask" }) + setDidClickCancel(true) + setIsPaused(false) + }}> + {t("chat:cancel.title")} + + + )} + {!isStreaming && secondaryButtonText && ( + + + handleSecondaryButtonClick(inputValue, selectedImages) + }> + {secondaryButtonText} + + + )} + )} )} diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 7bd0cde9d3..a03959f1ae 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -92,6 +92,14 @@ "title": "Cancel", "tooltip": "Cancel the current operation" }, + "pause": { + "title": "Pause", + "tooltip": "Pause the AI's response" + }, + "resume": { + "title": "Resume", + "tooltip": "Resume the AI's response" + }, "editMessage": { "placeholder": "Edit your message..." },