@@ -5,6 +5,8 @@ const { detectNonRetryableHarnessGuard, isAuthenticationFailedError } = require(
55const { crashSignalNameForExitCode } = require ( "./harness_crash_signals.cjs" ) ;
66const { ERR_VALIDATION } = require ( "./error_codes.cjs" ) ;
77const { sessionContext } = require ( "./agent_session.cjs" ) ;
8+ const { stripVTControlCharacters } = require ( "node:util" ) ;
9+ const { applyAddMaskRedaction, collectAddMaskedValues } = require ( "./add_mask_redaction.cjs" ) ;
810
911/** @typedef {import("./types/agent_session").AgentExecutionData } AgentExecutionData */
1012/** @typedef {import("./types/agent_session").SessionEvent } SessionEvent */
@@ -49,7 +51,8 @@ function isAgentExecutionEvent(event) {
4951function recordErrors ( record ) {
5052 if ( ! record || typeof record !== "object" || Array . isArray ( record ) ) return [ ] ;
5153 const { agentId, parentToolUseId } = sessionContext ( record ) ;
52- if ( agentId || parentToolUseId ) return [ ] ;
54+ const parentToolCallId = record . data ?. parentToolCallId !== undefined ? record . data . parentToolCallId : record . parentToolCallId ;
55+ if ( agentId || parentToolUseId || ( typeof parentToolCallId === "string" && parentToolCallId . length > 0 ) ) return [ ] ;
5356 const data = record . data ?? record ;
5457 if ( [ "error" , "session.error" ] . includes ( record . type ) && [ "warning" , "info" ] . includes ( data . severity ) ) return [ ] ;
5558 if ( [ "session.error" , "claude.assistant_error" , "claude.api_retry" , "error" , "turn.failed" ] . includes ( record . type ) ) return [ data ] ;
@@ -79,16 +82,69 @@ function isAgentDiagnosticLine(line) {
7982 * Plain transcript blocks are not diagnostics, even if their text starts with
8083 * an error signature. A source-prefixed runtime line restores attribution.
8184 * @param {string } content
85+ * @param {SessionEvent[] } [events]
8286 * @param {string } [rawSourceEngine] Required attribution for raw structured errors.
8387 * @returns {{diagnostics: string[], errors: any[]} }
8488 */
85- function collectAgentErrorEvidence ( content , rawSourceEngine ) {
89+ function collectAgentErrorEvidence ( content , events = [ ] , rawSourceEngine ) {
8690 const diagnostics = [ ] ;
8791 const errors = [ ] ;
88- const attributedErrors = value => ( rawSourceEngine !== undefined && value ?. data ?. sourceEngine !== rawSourceEngine ? [ ] : recordErrors ( value ) ) ;
92+ let conversationEvents = events ;
93+ if ( ! events . length && content . includes ( "[deepseek-harness]" ) ) {
94+ // Load lazily: the native parser also uses execution collection for failures.
95+ const { isDeepSeekLog, parseDeepSeekLog } = require ( "./parse_deepseek_log.cjs" ) ;
96+ if ( isDeepSeekLog ( content ) ) conversationEvents = parseDeepSeekLog ( content ) . logEntries ;
97+ }
98+ const maskedValues = collectAddMaskedValues ( content ) ;
99+ /** @param {string } value */
100+ const conversationKey = value => applyAddMaskRedaction ( stripVTControlCharacters ( value ) , maskedValues ) . trim ( ) ;
101+ /** @param {unknown } value */
102+ const conversationDocumentKey = value =>
103+ JSON . stringify ( value , ( _ , item ) => {
104+ if ( typeof item === "string" ) return applyAddMaskRedaction ( stripVTControlCharacters ( item ) , maskedValues ) ;
105+ if ( item && typeof item === "object" && ! Array . isArray ( item ) )
106+ return Object . fromEntries (
107+ Object . keys ( item )
108+ . sort ( )
109+ . map ( key => [ key , item [ key ] ] )
110+ ) ;
111+ return item ;
112+ } ) ;
113+ const conversationLines = new Set ( ) ;
114+ const conversationTexts = new Set ( ) ;
115+ const conversationDocuments = new Set ( ) ;
116+ /** @param {unknown } value @returns {void } */
117+ const attributeConversationValue = value => {
118+ if ( typeof value === "string" ) {
119+ conversationTexts . add ( conversationKey ( value ) ) ;
120+ for ( const line of value . split ( / \r ? \n / ) ) conversationLines . add ( conversationKey ( line ) ) ;
121+ } else if ( value && typeof value === "object" ) {
122+ conversationDocuments . add ( conversationDocumentKey ( value ) ) ;
123+ for ( const item of Object . values ( value ) ) attributeConversationValue ( item ) ;
124+ }
125+ } ;
126+ for ( const event of conversationEvents ) {
127+ if ( [ "assistant.message" , "assistant.reasoning" , "assistant.refusal" , "user.message" ] . includes ( event ?. type ) ) attributeConversationValue ( event . data ?. content ) ;
128+ else if ( [ "tool.execution_complete" , "tool.execution_update" ] . includes ( event ?. type ) ) {
129+ for ( const field of [ "output" , "result" , "error" ] ) attributeConversationValue ( event . data ?. [ field ] ) ;
130+ }
131+ }
132+ if ( conversationTexts . has ( conversationKey ( content ) ) ) return { diagnostics, errors } ;
133+ /** @param {unknown } record */
134+ const recordEvidence = record => {
135+ if ( rawSourceEngine !== undefined ) {
136+ if ( ! record || typeof record !== "object" || ! ( "data" in record ) ) return [ ] ;
137+ const data = record . data ;
138+ if ( ! data || typeof data !== "object" || ! ( "sourceEngine" in data ) || data . sourceEngine !== rawSourceEngine ) return [ ] ;
139+ }
140+ const evidence = recordErrors ( record ) ;
141+ return evidence . length && conversationDocuments . size && conversationDocuments . has ( conversationDocumentKey ( record ) ) ? [ ] : evidence ;
142+ } ;
89143 try {
90144 const document = JSON . parse ( content ) ;
91- for ( const value of Array . isArray ( document ) ? document : [ document ] ) errors . push ( ...attributedErrors ( value ) ) ;
145+ const evidence = ( Array . isArray ( document ) ? document : [ document ] ) . flatMap ( recordEvidence ) ;
146+ if ( evidence . length && Array . isArray ( document ) && conversationDocuments . has ( conversationDocumentKey ( document ) ) ) return { diagnostics, errors } ;
147+ errors . push ( ...evidence ) ;
92148 return { diagnostics, errors } ;
93149 } catch {
94150 // Mixed stdio is parsed as individual records and attributed diagnostic lines.
@@ -107,6 +163,7 @@ function collectAgentErrorEvidence(content, rawSourceEngine) {
107163 transcriptBlock = true ;
108164 continue ;
109165 }
166+ if ( conversationLines . has ( conversationKey ( line ) ) ) continue ;
110167 let record ;
111168 try {
112169 record = JSON . parse ( line ) ;
@@ -121,16 +178,25 @@ function collectAgentErrorEvidence(content, rawSourceEngine) {
121178 continue ;
122179 }
123180 if ( ! transcriptBlock ) {
124- for ( const value of Array . isArray ( record ) ? record : [ record ] ) errors . push ( ...attributedErrors ( value ) ) ;
181+ const evidence = ( Array . isArray ( record ) ? record : [ record ] ) . flatMap ( recordEvidence ) ;
182+ if ( evidence . length && Array . isArray ( record ) && conversationDocuments . has ( conversationDocumentKey ( record ) ) ) continue ;
183+ errors . push ( ...evidence ) ;
125184 }
126185 }
127186 return { diagnostics, errors } ;
128187}
129188
130- /** @param {string } content @param {string } [rawSourceEngine] @returns {string } */
131- function agentErrorDiagnosticText ( content , rawSourceEngine ) {
132- const { diagnostics, errors } = collectAgentErrorEvidence ( content , rawSourceEngine ) ;
133- return [ ...diagnostics , ...collectNativeErrorEvidence ( errors ) . diagnostics ] . join ( "\n" ) ;
189+ /**
190+ * @param {string } content
191+ * @param {SessionEvent[] | string } [eventsOrSourceEngine]
192+ * @param {string } [rawSourceEngine]
193+ * @returns {string }
194+ */
195+ function agentErrorDiagnosticText ( content , eventsOrSourceEngine = [ ] , rawSourceEngine ) {
196+ const events = Array . isArray ( eventsOrSourceEngine ) ? eventsOrSourceEngine : [ ] ;
197+ const sourceEngine = typeof eventsOrSourceEngine === "string" ? eventsOrSourceEngine : rawSourceEngine ;
198+ const { diagnostics, errors } = collectAgentErrorEvidence ( content , events , sourceEngine ) ;
199+ return [ ...diagnostics , ...collectNativeErrorEvidence ( [ ...events . flatMap ( recordErrors ) , ...errors ] ) . diagnostics ] . join ( "\n" ) ;
134200}
135201
136202const ENGINE_ERROR_SUMMARIES = Object . freeze ( {
@@ -158,11 +224,14 @@ const ENGINE_ERROR_SUMMARIES = Object.freeze({
158224/**
159225 * Preserve actionable error classes without publishing raw log lines or payloads.
160226 * @param {string } content
227+ * @param {SessionEvent[] | string } [eventsOrSourceEngine]
161228 * @param {string } [rawSourceEngine]
162229 * @returns {string }
163230 */
164- function agentErrorSummaryText ( content , rawSourceEngine ) {
165- const execution = collectAgentExecution ( { content, rawSourceEngine } ) ;
231+ function agentErrorSummaryText ( content , eventsOrSourceEngine = [ ] , rawSourceEngine ) {
232+ const events = Array . isArray ( eventsOrSourceEngine ) ? eventsOrSourceEngine : [ ] ;
233+ const sourceEngine = typeof eventsOrSourceEngine === "string" ? eventsOrSourceEngine : rawSourceEngine ;
234+ const execution = collectAgentExecution ( { content, events, rawSourceEngine : sourceEngine } ) ;
166235 return ( execution ?. data . categories || [ ] )
167236 . filter ( category => Object . hasOwn ( ENGINE_ERROR_SUMMARIES , category ) )
168237 . map ( category => `Engine error: ${ ENGINE_ERROR_SUMMARIES [ category ] } (${ category } )` )
@@ -191,7 +260,7 @@ function collectNativeErrorEvidence(errors) {
191260 continue ;
192261 }
193262 if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) continue ;
194- for ( const key of [ "code" , "error_code" , "errorCode" , "status" , "error_status" , "api_error_status" ] ) {
263+ for ( const key of [ "code" , "error_code" , "errorCode" , "statusCode" , "status_code" , " status", "error_status" , "api_error_status" ] ) {
195264 const code = value [ key ] ;
196265 if ( key === "status" && typeof code === "string" && ! / ^ \d { 3 } $ / . test ( code ) ) continue ;
197266 if ( ( typeof code === "string" && code . length > 0 ) || Number . isSafeInteger ( code ) ) codes . add ( code ) ;
@@ -219,7 +288,7 @@ function collectNativeErrorEvidence(errors) {
219288 */
220289function collectAgentExecution ( { content = "" , events = [ ] , categories = [ ] , exitCode, observations = [ ] , rawSourceEngine } = { } ) {
221290 const categorySet = new Set ( categories ) ;
222- const { diagnostics, errors : rawErrors } = collectAgentErrorEvidence ( content , rawSourceEngine ) ;
291+ const { diagnostics, errors : rawErrors } = collectAgentErrorEvidence ( content , events , rawSourceEngine ) ;
223292 const errors = [ ...events . flatMap ( recordErrors ) , ...rawErrors ] ;
224293 const { diagnostics : nativeDiagnostics , codes, types } = collectNativeErrorEvidence ( errors ) ;
225294 let observedExit = exitCode ;
0 commit comments