()
| 3122 | } |
| 3123 | |
| 3124 | public async cancelTask(): Promise<void> { |
| 3125 | const task = this.getCurrentTask() |
| 3126 | |
| 3127 | if (!task) { |
| 3128 | return |
| 3129 | } |
| 3130 | |
| 3131 | console.log(`[cancelTask] cancelling task ${task.taskId}.${task.instanceId}`) |
| 3132 | |
| 3133 | let historyItem: HistoryItem | undefined |
| 3134 | try { |
| 3135 | const history = await this.getTaskWithId(task.taskId) |
| 3136 | historyItem = history.historyItem |
| 3137 | } catch (error) { |
| 3138 | // During task startup there is a short window where currentTask exists |
| 3139 | // but task history has not been persisted yet. Cancelling should still |
| 3140 | // abort safely; we just skip post-cancel rehydration in that case. |
| 3141 | if (error instanceof Error && error.message === "Task not found") { |
| 3142 | this.log(`[cancelTask] task history missing for ${task.taskId}; skipping rehydrate`) |
| 3143 | } else { |
| 3144 | throw error |
| 3145 | } |
| 3146 | } |
| 3147 | |
| 3148 | // Preserve parent and root task information for history item. |
| 3149 | let rootTask = task.rootTask |
| 3150 | let parentTask = task.parentTask |
| 3151 | |
| 3152 | // Mark this as a user-initiated cancellation so provider-only rehydration can occur |
| 3153 | task.abortReason = "user_cancelled" |
| 3154 | |
| 3155 | // Capture the current instance to detect if rehydrate already occurred elsewhere |
| 3156 | const originalInstanceId = task.instanceId |
| 3157 | |
| 3158 | // Immediately cancel the underlying HTTP request if one is in progress |
| 3159 | // This ensures the stream fails quickly rather than waiting for network timeout |
| 3160 | task.cancelCurrentRequest() |
| 3161 | |
| 3162 | // Begin abort (non-blocking) |
| 3163 | task.abortTask() |
| 3164 | |
| 3165 | // Immediately mark the original instance as abandoned to prevent any residual activity |
| 3166 | task.abandoned = true |
| 3167 | |
| 3168 | await pWaitFor( |
| 3169 | () => |
| 3170 | this.getCurrentTask()! === undefined || |
| 3171 | this.getCurrentTask()!.isStreaming === false || |
| 3172 | this.getCurrentTask()!.didFinishAbortingStream || |
| 3173 | // If only the first chunk is processed, then there's no |
| 3174 | // need to wait for graceful abort (closes edits, browser, |
| 3175 | // etc). |
| 3176 | this.getCurrentTask()!.isWaitingForFirstChunk, |
| 3177 | { |
| 3178 | timeout: 3_000, |
| 3179 | }, |
| 3180 | ).catch(() => { |
| 3181 | console.error("Failed to abort task") |
nothing calls this directly
no test coverage detected