Skip to content

Commit a6e57b5

Browse files
icecrasher321Royce Christon
authored andcommitted
fix(terminal): subflow logs rendering (simstudioai#3189)
1 parent b6f2acd commit a6e57b5

9 files changed

Lines changed: 136 additions & 47 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
791791
iterationCurrent: iterationContext.iterationCurrent,
792792
iterationTotal: iterationContext.iterationTotal,
793793
iterationType: iterationContext.iterationType,
794+
iterationContainerId: iterationContext.iterationContainerId,
794795
}),
795796
},
796797
})
@@ -831,6 +832,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
831832
iterationCurrent: iterationContext.iterationCurrent,
832833
iterationTotal: iterationContext.iterationTotal,
833834
iterationType: iterationContext.iterationType,
835+
iterationContainerId: iterationContext.iterationContainerId,
834836
}),
835837
},
836838
})
@@ -859,6 +861,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
859861
iterationCurrent: iterationContext.iterationCurrent,
860862
iterationTotal: iterationContext.iterationTotal,
861863
iterationType: iterationContext.iterationType,
864+
iterationContainerId: iterationContext.iterationContainerId,
862865
}),
863866
},
864867
})

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
@@ -18,7 +18,11 @@ import {
1818
} from '@/executor/constants'
1919
import type { DAGNode } from '@/executor/dag/builder'
2020
import { ChildWorkflowError } from '@/executor/errors/child-workflow-error'
21-
import type { BlockStateWriter, ContextExtensions } from '@/executor/execution/types'
21+
import type {
22+
BlockStateWriter,
23+
ContextExtensions,
24+
IterationContext,
25+
} from '@/executor/execution/types'
2226
import {
2327
generatePauseContextId,
2428
mapNodeMetadataToPauseScopes,
@@ -598,28 +602,41 @@ export class BlockExecutor {
598602
}
599603
}
600604

601-
private getIterationContext(
602-
ctx: ExecutionContext,
603-
node: DAGNode
604-
): { iterationCurrent: number; iterationTotal: number; iterationType: SubflowType } | undefined {
605+
private createIterationContext(
606+
iterationCurrent: number,
607+
iterationType: SubflowType,
608+
iterationContainerId?: string,
609+
iterationTotal?: number
610+
): IterationContext {
611+
return {
612+
iterationCurrent,
613+
iterationTotal,
614+
iterationType,
615+
iterationContainerId,
616+
}
617+
}
618+
619+
private getIterationContext(ctx: ExecutionContext, node: DAGNode): IterationContext | undefined {
605620
if (!node?.metadata) return undefined
606621

607-
if (node.metadata.branchIndex !== undefined && node.metadata.branchTotal) {
608-
return {
609-
iterationCurrent: node.metadata.branchIndex,
610-
iterationTotal: node.metadata.branchTotal,
611-
iterationType: 'parallel',
612-
}
622+
if (node.metadata.branchIndex !== undefined && node.metadata.branchTotal !== undefined) {
623+
return this.createIterationContext(
624+
node.metadata.branchIndex,
625+
'parallel',
626+
node.metadata.parallelId,
627+
node.metadata.branchTotal
628+
)
613629
}
614630

615631
if (node.metadata.isLoopNode && node.metadata.loopId) {
616632
const loopScope = ctx.loopExecutions?.get(node.metadata.loopId)
617-
if (loopScope && loopScope.iteration !== undefined && loopScope.maxIterations) {
618-
return {
619-
iterationCurrent: loopScope.iteration,
620-
iterationTotal: loopScope.maxIterations,
621-
iterationType: 'loop',
622-
}
633+
if (loopScope && loopScope.iteration !== undefined) {
634+
return this.createIterationContext(
635+
loopScope.iteration,
636+
'loop',
637+
node.metadata.loopId,
638+
loopScope.maxIterations
639+
)
623640
}
624641
}
625642

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)