fix(copilot): restore client auto-approve with 5s server grace to fix UI not updating

When the server was the sole auto-approver (both at 60s, client removed),
users who stayed in the session saw nothing happen — the server fired
the turn but the frontend had no SSE subscription to receive the events.

Restore the client-side auto-approve effect so it fires at 60s (creating
the SSE subscription), and add a 5s server grace (server fires at 65s).
When the client IS present, it fires first → SSE → user sees the build.
The server wakes 5s later, sees the client's message, skips. When the
client is gone (tab closed), the server fires at 65s as the fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
anvyle
2026-04-14 23:21:44 +02:00
parent d892b66580
commit 6d1cd41c43
2 changed files with 19 additions and 9 deletions

View File

@@ -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

View File

@@ -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);