diff --git a/autogpt_platform/backend/backend/copilot/tools/decompose_goal.py b/autogpt_platform/backend/backend/copilot/tools/decompose_goal.py index cb2f33e855..ff1feadd3b 100644 --- a/autogpt_platform/backend/backend/copilot/tools/decompose_goal.py +++ b/autogpt_platform/backend/backend/copilot/tools/decompose_goal.py @@ -23,11 +23,15 @@ MAX_STEPS = 8 DEFAULT_ACTION = "add_block" VALID_ACTIONS = {"add_block", "connect_blocks", "configure", "add_input", "add_output"} -# Auto-approve countdown — single source of truth for both client and server. -# The frontend reads ``auto_approve_seconds`` from the tool response and runs -# the visible countdown. The server fires at the same deadline. +# Auto-approve countdown — the frontend reads ``auto_approve_seconds`` from the +# tool response and runs the visible countdown (60s). The server fires 5s later +# as a fallback for the "user closed the tab" case. The 5s gap ensures the +# client always fires first when present, creating the SSE subscription that +# lets the user see the build in real-time. When the server wakes at 65s, it +# checks the predicate and skips (the client's message is already there). AUTO_APPROVE_CLIENT_SECONDS = 60 -AUTO_APPROVE_SERVER_SECONDS = AUTO_APPROVE_CLIENT_SECONDS +AUTO_APPROVE_SERVER_GRACE_SECONDS = 5 +AUTO_APPROVE_SERVER_SECONDS = AUTO_APPROVE_CLIENT_SECONDS + AUTO_APPROVE_SERVER_GRACE_SECONDS AUTO_APPROVE_MESSAGE = "Approved. Please build the agent." # Redis key prefix for cross-process cancel signalling. The cancel diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/DecomposeGoal/DecomposeGoal.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/DecomposeGoal/DecomposeGoal.tsx index 25c302ce5e..f7129e7f52 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/DecomposeGoal/DecomposeGoal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/DecomposeGoal/DecomposeGoal.tsx @@ -209,11 +209,17 @@ export function DecomposeGoalTool({ return () => clearInterval(interval); }, [showActions, timerActive, part.toolCallId]); - // The server-side timer is the sole auto-approver (sends the synthetic - // "Approved" message and enqueues the next copilot turn). The client - // countdown is purely visual — no client-side approve() call when it - // hits 0. This avoids the duplicate-message bug where both client and - // server fire at ~60s. User clicks (Start now / Approve) still work. + // Auto-approve when countdown reaches 0. The client fires at 60s; the + // server fires 5s later as a fallback for the "user closed the tab" case. + // When the client IS present, its approve() creates the SSE subscription + // so the user sees the build in real-time. The server's predicate then + // sees the user's message and skips — no duplicate. + // approve() is stable via approvedRef — safe to omit from deps. + useEffect(() => { + if (secondsLeft === 0 && timerActive && actionsEnabled) { + approve(); + } + }, [secondsLeft, timerActive, actionsEnabled]); const progress = secondsLeft / countdownSeconds; const dashOffset = CIRCUMFERENCE * (1 - progress);