11import { getReportChatType } from '@selectors/Report' ;
22import agentZeroProcessingIndicatorSelector from '@selectors/ReportNameValuePairs' ;
33import React , { createContext , useContext , useEffect , useRef , useState } from 'react' ;
4- import useLocalize from '@hooks/useLocalize' ;
54import useNetwork from '@hooks/useNetwork' ;
65import useOnyx from '@hooks/useOnyx' ;
7- import { clearConciergeThinkingKickoff , getReportChannelName } from '@libs/actions/Report' ;
6+ import { getReportChannelName } from '@libs/actions/Report' ;
87import Log from '@libs/Log' ;
98import Pusher from '@libs/Pusher' ;
109import CONST from '@src/CONST' ;
@@ -17,7 +16,7 @@ type ReasoningEntry = {
1716} ;
1817
1918type AgentZeroStatusState = {
20- /** Whether AgentZero is actively working — true when the server sent a processing label or we're optimistically waiting */
19+ /** Whether AgentZero is actively working — true when the server has sent a processing label */
2120 isProcessing : boolean ;
2221
2322 /** Chronological list of reasoning steps streamed via Pusher during the current processing request */
@@ -27,23 +26,13 @@ type AgentZeroStatusState = {
2726 statusLabel : string ;
2827} ;
2928
30- type AgentZeroStatusActions = {
31- /** Sets optimistic "thinking" state immediately after the user sends a message, before the server responds */
32- kickoffWaitingIndicator : ( ) => void ;
33- } ;
34-
3529const defaultState : AgentZeroStatusState = {
3630 isProcessing : false ,
3731 reasoningHistory : [ ] ,
3832 statusLabel : '' ,
3933} ;
4034
41- const defaultActions : AgentZeroStatusActions = {
42- kickoffWaitingIndicator : ( ) => { } ,
43- } ;
44-
4535const AgentZeroStatusStateContext = createContext < AgentZeroStatusState > ( defaultState ) ;
46- const AgentZeroStatusActionsContext = createContext < AgentZeroStatusActions > ( defaultActions ) ;
4736
4837/**
4938 * Cheap outer guard — only subscribes to the scalar CONCIERGE_REPORT_ID.
@@ -77,59 +66,33 @@ function AgentZeroStatusProvider({reportID, children}: React.PropsWithChildren<{
7766const MIN_DISPLAY_TIME = 300 ; // ms
7867// Debounce delay for server label updates
7968const DEBOUNCE_DELAY = 150 ; // ms
80- const OPTIMISTIC_TIMEOUT = 120000 ; // 2 minutes
8169
8270/**
83- * Inner gate — all Pusher, reasoning, label, and processing state.
84- * Only mounted when reportID matches the Concierge report .
71+ * Inner gate — all Pusher, reasoning, and label state.
72+ * Only mounted for AgentZero chats ( Concierge DMs or policy #admins rooms) .
8573 * Remounted via key prop when reportID changes, so all state resets automatically.
8674 */
8775function AgentZeroStatusGate ( { reportID, children} : React . PropsWithChildren < { reportID : string } > ) {
8876 // Server-driven processing label from report name-value pairs (e.g. "Looking up categories...")
77+ // Backend only writes this when AgentZero is actually handling the chat — the client no longer
78+ // sets an optimistic label on send, so if AZ short-circuits (chat job exists, human responded
79+ // within R2LR_TIME) nothing renders.
8980 const [ serverLabel ] = useOnyx ( `${ ONYXKEYS . COLLECTION . REPORT_NAME_VALUE_PAIRS } ${ reportID } ` , { selector : agentZeroProcessingIndicatorSelector } ) ;
9081
91- // Timestamp set when the user sends a message, before the server label arrives — shows "Concierge is thinking..."
92- const [ optimisticStartTime , setOptimisticStartTime ] = useState < number | null > ( null ) ;
9382 // Debounced label shown to the user — smooths rapid server label changes
9483 const displayedLabelRef = useRef < string > ( '' ) ;
9584 const [ displayedLabel , setDisplayedLabel ] = useState < string > ( '' ) ;
9685 // Chronological list of reasoning steps streamed via Pusher during a single processing request
9786 const [ reasoningHistory , setReasoningHistory ] = useState < ReasoningEntry [ ] > ( [ ] ) ;
98- const { translate} = useLocalize ( ) ;
9987 // Timer for debounced label updates — ensures a minimum display time before switching
10088 const updateTimerRef = useRef < NodeJS . Timeout | null > ( null ) ;
10189 // Timestamp of the last label update — used to enforce MIN_DISPLAY_TIME
10290 const lastUpdateTimeRef = useRef < number > ( 0 ) ;
10391 const { isOffline} = useNetwork ( ) ;
10492
105- // Auto-kickoff "thinking" indicator when opened from search (where kickoffWaitingIndicator isn't accessible)
106- const [ shouldKickoff ] = useOnyx ( ONYXKEYS . CONCIERGE_THINKING_KICKOFF ) ;
107- useEffect ( ( ) => {
108- if ( ! shouldKickoff ) {
109- return ;
110- }
111- clearConciergeThinkingKickoff ( ) ;
112- // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot kickoff from search; Onyx flag is cleared immediately so it cannot cascade
113- setOptimisticStartTime ( Date . now ( ) ) ;
114- } , [ shouldKickoff ] ) ;
115-
11693 // Tracks the current agentZeroRequestID so the Pusher callback can detect new requests
11794 const agentZeroRequestIDRef = useRef ( '' ) ;
11895
119- // Clear optimistic state once server label arrives — the server has taken over
120- if ( serverLabel && optimisticStartTime ) {
121- setOptimisticStartTime ( null ) ;
122- }
123-
124- // Clear optimistic state when coming back online — stale optimism from offline
125- const [ prevIsOffline , setPrevIsOffline ] = useState ( isOffline ) ;
126- if ( prevIsOffline !== isOffline ) {
127- setPrevIsOffline ( isOffline ) ;
128- if ( ! isOffline && optimisticStartTime ) {
129- setOptimisticStartTime ( null ) ;
130- }
131- }
132-
13396 // Clear reasoning when processing ends (server label transitions from truthy → falsy)
13497 const [ prevServerLabel , setPrevServerLabel ] = useState ( serverLabel ) ;
13598 if ( prevServerLabel !== serverLabel ) {
@@ -190,12 +153,7 @@ function AgentZeroStatusGate({reportID, children}: React.PropsWithChildren<{repo
190153 // Synchronize the displayed label with debounce and minimum display time.
191154 // displayedLabelRef mirrors state so the effect can check the current value without depending on displayedLabel.
192155 useEffect ( ( ) => {
193- let targetLabel = '' ;
194- if ( serverLabel ) {
195- targetLabel = serverLabel ;
196- } else if ( optimisticStartTime ) {
197- targetLabel = translate ( 'common.thinking' ) ;
198- }
156+ const targetLabel = serverLabel ?? '' ;
199157
200158 if ( displayedLabelRef . current === targetLabel ) {
201159 return ;
@@ -213,7 +171,6 @@ function AgentZeroStatusGate({reportID, children}: React.PropsWithChildren<{repo
213171 // Immediate update when enough time has passed or when clearing the label
214172 if ( remainingMinTime === 0 || targetLabel === '' ) {
215173 displayedLabelRef . current = targetLabel ;
216-
217174 setDisplayedLabel ( targetLabel ) ;
218175 lastUpdateTimeRef . current = now ;
219176 } else {
@@ -233,52 +190,23 @@ function AgentZeroStatusGate({reportID, children}: React.PropsWithChildren<{repo
233190 }
234191 clearTimeout ( updateTimerRef . current ) ;
235192 } ;
236- } , [ serverLabel , optimisticStartTime , translate ] ) ;
237-
238- // Pusher updates carrying the server label can be silently dropped, leaving the optimistic indicator stuck forever.
239- useEffect ( ( ) => {
240- if ( ! optimisticStartTime ) {
241- return ;
242- }
243- const elapsed = Date . now ( ) - optimisticStartTime ;
244- const remaining = Math . max ( 0 , OPTIMISTIC_TIMEOUT - elapsed ) ;
245- const timer = setTimeout ( ( ) => {
246- setOptimisticStartTime ( null ) ;
247- } , remaining ) ;
248- return ( ) => clearTimeout ( timer ) ;
249- } , [ optimisticStartTime ] ) ;
250-
251- const kickoffWaitingIndicator = ( ) => {
252- setOptimisticStartTime ( Date . now ( ) ) ;
253- } ;
193+ } , [ serverLabel ] ) ;
254194
255- // True when AgentZero is actively working — either the server sent a label or we're optimistically waiting
256- const isProcessing = ! isOffline && ( ! ! serverLabel || ! ! optimisticStartTime ) ;
195+ // True when AgentZero is actively working — the server has sent a label
196+ const isProcessing = ! isOffline && ! ! serverLabel ;
257197
258198 const stateValue : AgentZeroStatusState = {
259199 isProcessing,
260200 reasoningHistory,
261201 statusLabel : displayedLabel ,
262202 } ;
263203
264- const actionsValue : AgentZeroStatusActions = {
265- kickoffWaitingIndicator,
266- } ;
267-
268- return (
269- < AgentZeroStatusActionsContext . Provider value = { actionsValue } >
270- < AgentZeroStatusStateContext . Provider value = { stateValue } > { children } </ AgentZeroStatusStateContext . Provider >
271- </ AgentZeroStatusActionsContext . Provider >
272- ) ;
204+ return < AgentZeroStatusStateContext . Provider value = { stateValue } > { children } </ AgentZeroStatusStateContext . Provider > ;
273205}
274206
275207function useAgentZeroStatus ( ) : AgentZeroStatusState {
276208 return useContext ( AgentZeroStatusStateContext ) ;
277209}
278210
279- function useAgentZeroStatusActions ( ) : AgentZeroStatusActions {
280- return useContext ( AgentZeroStatusActionsContext ) ;
281- }
282-
283- export { AgentZeroStatusProvider , useAgentZeroStatus , useAgentZeroStatusActions } ;
284- export type { AgentZeroStatusState , AgentZeroStatusActions , ReasoningEntry } ;
211+ export { AgentZeroStatusProvider , useAgentZeroStatus } ;
212+ export type { AgentZeroStatusState , ReasoningEntry } ;
0 commit comments