Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tmp/Roo-Code
Submodule Roo-Code added at 86debe
10 changes: 10 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,16 @@ export class Task extends EventEmitter<TaskEvents> 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] If this.abort is set while paused, break exits only the inner pause loop and then the method continues processing the current chunk. This risks emitting partial content after an abort. Consider exiting the outer streaming loop (or returning) when abort is detected inside the pause wait.

await delay(100)
// Check if abort was requested while paused
if (this.abort) {
break
}
}

const chunk = item.value
item = await iterator.next()
if (!chunk) {
Expand Down
40 changes: 34 additions & 6 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const task = this.getCurrentTask()
if (!task) {
return
}

this.log(`[pauseTask] pausing task ${task.taskId}`)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] API compatibility: resumeTask(taskId: string) was replaced by a no-arg variant. If any callers still pass taskId, this will break at compile time. Suggest keeping a backward-compatible overload (e.g., resumeTask(taskId?: string)) where a provided taskId delegates to showTaskWithId(taskId) and otherwise resumes the current task.

// 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<void> {
const task = this.getCurrentTask()
if (!task) {
return
}

this.log(`[resumeTask] resuming task ${task.taskId}`)

// Set the task as not paused
task.isPaused = false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Event typing: Emitting RooCodeEventName.TaskPaused/TaskUnpaused assumes these are defined in the event enum and included in TaskEvents. Please ensure the enum and listener types are updated so consumers can subscribe without any.


// Emit resume event for tracking
task.emit(RooCodeEventName.TaskUnpaused, task.taskId)

// Update the UI to reflect the resumed state
await this.postStateToWebview()
}

// Modes
Expand Down
6 changes: 6 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? []
Expand Down
2 changes: 2 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export interface WebviewMessage {
| "openFile"
| "openMention"
| "cancelTask"
| "pauseTask"
| "resumeTask"
| "updateVSCodeSetting"
| "getVSCodeSetting"
| "vsCodeSetting"
Expand Down
98 changes: 75 additions & 23 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
ref,
) => {
const isMountedRef = useRef(true)
const [isPaused, setIsPaused] = useState(false)

const [audioBaseUri] = useState(() => {
const w = window as any
Expand Down Expand Up @@ -459,6 +460,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setEnableButtons(false)
setPrimaryButtonText(undefined)
setSecondaryButtonText(undefined)
setIsPaused(false)
}
}, [messages.length])

Expand Down Expand Up @@ -578,6 +580,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setSelectedImages([])
setClineAsk(undefined)
setEnableButtons(false)
setIsPaused(false)
// Do not reset mode here as it should persist.
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
Expand Down Expand Up @@ -728,8 +731,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const trimmedInput = text?.trim()

if (isStreaming) {
vscode.postMessage({ type: "cancelTask" })
setDidClickCancel(true)
// If paused, resume; otherwise pause
if (isPaused) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] isPaused is tracked only in local component state. After a reload or other state refresh, UI can desynchronize from provider. Consider sourcing from ExtensionStateContext (e.g., currentTask.isPaused) and syncing via postStateToWebview.

vscode.postMessage({ type: "resumeTask" })
setIsPaused(false)
} else {
vscode.postMessage({ type: "pauseTask" })
setIsPaused(true)
}
return
}

Expand Down Expand Up @@ -767,7 +776,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setClineAsk(undefined)
setEnableButtons(false)
},
[clineAsk, startNewTask, isStreaming],
[clineAsk, startNewTask, isStreaming, isPaused],
)

const { info: model } = useSelectedModel(apiConfiguration)
Expand Down Expand Up @@ -1945,26 +1954,69 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
</StandardTooltip>
)}
{(secondaryButtonText || isStreaming) && (
<StandardTooltip
content={
isStreaming
? t("chat:cancel.tooltip")
: secondaryButtonText === t("chat:startNewTask.title")
? t("chat:startNewTask.tooltip")
: secondaryButtonText === t("chat:reject.title")
? t("chat:reject.tooltip")
: secondaryButtonText === t("chat:terminate.title")
? t("chat:terminate.tooltip")
: undefined
}>
<VSCodeButton
appearance="secondary"
disabled={!enableButtons && !(isStreaming && !didClickCancel)}
className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
onClick={() => handleSecondaryButtonClick(inputValue, selectedImages)}>
{isStreaming ? t("chat:cancel.title") : secondaryButtonText}
</VSCodeButton>
</StandardTooltip>
<>
{isStreaming && (
<StandardTooltip
content={
isPaused
? t("chat:resume.tooltip", {
defaultValue: "Resume the AI's response",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline fallback strings are used in translation calls (e.g. t('chat:resume.title', { defaultValue: 'Resume' }) and t('chat:pause.title', { defaultValue: 'Pause' })). According to our guidelines, remove these defaultValue options so that the system falls back on the English JSON file automatically.

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

})
: t("chat:pause.tooltip", {
defaultValue: "Pause the AI's response",
})
}>
<VSCodeButton
appearance="secondary"
disabled={didClickCancel}
className="flex-1 mr-[6px]"
onClick={() =>
handleSecondaryButtonClick(inputValue, selectedImages)
}>
{isPaused
? t("chat:resume.title", { defaultValue: "Resume" })
: t("chat:pause.title", { defaultValue: "Pause" })}
</VSCodeButton>
</StandardTooltip>
)}
{isStreaming && (
<StandardTooltip content={t("chat:cancel.tooltip")}>
<VSCodeButton
appearance="secondary"
disabled={didClickCancel}
className="flex-1 ml-[6px]"
onClick={() => {
vscode.postMessage({ type: "cancelTask" })
setDidClickCancel(true)
setIsPaused(false)
}}>
{t("chat:cancel.title")}
</VSCodeButton>
</StandardTooltip>
)}
{!isStreaming && secondaryButtonText && (
<StandardTooltip
content={
secondaryButtonText === t("chat:startNewTask.title")
? t("chat:startNewTask.tooltip")
: secondaryButtonText === t("chat:reject.title")
? t("chat:reject.tooltip")
: secondaryButtonText === t("chat:terminate.title")
? t("chat:terminate.tooltip")
: undefined
}>
<VSCodeButton
appearance="secondary"
disabled={!enableButtons}
className="flex-1 ml-[6px]"
onClick={() =>
handleSecondaryButtonClick(inputValue, selectedImages)
}>
{secondaryButtonText}
</VSCodeButton>
</StandardTooltip>
)}
</>
)}
</>
)}
Expand Down
8 changes: 8 additions & 0 deletions webview-ui/src/i18n/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@
"title": "Cancel",
"tooltip": "Cancel the current operation"
},
"pause": {
"title": "Pause",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] New i18n keys are added for en only. To avoid missing-translation warnings, consider adding placeholders for core locales or confirm fallback behavior is acceptable.

"tooltip": "Pause the AI's response"
},
"resume": {
"title": "Resume",
"tooltip": "Resume the AI's response"
},
"editMessage": {
"placeholder": "Edit your message..."
},
Expand Down
Loading