@@ -32,13 +32,13 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
3232} ) => {
3333 const [ currentCheckIndex , setCurrentCheckIndex ] = useState ( 0 ) ;
3434 const [ checks , setChecks ] = useState < SentinelCheck [ ] > ( [ ] ) ;
35- const [ expandedCheck , setExpandedCheck ] = useState < number | null > ( null ) ;
35+ const [ collapsedChecks , setCollapsedChecks ] = useState < Set < number > > ( new Set ( ) ) ;
3636 const [ totalChecks , setTotalChecks ] = useState ( 0 ) ;
3737 const [ runtime , setRuntime ] = useState ( 0 ) ;
38- const [ nextCheckIn , setNextCheckIn ] = useState < number > ( sleepDuration ) ;
3938 const [ currentStatus , setCurrentStatus ] = useState < "checking" | "sleeping" | "complete" > ( "checking" ) ;
4039 const [ countdown , setCountdown ] = useState < number > ( 0 ) ;
41- const [ lastCheckTime , setLastCheckTime ] = useState < number > ( Date . now ( ) ) ;
40+ const [ sleepStartTimestamp , setSleepStartTimestamp ] = useState < string | null > ( null ) ;
41+ const [ sleepDurationSeconds , setSleepDurationSeconds ] = useState < number > ( 0 ) ;
4242
4343 useEffect ( ( ) => {
4444 // Collect all messages related to this sentinel step
@@ -48,7 +48,6 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
4848 msg . config . metadata ?. sentinel_id === sentinelId
4949 ) ;
5050
51-
5251 // Group messages by check number
5352 const checkMap = new Map < number , SentinelCheck > ( ) ;
5453
@@ -66,89 +65,71 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
6665
6766 const check = checkMap . get ( checkNumber ) ! ;
6867
69- // If this is a sentinel_check message, extract check info
70- if ( metadata ?. type === "sentinel_check" ) {
68+ // If this is a sentinel_check or sentinel_sleeping message, extract check info
69+ if ( metadata ?. type === "sentinel_check" || metadata ?. type === "sentinel_sleeping" ) {
7170 check . reason = metadata . reason ;
72- check . nextCheckIn = parseInt ( metadata . next_check_in || "0" ) ;
73- } else {
71+ check . nextCheckIn = parseInt ( metadata . next_check_in || metadata . sleep_duration || "0" ) ;
72+ } else if (
73+ metadata ?. type !== "sentinel_status" &&
74+ metadata ?. type !== "sentinel_complete" &&
75+ metadata ?. type !== "sentinel_start"
76+ ) {
7477 // This is an agent message during the check
7578 check . messages . push ( msg ) ;
7679 }
7780 }
7881 } ) ;
7982
80- // Find the latest sentinel_check or sentinel_complete message
81- const latestStatusMsg = sentinelMessages . reverse ( ) . find ( msg =>
82- msg . config . metadata ?. type === "sentinel_check" ||
83+ // Find the latest status message (sentinel_status, sentinel_sleeping, or sentinel_complete)
84+ const latestStatusMsg = [ ...sentinelMessages ] . reverse ( ) . find ( msg =>
85+ msg . config . metadata ?. type === "sentinel_status" ||
86+ msg . config . metadata ?. type === "sentinel_sleeping" ||
8387 msg . config . metadata ?. type === "sentinel_complete"
8488 ) ;
85- const latestStatusCheckNumber = parseInt ( latestStatusMsg ?. config . metadata ?. check_number || "0" ) ;
8689
87- // Determine current status
90+ // Determine current status from the latest status message
8891 if ( latestStatusMsg ) {
8992 const metadata = latestStatusMsg . config . metadata ;
9093 setTotalChecks ( parseInt ( metadata ?. total_checks || "0" ) ) ;
9194 setRuntime ( parseInt ( metadata ?. runtime || "0" ) ) ;
9295
9396 if ( metadata ?. type === "sentinel_complete" ) {
94- setNextCheckIn ( 0 ) ;
9597 setCurrentStatus ( "complete" ) ;
9698 setCountdown ( 0 ) ;
97- } else if ( metadata ?. type === "sentinel_check" ) {
98- const checkInSeconds = parseInt ( metadata ?. next_check_in || sleepDuration ) ;
99- setNextCheckIn ( checkInSeconds ) ;
100-
101- // Check if there are any agent messages with a check_number higher than the latest sentinel_check
102- // This indicates the agent is actively working on the next check
103- const activeMessages = sentinelMessages . filter ( msg => {
104- const msgCheckNumber = parseInt ( msg . config . metadata ?. check_number || "0" ) ;
105- const isAgentMessage = msg . config . metadata ?. type !== "sentinel_check" &&
106- msg . config . metadata ?. type !== "sentinel_complete" &&
107- msg . config . metadata ?. sentinel_id === sentinelId ;
108- return isAgentMessage && msgCheckNumber > latestStatusCheckNumber ;
109- } ) ;
110-
111- if ( activeMessages . length > 0 ) {
112- const activeCheckNumber = parseInt ( activeMessages [ 0 ] . config . metadata ?. check_number || "0" ) ;
113-
114- // Add these active messages to the check map
115- if ( ! checkMap . has ( activeCheckNumber ) ) {
116- checkMap . set ( activeCheckNumber , {
117- checkNumber : activeCheckNumber ,
118- messages : activeMessages ,
119- reason : "Actively checking..." ,
120- } ) ;
121- } else {
122- const activeCheck = checkMap . get ( activeCheckNumber ) ! ;
123- activeCheck . messages = activeMessages ;
124- activeCheck . reason = "Actively checking..." ;
125- }
126-
127- setCurrentStatus ( "checking" ) ;
128- setCountdown ( 0 ) ;
99+ setSleepStartTimestamp ( null ) ;
100+ setSleepDurationSeconds ( 0 ) ;
101+ } else if ( metadata ?. type === "sentinel_status" ) {
102+ // Orchestrator says it's actively checking
103+ setCurrentStatus ( "checking" ) ;
104+ setCountdown ( 0 ) ;
105+ setSleepStartTimestamp ( null ) ;
106+ setSleepDurationSeconds ( 0 ) ;
107+ } else if ( metadata ?. type === "sentinel_sleeping" ) {
108+ // Orchestrator sent a sleeping message with timestamp
109+ setCurrentStatus ( "sleeping" ) ;
110+ setSleepStartTimestamp ( metadata . sleep_start_timestamp || null ) ;
111+ const duration = parseInt ( metadata ?. sleep_duration || "0" ) ;
112+ setSleepDurationSeconds ( duration ) ;
113+
114+ // Calculate initial countdown from timestamp
115+ if ( metadata . sleep_start_timestamp ) {
116+ const sleepStart = new Date ( metadata . sleep_start_timestamp ) . getTime ( ) ;
117+ const now = Date . now ( ) ;
118+ const elapsed = Math . floor ( ( now - sleepStart ) / 1000 ) ;
119+ const remaining = Math . max ( 0 , duration - elapsed ) ;
120+ setCountdown ( remaining ) ;
129121 } else {
130- // Use the timestamp from the sentinel_check message
131- const checkTimestamp = latestStatusMsg . config . timestamp ;
132- if ( checkTimestamp ) {
133- const checkTime = new Date ( checkTimestamp ) . getTime ( ) ;
134- const elapsed = Math . floor ( ( Date . now ( ) - checkTime ) / 1000 ) ;
135- const remaining = Math . max ( 0 , checkInSeconds - elapsed ) ;
136- setCountdown ( remaining ) ;
137- setLastCheckTime ( checkTime ) ;
138- } else {
139- setCountdown ( checkInSeconds ) ;
140- setLastCheckTime ( Date . now ( ) ) ;
141- }
142- setCurrentStatus ( "sleeping" ) ;
122+ setCountdown ( duration ) ;
143123 }
144124 }
145125 } else {
146126 // If no status messages yet, we're still checking (check #1)
147127 const check1Messages = sentinelMessages . filter ( msg => {
148128 const msgCheckNumber = parseInt ( msg . config . metadata ?. check_number || "0" ) ;
149129 return msgCheckNumber === 1 &&
150- msg . config . metadata ?. type !== "sentinel_check" &&
151- msg . config . metadata ?. type !== "sentinel_complete" ;
130+ msg . config . metadata ?. type !== "sentinel_sleeping" &&
131+ msg . config . metadata ?. type !== "sentinel_complete" &&
132+ msg . config . metadata ?. type !== "sentinel_status" ;
152133 } ) ;
153134
154135 if ( ! checkMap . has ( 1 ) ) {
@@ -160,6 +141,8 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
160141 }
161142
162143 setCurrentStatus ( "checking" ) ;
144+ setSleepStartTimestamp ( null ) ;
145+ setSleepDurationSeconds ( 0 ) ;
163146 }
164147
165148 // Convert map to sorted array and set current check to the latest
@@ -170,26 +153,31 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
170153 }
171154 } , [ allMessages , currentMessageIndex , sentinelId , sleepDuration ] ) ;
172155
173- // Countdown timer effect
156+ // Countdown timer effect - computes countdown from timestamp
174157 useEffect ( ( ) => {
175- if ( currentStatus !== "sleeping" || countdown <= 0 ) {
158+ if ( currentStatus !== "sleeping" || ! sleepStartTimestamp || sleepDurationSeconds <= 0 ) {
176159 return ;
177160 }
178161
179162 const interval = setInterval ( ( ) => {
180- const elapsed = Math . floor ( ( Date . now ( ) - lastCheckTime ) / 1000 ) ;
181- const remaining = Math . max ( 0 , nextCheckIn - elapsed ) ;
163+ const sleepStart = new Date ( sleepStartTimestamp ) . getTime ( ) ;
164+ const now = Date . now ( ) ;
165+ const elapsed = Math . floor ( ( now - sleepStart ) / 1000 ) ;
166+ const remaining = Math . max ( 0 , sleepDurationSeconds - elapsed ) ;
182167 setCountdown ( remaining ) ;
183168
184169 if ( remaining === 0 ) {
185170 clearInterval ( interval ) ;
186- // Auto-switch to checking when timeout reaches 0
171+ // Auto-switch to checking when countdown reaches 0
187172 setCurrentStatus ( "checking" ) ;
188173 }
189174 } , 100 ) ; // Update every 100ms for smooth countdown
190175
191176 return ( ) => clearInterval ( interval ) ;
192- } , [ currentStatus , countdown , nextCheckIn , lastCheckTime ] ) ;
177+ } , [ currentStatus , sleepStartTimestamp , sleepDurationSeconds ] ) ;
178+
179+ // Helper to check if a check is expanded (expanded by default, collapsed if in set)
180+ const isCheckExpanded = ( checkNumber : number ) => ! collapsedChecks . has ( checkNumber ) ;
193181
194182 const currentCheck = checks [ currentCheckIndex ] ;
195183
@@ -251,7 +239,9 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
251239 < div className = "font-semibold text-primary" > { title } </ div >
252240 < div className = "flex items-center gap-2" >
253241 { getStatusIcon ( ) }
254- < span className = "text-sm" > { getStatusText ( ) } </ span >
242+ { ( runStatus === "active" && ( currentStatus === "checking" || currentStatus === "sleeping" ) ) && (
243+ < span className = "text-sm" > { getStatusText ( ) } </ span >
244+ ) }
255245 </ div >
256246 </ div >
257247 < div className = "text-sm space-y-1" >
@@ -305,22 +295,28 @@ const RenderSentinelStep: React.FC<RenderSentinelStepProps> = ({
305295 < div className = "space-y-2" >
306296 < div className = "text-sm" >
307297 { currentCheck . reason ||
308- `Check # ${ currentCheck . checkNumber } - Condition not yet satisfied ` }
298+ `Actively Checking... ` }
309299 </ div >
310300
311301 { /* Expandable section for agent messages */ }
312302 { currentCheck . messages . length > 0 && (
313303 < div className = "mt-2" >
314304 < button
315- onClick = { ( ) => setExpandedCheck (
316- expandedCheck === currentCheck . checkNumber ? null : currentCheck . checkNumber
317- ) }
318- className = "text-sm text-magenta-800 hover:text-magenta-900 underline"
305+ onClick = { ( ) => setCollapsedChecks ( ( prev ) => {
306+ const newSet = new Set ( prev ) ;
307+ if ( newSet . has ( currentCheck . checkNumber ) ) {
308+ newSet . delete ( currentCheck . checkNumber ) ;
309+ } else {
310+ newSet . add ( currentCheck . checkNumber ) ;
311+ }
312+ return newSet ;
313+ } ) }
314+ className = "text-magenta-800 hover:text-magenta-900 underline"
319315 >
320- { expandedCheck === currentCheck . checkNumber ? "Hide" : "Show" } check steps ({ currentCheck . messages . length } )
316+ { isCheckExpanded ( currentCheck . checkNumber ) ? "Hide" : "Show" } check steps ({ currentCheck . messages . length } )
321317 </ button >
322318
323- { expandedCheck === currentCheck . checkNumber && (
319+ { isCheckExpanded ( currentCheck . checkNumber ) && (
324320 < div className = "mt-2 space-y-1" >
325321 { currentCheck . messages . map ( ( msg , idx ) => (
326322 < RenderMessage
0 commit comments