From 7236d02ebb30f7800a705dd4e6ce303fe07b9c2b Mon Sep 17 00:00:00 2001 From: Ara Date: Mon, 9 Feb 2026 15:57:29 -0800 Subject: [PATCH] feat(tools): add auto-approval support for attempt_completion commands (#8926) * feat(tools): add auto-approval support for attempt_completion commands - Add auto-approval logic for bash commands in AttemptCompletionHandler - Show commands as 'say' instead of 'ask' when auto-approved - Display notification prompting user approval when manual approval needed - Add 30-second timeout notification for long-running auto-approved commands - Fix Logger import path from @/shared to @shared * Send to cline provider --- .../handlers/AttemptCompletionHandler.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/core/task/tools/handlers/AttemptCompletionHandler.ts b/src/core/task/tools/handlers/AttemptCompletionHandler.ts index 60ee8067fe..86a5876e84 100644 --- a/src/core/task/tools/handlers/AttemptCompletionHandler.ts +++ b/src/core/task/tools/handlers/AttemptCompletionHandler.ts @@ -7,9 +7,10 @@ import { showSystemNotification } from "@integrations/notifications" import { telemetryService } from "@services/telemetry" import { findLastIndex } from "@shared/array" import { COMPLETION_RESULT_CHANGES_FLAG } from "@shared/ExtensionMessage" +import { Logger } from "@shared/services/Logger" import { ClineDefaultTool } from "@shared/tools" -import { Logger } from "@/shared/services/Logger" import type { ToolResponse } from "../../index" +import { showNotificationForApproval } from "../../utils" import { buildUserFeedbackContent } from "../../utils/buildUserFeedbackContent" import type { IPartialBlockHandler, IToolHandler } from "../ToolExecutorCoordinator" import type { TaskConfig } from "../types/TaskConfig" @@ -162,14 +163,31 @@ export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHand await config.callbacks.updateFCListFromToolResponse(block.params.task_progress) } - // complete command message - need to ask for approval - const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("command", command, config) - if (!didApprove) { - return formatResponse.toolDenied() + // Check if command should be auto-approved + // attempt_completion commands don't have requires_approval param, so we treat them as safe commands + const autoApproveResult = config.autoApprover?.shouldAutoApproveTool(ClineDefaultTool.BASH) + const autoApproveSafe = Array.isArray(autoApproveResult) ? autoApproveResult[0] : autoApproveResult + + if (autoApproveSafe) { + // Auto-approve flow - show command as 'say' instead of 'ask' + await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "command") + await config.callbacks.say("command", command, undefined, undefined, false) + } else { + // Manual approval flow - need to ask for approval + showNotificationForApproval( + `Cline wants to execute a command: ${command}`, + config.autoApprovalSettings.enableNotifications, + ) + + const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("command", command, config) + if (!didApprove) { + return formatResponse.toolDenied() + } } - // User approved, execute the command + // Execute the command const [userRejected, execCommandResult] = await config.callbacks.executeCommandTool(command!, undefined) // no timeout for attempt_completion command + if (userRejected) { config.taskState.didRejectTool = true return execCommandResult