(options?: { skipDelegationRepair?: boolean })
| 481 | // Removes and destroys the top Cline instance (the current finished task), |
| 482 | // activating the previous one (resuming the parent task). |
| 483 | async removeClineFromStack(options?: { skipDelegationRepair?: boolean }) { |
| 484 | const callerStack = new Error().stack |
| 485 | |
| 486 | if (this.clineStack.length === 0) { |
| 487 | return |
| 488 | } |
| 489 | |
| 490 | // Pop the top Cline instance from the stack. |
| 491 | let task = this.clineStack.pop() |
| 492 | |
| 493 | if (task) { |
| 494 | // Capture delegation metadata before abort/dispose, since abortTask(true) |
| 495 | // is async and the task reference is cleared afterwards. |
| 496 | const childTaskId = task.taskId |
| 497 | const parentTaskId = task.parentTaskId |
| 498 | |
| 499 | task.emit(RooCodeEventName.TaskUnfocused) |
| 500 | |
| 501 | try { |
| 502 | // Abort the running task and set isAbandoned to true so |
| 503 | // all running promises will exit as well. |
| 504 | await task.abortTask(true) |
| 505 | } catch (e) { |
| 506 | this.log( |
| 507 | `[ClineProvider#removeClineFromStack] abortTask() failed ${task.taskId}.${task.instanceId}: ${e.message}`, |
| 508 | ) |
| 509 | } |
| 510 | |
| 511 | // Remove event listeners before clearing the reference. |
| 512 | const cleanupFunctions = this.taskEventListeners.get(task) |
| 513 | |
| 514 | if (cleanupFunctions) { |
| 515 | cleanupFunctions.forEach((cleanup) => cleanup()) |
| 516 | this.taskEventListeners.delete(task) |
| 517 | } |
| 518 | |
| 519 | // Make sure no reference kept, once promises end it will be |
| 520 | // garbage collected. |
| 521 | task = undefined |
| 522 | |
| 523 | // Delegation-aware parent metadata repair: |
| 524 | // If the popped task was a delegated child, repair the parent's metadata |
| 525 | // so it transitions from "delegated" back to "active" and becomes resumable |
| 526 | // from the task history list. |
| 527 | // Skip when called from delegateParentAndOpenChild() during nested delegation |
| 528 | // transitions (A→B→C), where the caller intentionally replaces the active |
| 529 | // child and will update the parent to point at the new child. |
| 530 | if (parentTaskId && childTaskId && !options?.skipDelegationRepair) { |
| 531 | try { |
| 532 | await this.runDelegationTransition(parentTaskId, async () => { |
| 533 | const { historyItem: parentHistory } = await this.getTaskWithId(parentTaskId) |
| 534 | |
| 535 | if (parentHistory?.status === "delegated" && parentHistory?.awaitingChildId === childTaskId) { |
| 536 | assertValidTransition(parentHistory.status, "active") |
| 537 | await this.updateTaskHistory({ |
| 538 | ...parentHistory, |
| 539 | status: "active", |
| 540 | awaitingChildId: undefined, |
no test coverage detected