@@ -10,7 +10,6 @@ import { withTestRenderer } from "./harness";
1010import { attachSessionBridge , createRecordingPort } from "./runtime-bridge" ;
1111import { createAppShell } from "./shell/index" ;
1212import {
13- isCollapsibleRow ,
1413 paintStreamRow ,
1514 toolSentenceLines ,
1615 type RowLayout ,
@@ -22,6 +21,12 @@ const LAYOUT: RowLayout = { width: 72, multiAgent: false };
2221
2322const painted = ( row : StreamRow ) : string => paintStreamRow ( row , LAYOUT ) . content ;
2423
24+ const collapsed = ( row : StreamRow ) : string =>
25+ toolSentenceLines ( row )
26+ . flat ( )
27+ . map ( ( segment ) => segment . text )
28+ . join ( "" ) ;
29+
2530const LINEAR_ISSUES = JSON . stringify ( {
2631 issues : [
2732 { id : "1" , title : "First" } ,
@@ -81,7 +86,7 @@ describe("a call and its answer", () => {
8186 expect ( rows [ 0 ] ?. detail ) . toBeUndefined ( ) ;
8287 } ) ;
8388
84- test ( "mark the row failed, keeping the failure out of the collapsed line" , ( ) => {
89+ test ( "mark the row failed and put the error on the collapsed line" , ( ) => {
8590 const rows : StreamRow [ ] = [ ] ;
8691 pushToolCall ( rows , {
8792 name : "fetch" ,
@@ -95,7 +100,61 @@ describe("a call and its answer", () => {
95100 expect ( rows . length ) . toBe ( 1 ) ;
96101 expect ( rows [ 0 ] ?. failed ) . toBe ( true ) ;
97102 expect ( painted ( defined ( rows [ 0 ] ) ) ) . toContain ( "×" ) ;
98- expect ( rows [ 0 ] ?. detail ?. length ) . toBeGreaterThan ( 0 ) ;
103+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "connection refused" ) ;
104+ } ) ;
105+
106+ test ( "a failed read_file of a missing tool-output URI shows the error on the collapsed line" , ( ) => {
107+ const rows : StreamRow [ ] = [ ] ;
108+ pushToolCall ( rows , {
109+ name : "read_file" ,
110+ arguments : JSON . stringify ( { path : "tool-output:///missing-blob" } ) ,
111+ } ) ;
112+ pushToolResult ( rows , {
113+ name : "read_file" ,
114+ content : 'Blob not found for key: "missing-blob"' ,
115+ isError : true ,
116+ } ) ;
117+ expect ( rows [ 0 ] ?. failed ) . toBe ( true ) ;
118+ expect ( painted ( defined ( rows [ 0 ] ) ) ) . toContain ( "×" ) ;
119+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "Blob not found" ) ;
120+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain (
121+ "tool-output:///missing-blob" ,
122+ ) ;
123+ } ) ;
124+
125+ test ( "a failed read_file of a missing filesystem path shows the error on the collapsed line" , ( ) => {
126+ const rows : StreamRow [ ] = [ ] ;
127+ pushToolCall ( rows , {
128+ name : "read_file" ,
129+ arguments : JSON . stringify ( { path : "/no/such/file.ts" } ) ,
130+ } ) ;
131+ pushToolResult ( rows , {
132+ name : "read_file" ,
133+ content : "file not found: /no/such/file.ts" ,
134+ isError : true ,
135+ } ) ;
136+ expect ( rows [ 0 ] ?. failed ) . toBe ( true ) ;
137+ expect ( painted ( defined ( rows [ 0 ] ) ) ) . toContain ( "×" ) ;
138+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "file not found" ) ;
139+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "/no/such/file.ts" ) ;
140+ } ) ;
141+
142+ test ( "a successful read_file keeps the path as the subject and the success mark" , ( ) => {
143+ const rows : StreamRow [ ] = [ ] ;
144+ pushToolCall ( rows , {
145+ name : "read_file" ,
146+ arguments : JSON . stringify ( { path : "src/a.ts" } ) ,
147+ } ) ;
148+ pushToolResult ( rows , {
149+ name : "read_file" ,
150+ content : "export const a = 1;\n" ,
151+ } ) ;
152+ expect ( rows [ 0 ] ?. failed ) . toBeUndefined ( ) ;
153+ expect ( painted ( defined ( rows [ 0 ] ) ) ) . toContain ( "✓" ) ;
154+ expect ( painted ( defined ( rows [ 0 ] ) ) ) . not . toContain ( "×" ) ;
155+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "src/a.ts" ) ;
156+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . not . toContain ( "file not found" ) ;
157+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . not . toContain ( "Blob not found" ) ;
99158 } ) ;
100159
101160 test ( "a resolved sub-agent dispatch drops its live elapsed-time trailer for the real answer" , ( ) => {
@@ -256,12 +315,9 @@ describe("parallel calls to the same tool", () => {
256315 expect ( rows [ 1 ] ?. pending ) . toBe ( true ) ;
257316 } ) ;
258317
259- // Acceptance criterion: a failed sub-agent surfaces its error inline
260- // (expandable), not a bare mark with nothing behind it. `mergeToolRows` /
261- // `toolResultRow` already carry the failed result's own text into `detail`
262- // — untouched by this fix, but only reachable per-call once results resolve
263- // to the right row instead of a neighbour's.
264- test ( "a failed call keeps its error text behind the expand arrow" , ( ) => {
318+ // A failed call must show its error on the collapsed line, not only behind
319+ // the expand arrow.
320+ test ( "a failed call shows its error text on the collapsed line" , ( ) => {
265321 const rows : StreamRow [ ] = [ ] ;
266322 pushToolCall ( rows , {
267323 name : "spawn_agent" ,
@@ -278,8 +334,8 @@ describe("parallel calls to the same tool", () => {
278334 callId : "c1" ,
279335 } ) ;
280336 expect ( rows [ 0 ] ?. failed ) . toBe ( true ) ;
281- expect ( isCollapsibleRow ( defined ( rows [ 0 ] ) ) ) . toBe ( true ) ;
282- expect ( rows [ 0 ] ?. detail ?. [ 0 ] ?. [ 0 ] ?. text ) . toContain ( "boom" ) ;
337+ expect ( painted ( defined ( rows [ 0 ] ) ) ) . toContain ( "×" ) ;
338+ expect ( collapsed ( defined ( rows [ 0 ] ) ) ) . toContain ( "boom" ) ;
283339 } ) ;
284340} ) ;
285341
0 commit comments