11import { generateInternalToken } from '@/lib/auth/internal'
22import { createLogger } from '@/lib/logs/console/logger'
3+ import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
34import { getBaseUrl } from '@/lib/urls/utils'
45import type { BlockOutput } from '@/blocks/types'
56import { Executor } from '@/executor'
@@ -104,18 +105,17 @@ export class WorkflowBlockHandler implements BlockHandler {
104105 // Remove current execution from stack after completion
105106 WorkflowBlockHandler . executionStack . delete ( executionId )
106107
107- // Log execution completion
108108 logger . info ( `Child workflow ${ childWorkflowName } completed in ${ Math . round ( duration ) } ms` )
109109
110- // Map child workflow output to parent block output
110+ const childTraceSpans = this . captureChildWorkflowLogs ( result , childWorkflowName , context )
111111 const mappedResult = this . mapChildOutputToParent (
112112 result ,
113113 workflowId ,
114114 childWorkflowName ,
115- duration
115+ duration ,
116+ childTraceSpans
116117 )
117118
118- // If the child workflow failed, throw an error to trigger proper error handling in the parent
119119 if ( ( mappedResult as any ) . success === false ) {
120120 const childError = ( mappedResult as any ) . error || 'Unknown error'
121121 throw new Error ( `Error in child workflow "${ childWorkflowName } ": ${ childError } ` )
@@ -125,19 +125,13 @@ export class WorkflowBlockHandler implements BlockHandler {
125125 } catch ( error : any ) {
126126 logger . error ( `Error executing child workflow ${ workflowId } :` , error )
127127
128- // Clean up execution stack in case of error
129128 const executionId = `${ context . workflowId } _sub_${ workflowId } _${ block . id } `
130129 WorkflowBlockHandler . executionStack . delete ( executionId )
131-
132- // Get workflow name for error reporting
133130 const { workflows } = useWorkflowRegistry . getState ( )
134131 const workflowMetadata = workflows [ workflowId ]
135132 const childWorkflowName = workflowMetadata ?. name || workflowId
136133
137- // Enhance error message with child workflow context
138134 const originalError = error . message || 'Unknown error'
139-
140- // Check if error message already has child workflow context to avoid duplication
141135 if ( originalError . startsWith ( 'Error in child workflow' ) ) {
142136 throw error // Re-throw as-is to avoid duplication
143137 }
@@ -151,12 +145,9 @@ export class WorkflowBlockHandler implements BlockHandler {
151145 */
152146 private async loadChildWorkflow ( workflowId : string ) {
153147 try {
154- // Fetch workflow from API with internal authentication header
155148 const headers : Record < string , string > = {
156149 'Content-Type' : 'application/json' ,
157150 }
158-
159- // Add internal auth header for server-side calls
160151 if ( typeof window === 'undefined' ) {
161152 const token = await generateInternalToken ( )
162153 headers . Authorization = `Bearer ${ token } `
@@ -182,16 +173,12 @@ export class WorkflowBlockHandler implements BlockHandler {
182173 }
183174
184175 logger . info ( `Loaded child workflow: ${ workflowData . name } (${ workflowId } )` )
185-
186- // Extract the workflow state (API returns normalized data in state field)
187176 const workflowState = workflowData . state
188177
189178 if ( ! workflowState || ! workflowState . blocks ) {
190179 logger . error ( `Child workflow ${ workflowId } has invalid state` )
191180 return null
192181 }
193-
194- // Use blocks directly since API returns data from normalized tables
195182 const serializedWorkflow = this . serializer . serializeWorkflow (
196183 workflowState . blocks ,
197184 workflowState . edges || [ ] ,
@@ -222,17 +209,101 @@ export class WorkflowBlockHandler implements BlockHandler {
222209 }
223210
224211 /**
225- * Maps child workflow output to parent block output format
212+ * Captures and transforms child workflow logs into trace spans
213+ */
214+ private captureChildWorkflowLogs (
215+ childResult : any ,
216+ childWorkflowName : string ,
217+ parentContext : ExecutionContext
218+ ) : any [ ] {
219+ try {
220+ if ( ! childResult . logs || ! Array . isArray ( childResult . logs ) ) {
221+ return [ ]
222+ }
223+
224+ const { traceSpans } = buildTraceSpans ( childResult )
225+
226+ if ( ! traceSpans || traceSpans . length === 0 ) {
227+ return [ ]
228+ }
229+
230+ const transformedSpans = traceSpans . map ( ( span : any ) => {
231+ return this . transformSpanForChildWorkflow ( span , childWorkflowName )
232+ } )
233+
234+ return transformedSpans
235+ } catch ( error ) {
236+ logger . error ( `Error capturing child workflow logs for ${ childWorkflowName } :` , error )
237+ return [ ]
238+ }
239+ }
240+
241+ /**
242+ * Transforms trace span for child workflow context
243+ */
244+ private transformSpanForChildWorkflow ( span : any , childWorkflowName : string ) : any {
245+ const transformedSpan = {
246+ ...span ,
247+ name : this . cleanChildSpanName ( span . name , childWorkflowName ) ,
248+ metadata : {
249+ ...span . metadata ,
250+ isFromChildWorkflow : true ,
251+ childWorkflowName,
252+ } ,
253+ }
254+
255+ if ( span . children && Array . isArray ( span . children ) ) {
256+ transformedSpan . children = span . children . map ( ( childSpan : any ) =>
257+ this . transformSpanForChildWorkflow ( childSpan , childWorkflowName )
258+ )
259+ }
260+
261+ if ( span . output ?. childTraceSpans ) {
262+ transformedSpan . output = {
263+ ...transformedSpan . output ,
264+ childTraceSpans : span . output . childTraceSpans ,
265+ }
266+ }
267+
268+ return transformedSpan
269+ }
270+
271+ /**
272+ * Cleans up child span names for readability
273+ */
274+ private cleanChildSpanName ( spanName : string , childWorkflowName : string ) : string {
275+ if ( spanName . includes ( `${ childWorkflowName } :` ) ) {
276+ const cleanName = spanName . replace ( `${ childWorkflowName } :` , '' ) . trim ( )
277+
278+ if ( cleanName === 'Workflow Execution' ) {
279+ return `${ childWorkflowName } workflow`
280+ }
281+
282+ if ( cleanName . startsWith ( 'Agent ' ) ) {
283+ return `${ cleanName } `
284+ }
285+
286+ return `${ cleanName } `
287+ }
288+
289+ if ( spanName === 'Workflow Execution' ) {
290+ return `${ childWorkflowName } workflow`
291+ }
292+
293+ return `${ spanName } `
294+ }
295+
296+ /**
297+ * Maps child workflow output to parent block output
226298 */
227299 private mapChildOutputToParent (
228300 childResult : any ,
229301 childWorkflowId : string ,
230302 childWorkflowName : string ,
231- duration : number
303+ duration : number ,
304+ childTraceSpans ?: any [ ]
232305 ) : BlockOutput {
233306 const success = childResult . success !== false
234-
235- // If child workflow failed, return minimal output
236307 if ( ! success ) {
237308 logger . warn ( `Child workflow ${ childWorkflowName } failed` )
238309 return {
@@ -241,18 +312,15 @@ export class WorkflowBlockHandler implements BlockHandler {
241312 error : childResult . error || 'Child workflow execution failed' ,
242313 } as Record < string , any >
243314 }
244-
245- // Extract the actual result content from the flattened structure
246315 let result = childResult
247316 if ( childResult ?. output ) {
248317 result = childResult . output
249318 }
250-
251- // Return a properly structured response with all required fields
252319 return {
253320 success : true ,
254321 childWorkflowName,
255322 result,
323+ childTraceSpans : childTraceSpans || [ ] ,
256324 } as Record < string , any >
257325 }
258326}
0 commit comments