@@ -5,7 +5,11 @@ import {
55 type SessionService ,
66} from "@posthog/core/sessions/sessionService" ;
77import { useService } from "@posthog/di/react" ;
8- import { type AcpMessage , ANALYTICS_EVENTS } from "@posthog/shared" ;
8+ import {
9+ type AcpMessage ,
10+ ANALYTICS_EVENTS ,
11+ type StaleConversationGateChoiceProperties ,
12+ } from "@posthog/shared" ;
913import type { Task , TaskRunStatus } from "@posthog/shared/domain-types" ;
1014import { showOfflineToast } from "@posthog/ui/features/connectivity/connectivityToast" ;
1115import {
@@ -273,7 +277,7 @@ export function SessionView({
273277 const staleGate = useStaleConversationGate ( sessionId , events ) ;
274278
275279 const trackStaleGateChoice = useCallback (
276- ( choice : "compact" | "continue" | "new_session" ) =>
280+ ( choice : StaleConversationGateChoiceProperties [ "choice" ] ) =>
277281 track ( ANALYTICS_EVENTS . STALE_CONVERSATION_GATE_CHOICE , {
278282 choice,
279283 used_tokens : staleGate . usedTokens ,
@@ -297,12 +301,9 @@ export function SessionView({
297301 staleGate . onContinue ( ) ;
298302 } , [ trackStaleGateChoice , staleGate . onContinue ] ) ;
299303
300- const handleStaleNewSession = useMemo ( ( ) => {
301- if ( ! onNewSession ) return undefined ;
302- return ( ) => {
303- trackStaleGateChoice ( "new_session" ) ;
304- onNewSession ( ) ;
305- } ;
304+ const handleStaleNewSession = useCallback ( ( ) => {
305+ trackStaleGateChoice ( "new_session" ) ;
306+ onNewSession ?.( ) ;
306307 } , [ trackStaleGateChoice , onNewSession ] ) ;
307308
308309 const [ isDraggingFile , setIsDraggingFile ] = useState ( false ) ;
@@ -606,37 +607,62 @@ export function SessionView({
606607 ) }
607608 </ Flex >
608609 </ Flex >
609- ) : hideInput ? null : staleGate . active && isRunning ? (
610+ ) : hideInput ? null : staleGate . active ? (
610611 // Replaces the composer (and any pending permission — answering
611612 // one also resumes the costly turn) until the user chooses.
612- // Waits for isRunning so the choices can't fire into a session
613- // that is still reconnecting.
614- < Box className = "min-h-0 shrink-0 overflow-y-auto" >
615- < Box
616- className = { compact ? "p-1" : "mx-auto px-2 pb-3" }
617- style = {
618- compact
619- ? undefined
620- : { maxWidth : CHAT_CONTENT_MAX_WIDTH }
621- }
622- >
623- < StaleConversationCostNotice
624- usedTokens = { staleGate . usedTokens }
625- lastActivityAt = { staleGate . lastActivityAt }
626- costUsd = { staleGate . costUsd }
627- onContinue = { handleStaleContinue }
628- // A queued /compact would land after the pending
629- // permission resumes the costly turn — paying the
630- // reload twice — so the option hides until then.
631- onCompact = {
632- firstPendingPermission
613+ isRunning ? (
614+ < Box className = "min-h-0 shrink-0 overflow-y-auto" >
615+ < Box
616+ className = { compact ? "p-1" : "mx-auto px-2 pb-3" }
617+ style = {
618+ compact
633619 ? undefined
634- : handleStaleCompact
620+ : { maxWidth : CHAT_CONTENT_MAX_WIDTH }
635621 }
636- onNewSession = { handleStaleNewSession }
637- />
622+ >
623+ < StaleConversationCostNotice
624+ // Remount when the option set changes so the
625+ // selector's highlighted index can't dereference or
626+ // fire a different option than the one shown.
627+ key = {
628+ firstPendingPermission
629+ ? "no-compact"
630+ : "with-compact"
631+ }
632+ usedTokens = { staleGate . usedTokens }
633+ lastActivityAt = { staleGate . lastActivityAt }
634+ costUsd = { staleGate . costUsd }
635+ onContinue = { handleStaleContinue }
636+ // A queued /compact would land after the pending
637+ // permission resumes the costly turn — paying the
638+ // reload twice — so the option hides until then.
639+ onCompact = {
640+ firstPendingPermission
641+ ? undefined
642+ : handleStaleCompact
643+ }
644+ onNewSession = {
645+ onNewSession ? handleStaleNewSession : undefined
646+ }
647+ />
648+ </ Box >
638649 </ Box >
639- </ Box >
650+ ) : (
651+ // While reconnecting the gate still covers the composer
652+ // slot: handoff can leave pendingPermissions set, and the
653+ // choices must not fire into a half-connected session.
654+ < Flex
655+ align = "center"
656+ justify = "center"
657+ gap = "2"
658+ className = "min-h-[66px]"
659+ >
660+ < Spinner size = { 28 } className = "animate-spin text-gray-9" />
661+ < Text color = "gray" className = "text-base" >
662+ Connecting to agent...
663+ </ Text >
664+ </ Flex >
665+ )
640666 ) : firstPendingPermission ? (
641667 // This box replaces the composer while a permission is pending, so it's an input
642668 // region: `shrink-0` keeps it from being compressed by the scroller above, and
0 commit comments