@@ -355,19 +355,38 @@ function buildRepoSnapshot(row: ExecutionHistoryRow) {
355355 } ;
356356}
357357
358+ /**
359+ * Extract a human-readable failure message from executions.error JSON.
360+ * Failed/interrupted rows often have no output.summary — without this the
361+ * deposit UI falls through to the generic "Run failed." banner (QA: c7b84ad5).
362+ */
363+ function readErrorMessage ( error : unknown ) : string | null {
364+ if ( typeof error === 'string' && error . trim ( ) ) return error . trim ( ) ;
365+ if ( ! error || typeof error !== 'object' || Array . isArray ( error ) ) return null ;
366+ const record = error as JsonRecord ;
367+ const message = asString ( record . message ) || asString ( record . error ) || asString ( record . reason ) ;
368+ return message ;
369+ }
370+
358371function buildSummary ( row : ExecutionHistoryRow ) {
359372 const output = readOutputRecord ( row ) ;
360373 const context = readContextRecord ( row ) ;
361374 const assetPackCompletion = readAssetPackCompletion ( row ) ;
362375 const assetPackSynthesisArtifacts = buildAssetPackSynthesisArtifacts ( row ) ;
363376 const writtenAssets = buildWrittenAssets ( row ) ;
377+ const status = String ( row . status || '' ) . toLowerCase ( ) ;
378+ const terminalFailure =
379+ status === 'failed' || status === 'interrupted' || status === 'cancelled'
380+ ? readErrorMessage ( row . error )
381+ : null ;
364382
365383 return (
366384 asString ( output ?. summary ) ||
367385 asString ( assetPackCompletion ?. summary ) ||
368386 asString ( assetPackSynthesisArtifacts ?. summary ) ||
369387 asString ( writtenAssets ?. summary ) ||
370388 asString ( context ?. summary ) ||
389+ terminalFailure ||
371390 null
372391 ) ;
373392}
@@ -484,6 +503,9 @@ export function normalizeExecutionHistoryRow(row: ExecutionHistoryRow) {
484503 asset_pack_completion : buildNormalizedAssetPackCompletion ( row ) ,
485504 ledger_settlement : buildLedgerSettlement ( row ) ,
486505 error : row . error ?? null ,
506+ // Wall-clock for run timers on refresh (not derived from truncated event windows).
507+ duration_ms : row . duration_ms ?? null ,
508+ total_tokens : row . total_tokens ?? null ,
487509 } ;
488510}
489511
@@ -641,17 +663,34 @@ export async function getExecutionHistoryRunRoute(
641663 return createJsonResponse ( { error : 'Execution not found or access denied' } , 404 ) ;
642664 }
643665
644- const { data : events , error : eventsError } = await supabaseAdmin
645- . from ( 'execution_events' )
646- . select ( 'id, run_id, event_type, event_data, created_at, agent_name, phase' )
647- . eq ( 'run_id' , runId )
648- . order ( 'created_at' , { ascending : true } ) ;
649-
650- if ( eventsError ) {
651- return createJsonResponse (
652- { error : toErrorMessage ( eventsError , 'Failed to fetch execution event history' ) } ,
653- 500 ,
654- ) ;
666+ // PostgREST/Supabase defaults to max 1000 rows. Deposit synthesis runs produce
667+ // multi-thousand event streams (QA: refresh dropped the second half of a
668+ // ~30m run and halved the clock because only the first 1000 events loaded).
669+ // Page through the full event history in order.
670+ const EVENT_PAGE_SIZE = 1000 ;
671+ const events : ExecutionEventRow [ ] = [ ] ;
672+ let eventOffset = 0 ;
673+ for ( ; ; ) {
674+ const { data : page , error : eventsError } = await supabaseAdmin
675+ . from ( 'execution_events' )
676+ . select ( 'id, run_id, event_type, event_data, created_at, agent_name, phase' )
677+ . eq ( 'run_id' , runId )
678+ . order ( 'created_at' , { ascending : true } )
679+ . order ( 'id' , { ascending : true } )
680+ . range ( eventOffset , eventOffset + EVENT_PAGE_SIZE - 1 ) ;
681+
682+ if ( eventsError ) {
683+ return createJsonResponse (
684+ { error : toErrorMessage ( eventsError , 'Failed to fetch execution event history' ) } ,
685+ 500 ,
686+ ) ;
687+ }
688+ const batch = Array . isArray ( page ) ? ( page as ExecutionEventRow [ ] ) : [ ] ;
689+ events . push ( ...batch ) ;
690+ if ( batch . length < EVENT_PAGE_SIZE ) break ;
691+ eventOffset += EVENT_PAGE_SIZE ;
692+ // Hard ceiling so a runaway table cannot unbounded-read the API process.
693+ if ( eventOffset >= 50_000 ) break ;
655694 }
656695
657696 const normalizedRun = normalizeExecutionHistoryRow ( run ) ;
@@ -662,7 +701,9 @@ export async function getExecutionHistoryRunRoute(
662701 ...normalizedRun ,
663702 terminal_journal : terminalJournal ,
664703 } ,
665- events : ( events || [ ] ) . map ( normalizeExecutionEventRow ) ,
704+ events : events . map ( normalizeExecutionEventRow ) ,
705+ eventCount : events . length ,
706+ eventsTruncated : eventOffset >= 50_000 ,
666707 terminal_journal : terminalJournal ,
667708 } ) ;
668709}
0 commit comments