diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 85575b5f8..3a3512b7e 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -732,6 +732,42 @@ export class BackgroundManager { } } + /** + * Cancels a pending task by removing it from queue and marking as cancelled. + * Does NOT abort session (no session exists yet) or release concurrency slot (wasn't acquired). + */ + cancelPendingTask(taskId: string): boolean { + const task = this.tasks.get(taskId) + if (!task || task.status !== "pending") { + return false + } + + // Find and remove from queue + const key = task.model + ? `${task.model.providerID}/${task.model.modelID}` + : task.agent + const queue = this.queuesByKey.get(key) + if (queue) { + const index = queue.findIndex(item => item.task.id === taskId) + if (index !== -1) { + queue.splice(index, 1) + if (queue.length === 0) { + this.queuesByKey.delete(key) + } + } + } + + // Mark as cancelled + task.status = "cancelled" + task.completedAt = new Date() + + // Clean up pendingByParent + this.cleanupPendingByParent(task) + + log("[background-agent] Cancelled pending task:", { taskId, key }) + return true + } + private startPolling(): void { if (this.pollingInterval) return diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts index 02000931c..76b048f23 100644 --- a/src/tools/background-task/tools.ts +++ b/src/tools/background-task/tools.ts @@ -383,24 +383,31 @@ export function createBackgroundCancel(manager: BackgroundManager, client: Openc if (cancelAll) { const tasks = manager.getAllDescendantTasks(toolContext.sessionID) - const runningTasks = tasks.filter(t => t.status === "running") + const cancellableTasks = tasks.filter(t => t.status === "running" || t.status === "pending") - if (runningTasks.length === 0) { - return `✅ No running background tasks to cancel.` + if (cancellableTasks.length === 0) { + return `✅ No running or pending background tasks to cancel.` } const results: string[] = [] - for (const task of runningTasks) { - client.session.abort({ - path: { id: task.sessionID }, - }).catch(() => {}) + for (const task of cancellableTasks) { + if (task.status === "pending") { + // Pending task: use manager method (no session to abort) + manager.cancelPendingTask(task.id) + results.push(`- ${task.id}: ${task.description} (pending)`) + } else { + // Running task: abort session + client.session.abort({ + path: { id: task.sessionID }, + }).catch(() => {}) - task.status = "cancelled" - task.completedAt = new Date() - results.push(`- ${task.id}: ${task.description}`) + task.status = "cancelled" + task.completedAt = new Date() + results.push(`- ${task.id}: ${task.description} (running)`) + } } - return `✅ Cancelled ${runningTasks.length} background task(s): + return `✅ Cancelled ${cancellableTasks.length} background task(s): ${results.join("\n")}` } @@ -410,11 +417,26 @@ ${results.join("\n")}` return `❌ Task not found: ${args.taskId}` } - if (task.status !== "running") { + if (task.status !== "running" && task.status !== "pending") { return `❌ Cannot cancel task: current status is "${task.status}". -Only running tasks can be cancelled.` +Only running or pending tasks can be cancelled.` } + if (task.status === "pending") { + // Pending task: use manager method (no session to abort, no slot to release) + const cancelled = manager.cancelPendingTask(task.id) + if (!cancelled) { + return `❌ Failed to cancel pending task: ${task.id}` + } + + return `✅ Pending task cancelled successfully + +Task ID: ${task.id} +Description: ${task.description} +Status: ${task.status}` + } + + // Running task: abort session // Fire-and-forget: abort 요청을 보내고 await 하지 않음 // await 하면 메인 세션까지 abort 되는 문제 발생 client.session.abort({