diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index 5074d7f4e808..5efc92d3e4f6 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -95,13 +95,18 @@ export async function attemptCompletionTool( TelemetryService.instance.captureTaskCompleted(cline.taskId) cline.emit(RooCodeEventName.TaskCompleted, cline.taskId, cline.getTokenUsage(), cline.toolUsage) - if (cline.parentTask) { + // Check both parentTask (direct reference) and parentTaskId (persisted ID) + // This handles cases where VSCode was closed and the direct reference was lost + if (cline.parentTask || cline.parentTaskId) { const didApprove = await askFinishSubTaskApproval() if (!didApprove) { return } + // Return the completion content as a tool result for the child task before finishing + pushToolResult(formatResponse.toolResult(result)) + // tell the provider to remove the current subtask and resume the previous task in the stack await cline.providerRef.deref()?.finishSubTask(result) return diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 91b868796689..f897184c3487 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -482,12 +482,59 @@ export class ClineProvider // This is used when a subtask is finished and the parent task needs to be // resumed. async finishSubTask(lastMessage: string) { + // Get the current task before removing it from the stack + const currentTask = this.getCurrentTask() + const parentTaskId = currentTask?.parentTaskId + let didRestoreParentFromHistory = false + // Remove the last cline instance from the stack (this is the finished // subtask). await this.removeClineFromStack() - // Resume the last cline instance in the stack (if it exists - this is - // the 'parent' calling task). - await this.getCurrentTask()?.completeSubtask(lastMessage) + + // Check if there's a parent task in the stack + let parentTask = this.getCurrentTask() + + // If no parent task in stack but we have a parentTaskId, + // we need to restore the parent task (happens when VSCode was closed) + if (!parentTask && parentTaskId) { + try { + // Restore the parent task from history + const { historyItem } = await this.getTaskWithId(parentTaskId) + parentTask = await this.createTaskWithHistoryItem(historyItem) + didRestoreParentFromHistory = true + this.log(`[finishSubTask] Restored parent task ${parentTaskId} from history to receive subtask result`) + } catch (error) { + this.log( + `[finishSubTask] Failed to restore parent task ${parentTaskId}: ${error instanceof Error ? error.message : String(error)}`, + ) + // Even if parent restoration fails, we should still show completion + // The user can manually resume the parent from task history + return + } + } + + // Resume the parent task with the subtask result + await parentTask?.completeSubtask(lastMessage) + + // If the parent was restored from history, auto-approve the resume prompt so execution continues + if (didRestoreParentFromHistory && parentTask) { + try { + await pWaitFor( + () => { + const ask = parentTask!.taskAsk + return !!ask && (ask.ask === "resume_task" || ask.ask === "resume_completed_task") + }, + { timeout: 3000 }, + ).catch(() => undefined) + parentTask.approveAsk() + } catch (error) { + this.log( + `[finishSubTask] Auto-approve resume failed for parent ${parentTask.taskId}: ${ + error instanceof Error ? error.message : String(error) + }`, + ) + } + } } // Pending Edit Operations Management