@@ -127,6 +127,11 @@ export class AgentDetailPresenter {
127127 ? 6 * 60 * 60 // 6h buckets
128128 : 24 * 60 * 60 ; // 1d buckets
129129
130+ // NOTE: We intentionally don't filter by `task_kind = 'AGENT'` here:
131+ // ClickHouse stores `task_kind = ""` for pre-migration rows and rows
132+ // whose taskKind annotation was never set, even for AGENT tasks. We've
133+ // already verified this task is an agent via `findAgent` (Postgres), so
134+ // matching on environment_id + task_identifier is sufficient.
130135 const queryFn = this . clickhouse . reader . query ( {
131136 name : "agentRunStatusActivity" ,
132137 query : `SELECT
@@ -136,7 +141,6 @@ export class AgentDetailPresenter {
136141 FROM trigger_dev.task_runs_v2
137142 WHERE environment_id = {environmentId: String}
138143 AND task_identifier = {agentSlug: String}
139- AND task_kind = 'AGENT'
140144 AND created_at >= {fromTime: DateTime64(3, 'UTC')}
141145 AND created_at < {toTime: DateTime64(3, 'UTC')}
142146 GROUP BY bucket, status
@@ -159,8 +163,10 @@ export class AgentDetailPresenter {
159163 environmentId,
160164 agentSlug,
161165 bucketSeconds,
162- fromTime : from . toISOString ( ) ,
163- toTime : to . toISOString ( ) ,
166+ // ClickHouse's DateTime64(3, 'UTC') parser rejects the trailing `Z` from
167+ // JS toISOString() ("only 23 of 24 bytes was parsed"). Strip it.
168+ fromTime : from . toISOString ( ) . slice ( 0 , - 1 ) ,
169+ toTime : to . toISOString ( ) . slice ( 0 , - 1 ) ,
164170 } ) ;
165171
166172 if ( error ) {
@@ -169,22 +175,22 @@ export class AgentDetailPresenter {
169175 }
170176
171177 const bucketMap = new Map < number , Record < string , number > > ( ) ;
172- const usedGroups = new Set < GroupLabel > ( ) ;
173178 for ( const row of rows ) {
174179 const group = groupForStatus ( row . status ) ?? "RUNNING" ;
175- usedGroups . add ( group ) ;
176180 const ts = row . bucket * 1000 ;
177181 const existing = bucketMap . get ( ts ) ?? { } ;
178182 existing [ group ] = ( existing [ group ] ?? 0 ) + row . val ;
179183 bucketMap . set ( ts , existing ) ;
180184 }
181185
182- // Build zero-filled time series
186+ // Build zero-filled time series. We always emit every status group so
187+ // the chart legend is stable across time ranges (even when a group has
188+ // no runs in the current window).
183189 const bucketMs = bucketSeconds * 1000 ;
184190 const start = Math . floor ( from . getTime ( ) / bucketMs ) * bucketMs ;
185191 const end = Math . ceil ( to . getTime ( ) / bucketMs ) * bucketMs ;
186192 const points : AgentActivityPoint [ ] = [ ] ;
187- const orderedStatuses = GROUP_LABEL . filter ( ( g ) => usedGroups . has ( g ) ) ;
193+ const orderedStatuses = [ ... GROUP_LABEL ] ;
188194 for ( let ts = start ; ts < end ; ts += bucketMs ) {
189195 const existing = bucketMap . get ( ts ) ?? { } ;
190196 const point : AgentActivityPoint = { bucket : ts } ;
0 commit comments