@@ -186,6 +186,19 @@ export interface CliTestStep {
186186 * that don't emit the field still type-check.
187187 */
188188 outcomeContributesToFailure ?: boolean | null ;
189+ /**
190+ * Per-step failure text, carried from `RunStepDto.error` on the run-scoped
191+ * endpoint (`GET /runs/{id}?includeSteps=true`). Only present on `--run-id`
192+ * responses; the cumulative `/tests/{id}/steps` rows do not carry it, so the
193+ * field stays optional (additive, non-breaking for existing consumers).
194+ */
195+ error ?: string | null ;
196+ /**
197+ * Wire step kind from `RunStepDto.type` on the run-scoped endpoint. Same
198+ * availability rules as `error`. Named `stepType` to avoid colliding with
199+ * the free-form `action` label above.
200+ */
201+ stepType ?: 'action' | 'assertion' ;
189202}
190203
191204/**
@@ -3642,6 +3655,12 @@ function mapRunStepToCliTestStep(step: RunStepDto, run: RunResponse): CliTestSte
36423655 // non-contributors. (Per the CliTestStep contract: null ≠ false.)
36433656 outcomeContributesToFailure :
36443657 run . failedStepIndex === null ? null : numericIndex === run . failedStepIndex ,
3658+ // Carry the per-step failure text and the wire step kind through instead
3659+ // of dropping them: the agent asking "why did this step fail?" would
3660+ // otherwise have to download the whole artifact bundle to read a string
3661+ // this very response already contained.
3662+ error : step . error ,
3663+ stepType : step . type ,
36453664 } ;
36463665}
36473666
@@ -3970,6 +3989,14 @@ const RUN_HISTORY_TABLE_COL_WIDTHS = {
39703989 */
39713990const DESC_COL_MAX = 60 ;
39723991
3992+ /**
3993+ * Cap, in chars, for the one-line `error:` sub-line under a failed step row
3994+ * in `renderStepsText`. Long enough for a full assertion message, short
3995+ * enough that a stack-trace blob can't flood the table. Full text is in
3996+ * `--output json`.
3997+ */
3998+ const ERROR_SUBLINE_MAX = 200 ;
3999+
39734000/** Max chars to show in the TARGETURL sub-line (excess truncated with …). */
39744001const HISTORY_TARGET_URL_MAX = 80 ;
39754002
@@ -8433,16 +8460,29 @@ function renderStepsText(page: Page<CliTestStep>): string {
84338460 ' ' +
84348461 'UPDATED' ;
84358462
8436- const rows = page . items . map ( s => {
8463+ const rows = page . items . flatMap ( s => {
84378464 const marker = s . outcomeContributesToFailure === true ? '* ' : ' ' ;
8438- return [
8465+ const row = [
84398466 marker ,
84408467 pad ( String ( s . stepIndex ) , indexWidth ) ,
84418468 pad ( s . action , actionWidth ) ,
84428469 pad ( s . status ?? '—' , statusWidth ) ,
84438470 pad ( descOf ( s ) , descWidth ) ,
84448471 s . updatedAt ,
84458472 ] . join ( ' ' ) ;
8473+ // Run-scoped rows carry the per-step failure text; surface it as an
8474+ // indented sub-line under failed rows (mirrors the history table's
8475+ // `targetUrl:` sub-line). Collapsed to one line and capped so a huge
8476+ // stack blob can't wreck the table; full text ships in --output json.
8477+ if ( s . status === 'failed' && typeof s . error === 'string' && s . error . length > 0 ) {
8478+ const oneLine = s . error . replace ( / \s + / g, ' ' ) . trim ( ) ;
8479+ const shown =
8480+ oneLine . length > ERROR_SUBLINE_MAX
8481+ ? `${ oneLine . slice ( 0 , ERROR_SUBLINE_MAX - 1 ) } …`
8482+ : oneLine ;
8483+ return [ row , ` error: ${ shown } ` ] ;
8484+ }
8485+ return [ row ] ;
84468486 } ) ;
84478487
84488488 const lines : string [ ] = [ header , ...rows , '' ] ;
0 commit comments