From 53e264b968df991a176adf0f69374dd3a346a43e Mon Sep 17 00:00:00 2001 From: Roo Date: Tue, 15 Jul 2025 15:17:50 +0000 Subject: [PATCH] fix: comprehensive orchestrator mode restoration for sub-tasks - Enhanced resumePausedTask method with comprehensive mode restoration logic - Added mode restoration safety net in finishSubTask method - Improved mode validation and restoration in task resumption from history - Added error handling for interrupted sub-tasks with mode restoration - Ensures parent task mode is properly restored regardless of how sub-task ends Fixes #5747 --- src/core/task/Task.ts | 87 +++++++++++++++++++++++++++++++ src/core/webview/ClineProvider.ts | 30 +++++++++++ 2 files changed, 117 insertions(+) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index aa0590fedd9..a976631104e 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -763,6 +763,35 @@ export class Task extends EventEmitter { this.isPaused = false this.emit("taskUnpaused") + // Restore the mode that was active when this task was paused + const provider = this.providerRef.deref() + if (provider && this.pausedModeSlug) { + try { + const currentState = await provider.getState() + const currentMode = currentState?.mode ?? defaultModeSlug + + if (currentMode !== this.pausedModeSlug) { + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} restoring mode from '${currentMode}' to '${this.pausedModeSlug}'`, + ) + + await provider.handleModeSwitch(this.pausedModeSlug) + + // Delay to allow mode change to take effect + await delay(500) + + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} successfully restored to '${this.pausedModeSlug}' mode`, + ) + } + } catch (error) { + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} failed to restore mode to '${this.pausedModeSlug}': ${error}`, + ) + // Continue execution even if mode restoration fails + } + } + // Fake an answer from the subtask that it has completed running and // this is the result of what it has done add the message to the chat // history and to the webview ui. @@ -795,6 +824,35 @@ export class Task extends EventEmitter { modifiedClineMessages.splice(lastRelevantMessageIndex + 1) } + // Restore mode if this task was paused with a specific mode + const provider = this.providerRef.deref() + if (provider && this.pausedModeSlug && this.pausedModeSlug !== defaultModeSlug) { + try { + const currentState = await provider.getState() + const currentMode = currentState?.mode ?? defaultModeSlug + + if (currentMode !== this.pausedModeSlug) { + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} resuming from history - restoring mode from '${currentMode}' to '${this.pausedModeSlug}'`, + ) + + await provider.handleModeSwitch(this.pausedModeSlug) + + // Delay to allow mode change to take effect + await delay(500) + + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} successfully restored to '${this.pausedModeSlug}' mode from history`, + ) + } + } catch (error) { + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} failed to restore mode to '${this.pausedModeSlug}' from history: ${error}`, + ) + // Continue execution even if mode restoration fails + } + } + // since we don't use api_req_finished anymore, we need to check if the last api_req_started has a cost value, if it doesn't and no cancellation reason to present, then we remove it since it indicates an api request without any partial content streamed const lastApiReqStartedIndex = findLastIndex( modifiedClineMessages, @@ -1451,6 +1509,35 @@ export class Task extends EventEmitter { await abortStream(cancelReason, streamingFailedMessage) + // Enhanced error handling for sub-tasks: ensure parent task mode is restored + // when a sub-task is interrupted + if (this.parentTask && provider) { + try { + const parentPausedMode = this.parentTask.pausedModeSlug + if (parentPausedMode && parentPausedMode !== defaultModeSlug) { + const currentState = await provider.getState() + const currentMode = currentState?.mode ?? defaultModeSlug + + if (currentMode !== parentPausedMode) { + provider.log( + `[subtasks] sub-task ${this.taskId}.${this.instanceId} interrupted - restoring parent mode from '${currentMode}' to '${parentPausedMode}'`, + ) + + await provider.handleModeSwitch(parentPausedMode) + + provider.log( + `[subtasks] successfully restored parent mode to '${parentPausedMode}' after sub-task interruption`, + ) + } + } + } catch (modeError) { + provider.log( + `[subtasks] failed to restore parent mode after sub-task interruption: ${modeError}`, + ) + // Continue execution even if mode restoration fails + } + } + const history = await provider?.getTaskWithId(this.taskId) if (history) { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 233bd469267..41b10065593 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -228,8 +228,38 @@ export class ClineProvider // this is used when a sub task is finished and the parent task needs to be resumed async finishSubTask(lastMessage: string) { console.log(`[subtasks] finishing subtask ${lastMessage}`) + + // Get the parent task before removing the current one + const parentTask = this.clineStack.length > 1 ? this.clineStack[this.clineStack.length - 2] : undefined + // remove the last cline instance from the stack (this is the finished sub task) await this.removeClineFromStack() + + // Additional safety net: ensure mode is restored for the parent task + if (parentTask && parentTask.pausedModeSlug) { + try { + const currentState = await this.getState() + const currentMode = currentState?.mode ?? defaultModeSlug + + if (currentMode !== parentTask.pausedModeSlug) { + this.log( + `[subtasks] finishSubTask: restoring mode from '${currentMode}' to '${parentTask.pausedModeSlug}' for parent task ${parentTask.taskId}`, + ) + + await this.handleModeSwitch(parentTask.pausedModeSlug) + + this.log( + `[subtasks] finishSubTask: successfully restored mode to '${parentTask.pausedModeSlug}' for parent task ${parentTask.taskId}`, + ) + } + } catch (error) { + this.log( + `[subtasks] finishSubTask: failed to restore mode to '${parentTask.pausedModeSlug}' for parent task ${parentTask.taskId}: ${error}`, + ) + // Continue execution even if mode restoration fails + } + } + // resume the last cline instance in the stack (if it exists - this is the 'parent' calling task) await this.getCurrentCline()?.resumePausedTask(lastMessage) }