Skip to content

Commit af01dce

Browse files
fix(terminal): subflow logs rendering (#3189)
1 parent 8a24b56 commit af01dce

File tree

9 files changed

+136
-47
lines changed

9 files changed

+136
-47
lines changed

apps/sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
747747
iterationCurrent: iterationContext.iterationCurrent,
748748
iterationTotal: iterationContext.iterationTotal,
749749
iterationType: iterationContext.iterationType,
750+
iterationContainerId: iterationContext.iterationContainerId,
750751
}),
751752
},
752753
})
@@ -787,6 +788,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
787788
iterationCurrent: iterationContext.iterationCurrent,
788789
iterationTotal: iterationContext.iterationTotal,
789790
iterationType: iterationContext.iterationType,
791+
iterationContainerId: iterationContext.iterationContainerId,
790792
}),
791793
},
792794
})
@@ -815,6 +817,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
815817
iterationCurrent: iterationContext.iterationCurrent,
816818
iterationTotal: iterationContext.iterationTotal,
817819
iterationType: iterationContext.iterationType,
820+
iterationContainerId: iterationContext.iterationContainerId,
818821
}),
819822
},
820823
})

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export interface ExecutionGroup {
161161
*/
162162
interface IterationGroup {
163163
iterationType: string
164+
iterationContainerId: string
164165
iterationCurrent: number
165166
iterationTotal?: number
166167
blocks: ConsoleEntry[]
@@ -169,7 +170,7 @@ interface IterationGroup {
169170

170171
/**
171172
* Builds a tree structure from flat entries.
172-
* Groups iteration entries by (iterationType, iterationCurrent), showing all blocks
173+
* Groups iteration entries by (iterationType, iterationContainerId, iterationCurrent), showing all blocks
173174
* that executed within each iteration.
174175
* Sorts by start time to ensure chronological order.
175176
*/
@@ -186,16 +187,18 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
186187
}
187188
}
188189

189-
// Group iteration entries by (iterationType, iterationCurrent)
190+
// Group iteration entries by (iterationType, iterationContainerId, iterationCurrent)
190191
const iterationGroupsMap = new Map<string, IterationGroup>()
191192
for (const entry of iterationEntries) {
192-
const key = `${entry.iterationType}-${entry.iterationCurrent}`
193+
const iterationContainerId = entry.iterationContainerId || 'unknown'
194+
const key = `${entry.iterationType}-${iterationContainerId}-${entry.iterationCurrent}`
193195
let group = iterationGroupsMap.get(key)
194196
const entryStartMs = new Date(entry.startedAt || entry.timestamp).getTime()
195197

196198
if (!group) {
197199
group = {
198200
iterationType: entry.iterationType!,
201+
iterationContainerId,
199202
iterationCurrent: entry.iterationCurrent!,
200203
iterationTotal: entry.iterationTotal,
201204
blocks: [],
@@ -220,26 +223,34 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
220223
group.blocks.sort((a, b) => a.executionOrder - b.executionOrder)
221224
}
222225

223-
// Group iterations by iterationType to create subflow parents
224-
const subflowGroups = new Map<string, IterationGroup[]>()
226+
// Group iterations by (iterationType, iterationContainerId) to create subflow parents
227+
const subflowGroups = new Map<
228+
string,
229+
{ iterationType: string; iterationContainerId: string; groups: IterationGroup[] }
230+
>()
225231
for (const group of iterationGroupsMap.values()) {
226-
const type = group.iterationType
227-
let groups = subflowGroups.get(type)
228-
if (!groups) {
229-
groups = []
230-
subflowGroups.set(type, groups)
232+
const key = `${group.iterationType}-${group.iterationContainerId}`
233+
let subflowGroup = subflowGroups.get(key)
234+
if (!subflowGroup) {
235+
subflowGroup = {
236+
iterationType: group.iterationType,
237+
iterationContainerId: group.iterationContainerId,
238+
groups: [],
239+
}
240+
subflowGroups.set(key, subflowGroup)
231241
}
232-
groups.push(group)
242+
subflowGroup.groups.push(group)
233243
}
234244

235245
// Sort iterations within each subflow by iteration number
236-
for (const groups of subflowGroups.values()) {
237-
groups.sort((a, b) => a.iterationCurrent - b.iterationCurrent)
246+
for (const subflowGroup of subflowGroups.values()) {
247+
subflowGroup.groups.sort((a, b) => a.iterationCurrent - b.iterationCurrent)
238248
}
239249

240250
// Build subflow nodes with iteration children
241251
const subflowNodes: EntryNode[] = []
242-
for (const [iterationType, iterationGroups] of subflowGroups.entries()) {
252+
for (const subflowGroup of subflowGroups.values()) {
253+
const { iterationType, iterationContainerId, groups: iterationGroups } = subflowGroup
243254
// Calculate subflow timing from all its iterations
244255
const firstIteration = iterationGroups[0]
245256
const allBlocks = iterationGroups.flatMap((g) => g.blocks)
@@ -255,10 +266,10 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
255266
// Use the minimum executionOrder from all child blocks for proper ordering
256267
const subflowExecutionOrder = Math.min(...allBlocks.map((b) => b.executionOrder))
257268
const syntheticSubflow: ConsoleEntry = {
258-
id: `subflow-${iterationType}-${firstIteration.blocks[0]?.executionId || 'unknown'}`,
269+
id: `subflow-${iterationType}-${iterationContainerId}-${firstIteration.blocks[0]?.executionId || 'unknown'}`,
259270
timestamp: new Date(subflowStartMs).toISOString(),
260271
workflowId: firstIteration.blocks[0]?.workflowId || '',
261-
blockId: `${iterationType}-container`,
272+
blockId: `${iterationType}-container-${iterationContainerId}`,
262273
blockName: iterationType.charAt(0).toUpperCase() + iterationType.slice(1),
263274
blockType: iterationType,
264275
executionId: firstIteration.blocks[0]?.executionId,
@@ -284,10 +295,10 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
284295
// Use the minimum executionOrder from blocks in this iteration
285296
const iterExecutionOrder = Math.min(...iterBlocks.map((b) => b.executionOrder))
286297
const syntheticIteration: ConsoleEntry = {
287-
id: `iteration-${iterationType}-${iterGroup.iterationCurrent}-${iterBlocks[0]?.executionId || 'unknown'}`,
298+
id: `iteration-${iterationType}-${iterGroup.iterationContainerId}-${iterGroup.iterationCurrent}-${iterBlocks[0]?.executionId || 'unknown'}`,
288299
timestamp: new Date(iterStartMs).toISOString(),
289300
workflowId: iterBlocks[0]?.workflowId || '',
290-
blockId: `iteration-${iterGroup.iterationCurrent}`,
301+
blockId: `iteration-${iterGroup.iterationContainerId}-${iterGroup.iterationCurrent}`,
291302
blockName: `Iteration ${iterGroup.iterationCurrent}${iterGroup.iterationTotal !== undefined ? ` / ${iterGroup.iterationTotal}` : ''}`,
292303
blockType: iterationType,
293304
executionId: iterBlocks[0]?.executionId,
@@ -299,6 +310,7 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
299310
iterationCurrent: iterGroup.iterationCurrent,
300311
iterationTotal: iterGroup.iterationTotal,
301312
iterationType: iterationType as 'loop' | 'parallel',
313+
iterationContainerId: iterGroup.iterationContainerId,
302314
}
303315

304316
// Block nodes within this iteration

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ export function useWorkflowExecution() {
365365
iterationCurrent: data.iterationCurrent,
366366
iterationTotal: data.iterationTotal,
367367
iterationType: data.iterationType,
368+
iterationContainerId: data.iterationContainerId,
368369
})
369370
}
370371

@@ -387,13 +388,15 @@ export function useWorkflowExecution() {
387388
iterationCurrent: data.iterationCurrent,
388389
iterationTotal: data.iterationTotal,
389390
iterationType: data.iterationType,
391+
iterationContainerId: data.iterationContainerId,
390392
})
391393
}
392394

393395
const updateConsoleEntry = (data: BlockCompletedData) => {
394396
updateConsole(
395397
data.blockId,
396398
{
399+
executionOrder: data.executionOrder,
397400
input: data.input || {},
398401
replaceOutput: data.output,
399402
success: true,
@@ -404,6 +407,7 @@ export function useWorkflowExecution() {
404407
iterationCurrent: data.iterationCurrent,
405408
iterationTotal: data.iterationTotal,
406409
iterationType: data.iterationType,
410+
iterationContainerId: data.iterationContainerId,
407411
},
408412
executionId
409413
)
@@ -413,6 +417,7 @@ export function useWorkflowExecution() {
413417
updateConsole(
414418
data.blockId,
415419
{
420+
executionOrder: data.executionOrder,
416421
input: data.input || {},
417422
replaceOutput: {},
418423
success: false,
@@ -424,6 +429,7 @@ export function useWorkflowExecution() {
424429
iterationCurrent: data.iterationCurrent,
425430
iterationTotal: data.iterationTotal,
426431
iterationType: data.iterationType,
432+
iterationContainerId: data.iterationContainerId,
427433
},
428434
executionId
429435
)
@@ -453,6 +459,7 @@ export function useWorkflowExecution() {
453459
iterationCurrent: data.iterationCurrent,
454460
iterationTotal: data.iterationTotal,
455461
iterationType: data.iterationType,
462+
iterationContainerId: data.iterationContainerId,
456463
})
457464
}
458465

@@ -921,6 +928,7 @@ export function useWorkflowExecution() {
921928
useTerminalConsoleStore.getState().updateConsole(
922929
log.blockId,
923930
{
931+
executionOrder: log.executionOrder,
924932
replaceOutput: log.output,
925933
success: true,
926934
},

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export async function executeWorkflowWithFullLogging(
137137
iterationCurrent: event.data.iterationCurrent,
138138
iterationTotal: event.data.iterationTotal,
139139
iterationType: event.data.iterationType,
140+
iterationContainerId: event.data.iterationContainerId,
140141
})
141142

142143
if (options.onBlockComplete) {
@@ -167,6 +168,7 @@ export async function executeWorkflowWithFullLogging(
167168
iterationCurrent: event.data.iterationCurrent,
168169
iterationTotal: event.data.iterationTotal,
169170
iterationType: event.data.iterationType,
171+
iterationContainerId: event.data.iterationContainerId,
170172
})
171173
break
172174

apps/sim/executor/execution/block-executor.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import {
1717
} from '@/executor/constants'
1818
import type { DAGNode } from '@/executor/dag/builder'
1919
import { ChildWorkflowError } from '@/executor/errors/child-workflow-error'
20-
import type { BlockStateWriter, ContextExtensions } from '@/executor/execution/types'
20+
import type {
21+
BlockStateWriter,
22+
ContextExtensions,
23+
IterationContext,
24+
} from '@/executor/execution/types'
2125
import {
2226
generatePauseContextId,
2327
mapNodeMetadataToPauseScopes,
@@ -473,28 +477,41 @@ export class BlockExecutor {
473477
}
474478
}
475479

476-
private getIterationContext(
477-
ctx: ExecutionContext,
478-
node: DAGNode
479-
): { iterationCurrent: number; iterationTotal: number; iterationType: SubflowType } | undefined {
480+
private createIterationContext(
481+
iterationCurrent: number,
482+
iterationType: SubflowType,
483+
iterationContainerId?: string,
484+
iterationTotal?: number
485+
): IterationContext {
486+
return {
487+
iterationCurrent,
488+
iterationTotal,
489+
iterationType,
490+
iterationContainerId,
491+
}
492+
}
493+
494+
private getIterationContext(ctx: ExecutionContext, node: DAGNode): IterationContext | undefined {
480495
if (!node?.metadata) return undefined
481496

482-
if (node.metadata.branchIndex !== undefined && node.metadata.branchTotal) {
483-
return {
484-
iterationCurrent: node.metadata.branchIndex,
485-
iterationTotal: node.metadata.branchTotal,
486-
iterationType: 'parallel',
487-
}
497+
if (node.metadata.branchIndex !== undefined && node.metadata.branchTotal !== undefined) {
498+
return this.createIterationContext(
499+
node.metadata.branchIndex,
500+
'parallel',
501+
node.metadata.parallelId,
502+
node.metadata.branchTotal
503+
)
488504
}
489505

490506
if (node.metadata.isLoopNode && node.metadata.loopId) {
491507
const loopScope = ctx.loopExecutions?.get(node.metadata.loopId)
492-
if (loopScope && loopScope.iteration !== undefined && loopScope.maxIterations) {
493-
return {
494-
iterationCurrent: loopScope.iteration,
495-
iterationTotal: loopScope.maxIterations,
496-
iterationType: 'loop',
497-
}
508+
if (loopScope && loopScope.iteration !== undefined) {
509+
return this.createIterationContext(
510+
loopScope.iteration,
511+
'loop',
512+
node.metadata.loopId,
513+
loopScope.maxIterations
514+
)
498515
}
499516
}
500517

apps/sim/executor/execution/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ export interface SerializableExecutionState {
4949

5050
export interface IterationContext {
5151
iterationCurrent: number
52-
iterationTotal: number
52+
iterationTotal?: number
5353
iterationType: SubflowType
54+
iterationContainerId?: string
5455
}
5556

5657
export interface ExecutionCallbacks {

apps/sim/lib/workflows/executor/execution-events.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export interface BlockStartedEvent extends BaseExecutionEvent {
8080
iterationCurrent?: number
8181
iterationTotal?: number
8282
iterationType?: SubflowType
83+
iterationContainerId?: string
8384
}
8485
}
8586

@@ -102,6 +103,7 @@ export interface BlockCompletedEvent extends BaseExecutionEvent {
102103
iterationCurrent?: number
103104
iterationTotal?: number
104105
iterationType?: SubflowType
106+
iterationContainerId?: string
105107
}
106108
}
107109

@@ -124,6 +126,7 @@ export interface BlockErrorEvent extends BaseExecutionEvent {
124126
iterationCurrent?: number
125127
iterationTotal?: number
126128
iterationType?: SubflowType
129+
iterationContainerId?: string
127130
}
128131
}
129132

@@ -219,7 +222,12 @@ export function createSSECallbacks(options: SSECallbackOptions) {
219222
blockName: string,
220223
blockType: string,
221224
executionOrder: number,
222-
iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string }
225+
iterationContext?: {
226+
iterationCurrent: number
227+
iterationTotal?: number
228+
iterationType: string
229+
iterationContainerId?: string
230+
}
223231
) => {
224232
sendEvent({
225233
type: 'block:started',
@@ -235,6 +243,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
235243
iterationCurrent: iterationContext.iterationCurrent,
236244
iterationTotal: iterationContext.iterationTotal,
237245
iterationType: iterationContext.iterationType as any,
246+
iterationContainerId: iterationContext.iterationContainerId,
238247
}),
239248
},
240249
})
@@ -252,14 +261,20 @@ export function createSSECallbacks(options: SSECallbackOptions) {
252261
executionOrder: number
253262
endedAt: string
254263
},
255-
iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string }
264+
iterationContext?: {
265+
iterationCurrent: number
266+
iterationTotal?: number
267+
iterationType: string
268+
iterationContainerId?: string
269+
}
256270
) => {
257271
const hasError = callbackData.output?.error
258272
const iterationData = iterationContext
259273
? {
260274
iterationCurrent: iterationContext.iterationCurrent,
261275
iterationTotal: iterationContext.iterationTotal,
262276
iterationType: iterationContext.iterationType as any,
277+
iterationContainerId: iterationContext.iterationContainerId,
263278
}
264279
: {}
265280

0 commit comments

Comments
 (0)