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
87 changes: 87 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

The mode restoration logic in resumePausedTask (and similarly in resumeTaskFromHistory and in the streaming error handling block) is very similar. Consider extracting this logic into a dedicated helper function to reduce duplication and simplify maintenance.

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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
30 changes: 30 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down