@@ -6310,12 +6310,8 @@ export class Agent<
63106310
63116311 #getSuspendedToolCalls( existingSnapshot : WorkflowRunState | null | undefined ) : AgentRunToolCall [ ] {
63126312 const toolCalls : AgentRunToolCall [ ] = [ ] ;
6313- for ( const key in existingSnapshot ?. context ) {
6314- const step = existingSnapshot ?. context [ key ] ;
6315- if ( step ?. status !== 'suspended' ) continue ;
6316- const payload = step . suspendPayload ;
6317- if ( ! payload ) continue ;
63186313
6314+ const collectFromPayload = ( payload : Record < string , any > , stepKey : string ) => {
63196315 if ( payload . requireToolApproval ) {
63206316 toolCalls . push ( {
63216317 toolCallId : payload . requireToolApproval . toolCallId ,
@@ -6325,17 +6321,55 @@ export class Agent<
63256321 } ) ;
63266322 } else if ( payload . toolCallSuspended || payload . toolName || payload . toolCallId ) {
63276323 toolCalls . push ( {
6328- toolCallId : payload . toolCallId ?? this . #findResumeLabelForStep( existingSnapshot , key ) ,
6324+ toolCallId : payload . toolCallId ?? this . #findResumeLabelForStep( existingSnapshot , stepKey ) ,
63296325 toolName : payload . toolName ,
63306326 requiresApproval : false ,
63316327 suspendPayload : payload . toolCallSuspended ,
63326328 } ) ;
63336329 }
6330+ } ;
6331+
6332+ for ( const key in existingSnapshot ?. context ) {
6333+ const step = existingSnapshot ?. context [ key ] ;
6334+ if ( step ?. status !== 'suspended' ) continue ;
6335+ const payload = step . suspendPayload ;
6336+ if ( ! payload ) continue ;
6337+
6338+ // A foreach step (e.g. parallel tool calls in the agentic loop) can park several
6339+ // iterations at once, but its step-level suspendPayload only carries the first
6340+ // suspended iteration. The full set lives in `__workflow_meta.foreachOutput`,
6341+ // where each suspended entry keeps its own per-iteration payload — surface every
6342+ // one of them so all pending tool calls are discoverable and resumable.
6343+ const suspendedIterations = this . #getSuspendedForeachIterations( payload ) ;
6344+ if ( suspendedIterations . length > 0 ) {
6345+ for ( const iteration of suspendedIterations ) {
6346+ collectFromPayload ( iteration . suspendPayload , key ) ;
6347+ }
6348+ } else {
6349+ collectFromPayload ( payload , key ) ;
6350+ }
63346351 }
63356352
63366353 return toolCalls ;
63376354 }
63386355
6356+ #getSuspendedForeachIterations(
6357+ payload : Record < string , any > ,
6358+ ) : { status : 'suspended' ; suspendPayload : Record < string , any > } [ ] {
6359+ // The default engine persists foreach aggregation as an array; the evented
6360+ // engine persists it as an object keyed by iteration index.
6361+ const foreachOutput = payload . __workflow_meta ?. foreachOutput ;
6362+ const entries = Array . isArray ( foreachOutput )
6363+ ? foreachOutput
6364+ : foreachOutput && typeof foreachOutput === 'object'
6365+ ? Object . values ( foreachOutput )
6366+ : [ ] ;
6367+ return entries . filter (
6368+ ( entry : any ) : entry is { status : 'suspended' ; suspendPayload : Record < string , any > } =>
6369+ entry ?. status === 'suspended' && ! ! entry . suspendPayload ,
6370+ ) ;
6371+ }
6372+
63396373 async #validateSuspendedToolCallTarget( {
63406374 snapshot,
63416375 toolCallId,
@@ -6397,9 +6431,19 @@ export class Agent<
63976431
63986432 #getSuspendedToolInfo(
63996433 existingSnapshot : WorkflowRunState | null | undefined ,
6434+ targetToolCallId ?: string ,
64006435 ) : { toolCallId ?: string ; toolName ?: string } | undefined {
6401- const [ first ] = this . #getSuspendedToolCalls( existingSnapshot ) ;
6402- return first ? { toolCallId : first . toolCallId , toolName : first . toolName } : undefined ;
6436+ const suspendedToolCalls = this . #getSuspendedToolCalls( existingSnapshot ) ;
6437+ // Several tool calls can be parked at once. Never label an explicitly targeted
6438+ // resume with a sibling if this snapshot predates the target's persistence.
6439+ const info =
6440+ targetToolCallId !== undefined
6441+ ? ( suspendedToolCalls . find ( toolCall => toolCall . toolCallId === targetToolCallId ) ?? {
6442+ toolCallId : targetToolCallId ,
6443+ toolName : undefined ,
6444+ } )
6445+ : suspendedToolCalls [ 0 ] ;
6446+ return info ? { toolCallId : info . toolCallId , toolName : info . toolName } : undefined ;
64036447 }
64046448
64056449 #getResumeSpanInput( resumeData : unknown , suspendedToolInfo ?: { toolCallId ?: string ; toolName ?: string } ) : unknown {
@@ -6690,7 +6734,9 @@ export class Agent<
66906734 // For resumed runs, surface resumeData as the span input and link the resumed
66916735 // span back to the original suspended trace. Mirrors Workflow.resume tracing.
66926736 const isResume = ! ! resumeContext ;
6693- const suspendedToolInfo = isResume ? this . #getSuspendedToolInfo( resumeContext ?. snapshot ) : undefined ;
6737+ const suspendedToolInfo = isResume
6738+ ? this . #getSuspendedToolInfo( resumeContext ?. snapshot , options . toolCallId )
6739+ : undefined ;
66946740 const persistedTracingContext = isResume
66956741 ? ( resumeContext ?. snapshot ?. tracingContext as
66966742 | { traceId ?: string ; spanId ?: string ; parentSpanId ?: string }
0 commit comments