@@ -46,6 +46,182 @@ describe('compact run diagnostics', () => {
4646 expect ( result . files ) . toEqual ( [ { id : 'file-1' , name : 'test.pdf' , base64 : '[binary omitted]' } ] )
4747 } )
4848
49+ it ( 'retains explicit failures from legacy tool calls on a successful span' , ( ) => {
50+ const result = summarizeRun ( {
51+ status : 'completed' ,
52+ traceSpans : [
53+ {
54+ blockId : 'agent-1' ,
55+ name : 'Agent' ,
56+ type : 'agent' ,
57+ status : 'success' ,
58+ toolCalls : [
59+ {
60+ name : 'slack_message' ,
61+ status : 'error' ,
62+ error : 'not_in_channel' ,
63+ input : { channel : 'C123' , text : 'Hello' } ,
64+ output : { ok : false } ,
65+ } ,
66+ {
67+ name : 'slack_message' ,
68+ status : 'success' ,
69+ output : { error : 'An ordinary output field' } ,
70+ } ,
71+ ] ,
72+ } ,
73+ ] ,
74+ } )
75+
76+ expect ( result . executionStatus ) . toBe ( 'completed' )
77+ expect ( result . observedBlocks ) . toEqual ( [
78+ { blockId : 'agent-1' , name : 'Agent' , status : 'success' } ,
79+ ] )
80+ expect ( result . failures ) . toEqual ( [
81+ {
82+ blockId : 'agent-1' ,
83+ name : 'slack_message' ,
84+ status : 'error' ,
85+ error : 'not_in_channel' ,
86+ handled : false ,
87+ input : { channel : 'C123' , text : 'Hello' } ,
88+ output : { ok : false } ,
89+ } ,
90+ ] )
91+ expect ( result . truncated ) . toBe ( false )
92+ } )
93+
94+ it ( 'preserves explicit recovery and bounds legacy tool-call input and output' , ( ) => {
95+ const result = summarizeRun ( {
96+ traceSpans : [
97+ {
98+ blockId : 'agent-1' ,
99+ status : 'success' ,
100+ errorHandled : true ,
101+ toolCalls : [
102+ {
103+ name : 'render' ,
104+ error : 'Invalid export' ,
105+ input : { prompt : 'x' . repeat ( 500 ) } ,
106+ output : { fileBase64 : 'FILE_BYTES' } ,
107+ } ,
108+ ] ,
109+ } ,
110+ ] ,
111+ } )
112+
113+ expect ( result . failures ) . toMatchObject ( [
114+ {
115+ blockId : 'agent-1' ,
116+ name : 'render' ,
117+ error : 'Invalid export' ,
118+ handled : true ,
119+ output : { fileBase64 : '[binary omitted]' } ,
120+ } ,
121+ ] )
122+ expect ( JSON . stringify ( result ) ) . not . toContain ( 'FILE_BYTES' )
123+ expect ( JSON . stringify ( result ) ) . not . toContain ( 'x' . repeat ( 401 ) )
124+ expect ( result . truncated ) . toBe ( true )
125+ } )
126+
127+ it ( 'does not duplicate a span failure with its legacy tool-call error' , ( ) => {
128+ const result = summarizeRun ( {
129+ traceSpans : [
130+ {
131+ blockId : 'agent-1' ,
132+ name : 'Agent' ,
133+ status : 'error' ,
134+ errorMessage : 'not_in_channel' ,
135+ errorHandled : true ,
136+ toolCalls : [ { name : 'slack_message' , error : 'not_in_channel' } ] ,
137+ } ,
138+ ] ,
139+ } )
140+
141+ expect ( result . failures ) . toMatchObject ( [
142+ { blockId : 'agent-1' , name : 'Agent' , error : 'not_in_channel' , handled : true } ,
143+ ] )
144+ expect ( result . failures ) . toHaveLength ( 1 )
145+ } )
146+
147+ it ( 'retains distinct legacy failures alongside modern tool children' , ( ) => {
148+ const result = summarizeRun ( {
149+ traceSpans : [
150+ {
151+ blockId : 'agent-1' ,
152+ name : 'Agent' ,
153+ status : 'success' ,
154+ errorHandled : true ,
155+ toolCalls : [ { name : 'lookup' , error : 'Unavailable' } ] ,
156+ children : [
157+ {
158+ type : 'tool' ,
159+ name : 'lookup' ,
160+ status : 'error' ,
161+ errorMessage : 'Unavailable' ,
162+ errorHandled : true ,
163+ } ,
164+ ] ,
165+ } ,
166+ ] ,
167+ } )
168+
169+ expect ( result . failures ) . toHaveLength ( 2 )
170+ expect ( result . failures ) . toMatchObject ( [
171+ { blockId : 'agent-1' , name : 'lookup' , error : 'Unavailable' , handled : true } ,
172+ { name : 'lookup' , error : 'Unavailable' , handled : true } ,
173+ ] )
174+ expect ( result . truncated ) . toBe ( false )
175+ } )
176+
177+ it ( 'bounds legacy call inspection across spans without reading past the limit' , ( ) => {
178+ const firstCalls = Array . from ( { length : 60 } , ( ) => ( { name : 'lookup' } ) )
179+ const lastCalls = Array . from ( { length : 40 } , ( ) => ( { name : 'lookup' } ) )
180+ const beyondLimit = vi . fn ( ( ) => {
181+ throw new Error ( 'Tool calls beyond the diagnostic limit must not be read' )
182+ } )
183+ Object . defineProperty ( lastCalls , 40 , { get : beyondLimit } )
184+
185+ const result = summarizeRun ( {
186+ traceSpans : [
187+ { blockId : 'first' , toolCalls : firstCalls } ,
188+ { blockId : 'last' , toolCalls : lastCalls } ,
189+ ] ,
190+ } )
191+
192+ expect ( beyondLimit ) . not . toHaveBeenCalled ( )
193+ expect ( result . observedBlocks ) . toHaveLength ( 2 )
194+ expect ( result . failures ) . toEqual ( [ ] )
195+ expect ( result . truncated ) . toBe ( true )
196+ } )
197+
198+ it ( 'shares the failure budget with tool calls and ignores malformed or error-shaped data' , ( ) => {
199+ const result = summarizeRun ( {
200+ traceSpans : [
201+ { status : 'error' , errorMessage : 'Block failed' } ,
202+ {
203+ blockId : 'agent-1' ,
204+ toolCalls : [
205+ null ,
206+ { name : 'lookup' , error : '' } ,
207+ { name : 'lookup' , output : { error : 'Ordinary data' } } ,
208+ ...Array . from ( { length : 20 } , ( _ , index ) => ( {
209+ name : `lookup_${ index } ` ,
210+ error : 'Provider rejected the call' ,
211+ } ) ) ,
212+ ] ,
213+ } ,
214+ ] ,
215+ } )
216+
217+ expect ( result . failures ) . toHaveLength ( 10 )
218+ expect ( result . failures ) . toMatchObject ( [
219+ { error : 'Block failed' } ,
220+ ...Array . from ( { length : 9 } , ( _ , index ) => ( { name : `lookup_${ index } ` } ) ) ,
221+ ] )
222+ expect ( result . truncated ) . toBe ( true )
223+ } )
224+
49225 it ( 'bounds wide/deep traces, long text, and nested output values' , ( ) => {
50226 const result = summarizeRun ( {
51227 traceSpans : Array . from ( { length : 1000 } , ( _ , i ) => ( {
0 commit comments