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
This commit is contained in:
Roo 2025-07-15 15:17:50 +00:00
parent d0452d0d5c
commit 53e264b968
2 changed files with 117 additions and 0 deletions

View file

@ -763,6 +763,35 @@ export class Task extends EventEmitter<ClineEvents> {
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<ClineEvents> {
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<ClineEvents> {
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) {

View file

@ -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)
}