diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 59ff4f73c..04d997605 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -57,6 +57,7 @@ import { deriveWorkLogEntries, hasActionableProposedPlan, hasToolActivityForTurn, + hasSettledLatestTurnAdvanced, isLatestTurnSettled, formatElapsed, } from "../session-logic"; @@ -388,6 +389,7 @@ export default function ChatView({ threadId }: ChatViewProps) { const attachmentPreviewHandoffByMessageIdRef = useRef>({}); const attachmentPreviewHandoffTimeoutByMessageIdRef = useRef>({}); const sendInFlightRef = useRef(false); + const sendBaselineTurnIdRef = useRef(null); const dragDepthRef = useRef(0); const terminalOpenByThreadRef = useRef>({}); const setMessagesScrollContainerRef = useCallback((element: HTMLDivElement | null) => { @@ -2061,12 +2063,17 @@ export default function ChatView({ threadId }: ChatViewProps) { }; }, [phase]); - const beginSendPhase = useCallback((nextPhase: Exclude) => { - setSendStartedAt((current) => current ?? new Date().toISOString()); - setSendPhase(nextPhase); - }, []); + const beginSendPhase = useCallback( + (nextPhase: Exclude) => { + sendBaselineTurnIdRef.current = activeLatestTurn?.turnId ?? null; + setSendStartedAt((current) => current ?? new Date().toISOString()); + setSendPhase(nextPhase); + }, + [activeLatestTurn?.turnId], + ); const resetSendPhase = useCallback(() => { + sendBaselineTurnIdRef.current = null; setSendPhase("idle"); setSendStartedAt(null); }, []); @@ -2092,6 +2099,23 @@ export default function ChatView({ threadId }: ChatViewProps) { sendPhase, ]); + useEffect(() => { + if (sendPhase === "idle") { + return; + } + if ( + !hasSettledLatestTurnAdvanced( + activeLatestTurn, + activeThread?.session ?? null, + sendBaselineTurnIdRef.current, + ) + ) { + return; + } + + resetSendPhase(); + }, [activeLatestTurn, activeThread?.session, resetSendPhase, sendPhase]); + useEffect(() => { if (!activeThreadId) return; const previous = terminalOpenByThreadRef.current[activeThreadId] ?? false; diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 83a95d631..8a2d3e791 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -139,6 +139,17 @@ export function isLatestTurnSettled( return true; } +export function hasSettledLatestTurnAdvanced( + latestTurn: LatestTurnTiming | null, + session: SessionActivityState | null, + baselineTurnId: TurnId | null, +): boolean { + if (!isLatestTurnSettled(latestTurn, session)) { + return false; + } + return (latestTurn?.turnId ?? null) !== baselineTurnId; +} + export function deriveActiveWorkStartedAt( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null,