From cb7d3722c3ae1798432665d1f70162b67658c2c6 Mon Sep 17 00:00:00 2001 From: "Fusion (runfusion.ai)" Date: Sun, 17 May 2026 20:33:18 -0700 Subject: [PATCH] =?UTF-8?q?feat(FN-4946):=20complete=20Step=201=20?= =?UTF-8?q?=E2=80=94=20add=20implicit=20refusal=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fusion-Task-Id: FN-4946 Fusion-Task-Lineage: 9a48f72f-d2cc-4950-8a00-f69053dd1163 --- packages/engine/src/executor.ts | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index a9247e93b..a3d49721d 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -5461,6 +5461,55 @@ export class TaskExecutor { return { blocked: false }; } + private async handleImplicitTaskDoneRefusal( + task: Task, + worktreePath: string, + refusal: Extract, { ok: false }>, + ): Promise { + void worktreePath; + + await this.store.logEntry(task.id, refusal.message, undefined, this.currentRunContext); + executorLog.error(`${task.id}: fn_task_done refused (${refusal.refusalClass}) — ${refusal.reason} (implicit completion)`); + + const priorRequeues = task.taskDoneRetryCount ?? 0; + const nextRequeueCount = priorRequeues + 1; + if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) { + await this.store.updateTask(task.id, { + status: "failed", + error: refusal.message, + taskDoneRetryCount: nextRequeueCount, + paused: false, + pausedByAgentId: null, + worktree: null, + branch: null, + sessionFile: null, + }); + await this.store.logEntry( + task.id, + `${refusal.message} — requeued to todo immediately (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`, + undefined, + this.currentRunContext, + ); + await this.store.moveTask(task.id, "todo", { preserveProgress: true }); + } else { + await this.store.updateTask(task.id, { + status: "failed", + error: refusal.message, + paused: false, + pausedByAgentId: null, + worktree: null, + branch: null, + sessionFile: null, + }); + await this.store.logEntry(task.id, `${refusal.message} — moved to in-review for inspection`, undefined, this.currentRunContext); + await this.persistTokenUsage(task.id); + await this.store.moveTask(task.id, "in-review"); + } + + this.deleteActiveSession(task.id); + this.tokenUsageBaselines.delete(task.id); + } + private createTaskDoneTool( taskId: string, worktreePath: string,