From 1c463ac81f23ecd2e802d91654591f04cde09d78 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 17:30:17 -0800 Subject: [PATCH 1/4] fix(terminal): thread executionOrder through child workflow SSE events for loop support --- apps/sim/app/api/workflows/[id]/execute/route.ts | 4 +++- .../w/[workflowId]/hooks/use-workflow-execution.ts | 2 ++ apps/sim/executor/execution/block-executor.ts | 2 +- apps/sim/executor/execution/types.ts | 6 ++++-- apps/sim/executor/handlers/workflow/workflow-handler.ts | 9 ++++++++- apps/sim/executor/types.ts | 4 +++- apps/sim/lib/workflows/executor/execution-events.ts | 5 ++++- 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index cf8431f9dc..b393ae492a 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -987,7 +987,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: const onChildWorkflowInstanceReady = ( blockId: string, childWorkflowInstanceId: string, - iterationContext?: IterationContext + iterationContext?: IterationContext, + executionOrder?: number ) => { sendEvent({ type: 'block:childWorkflowStarted', @@ -1001,6 +1002,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: iterationCurrent: iterationContext.iterationCurrent, iterationContainerId: iterationContext.iterationContainerId, }), + ...(executionOrder !== undefined && { executionOrder }), }, }) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index f1bcca15b7..1af79967c8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -554,6 +554,7 @@ export function useWorkflowExecution() { childWorkflowInstanceId: string iterationCurrent?: number iterationContainerId?: string + executionOrder?: number }) => { if (isStaleExecution()) return updateConsole( @@ -564,6 +565,7 @@ export function useWorkflowExecution() { ...(data.iterationContainerId !== undefined && { iterationContainerId: data.iterationContainerId, }), + ...(data.executionOrder !== undefined && { executionOrder: data.executionOrder }), }, executionIdRef.current ) diff --git a/apps/sim/executor/execution/block-executor.ts b/apps/sim/executor/execution/block-executor.ts index 6d7f746d4e..761d089934 100644 --- a/apps/sim/executor/execution/block-executor.ts +++ b/apps/sim/executor/execution/block-executor.ts @@ -80,7 +80,7 @@ export class BlockExecutor { const startTime = performance.now() let resolvedInputs: Record = {} - const nodeMetadata = this.buildNodeMetadata(node) + const nodeMetadata = { ...this.buildNodeMetadata(node), executionOrder: blockLog?.executionOrder } let cleanupSelfReference: (() => void) | undefined if (block.metadata?.id === BlockType.HUMAN_IN_THE_LOOP) { diff --git a/apps/sim/executor/execution/types.ts b/apps/sim/executor/execution/types.ts index d3ac877b89..bea082fe8a 100644 --- a/apps/sim/executor/execution/types.ts +++ b/apps/sim/executor/execution/types.ts @@ -89,7 +89,8 @@ export interface ExecutionCallbacks { onChildWorkflowInstanceReady?: ( blockId: string, childWorkflowInstanceId: string, - iterationContext?: IterationContext + iterationContext?: IterationContext, + executionOrder?: number ) => void } @@ -155,7 +156,8 @@ export interface ContextExtensions { onChildWorkflowInstanceReady?: ( blockId: string, childWorkflowInstanceId: string, - iterationContext?: IterationContext + iterationContext?: IterationContext, + executionOrder?: number ) => void /** diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index 596e879683..b0123d7844 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -62,6 +62,7 @@ export class WorkflowBlockHandler implements BlockHandler { branchTotal?: number originalBlockId?: string isLoopNode?: boolean + executionOrder?: number } ): Promise { return this._executeCore(ctx, block, inputs, nodeMetadata) @@ -79,6 +80,7 @@ export class WorkflowBlockHandler implements BlockHandler { branchTotal?: number originalBlockId?: string isLoopNode?: boolean + executionOrder?: number } ): Promise { logger.info(`Executing workflow block: ${block.id}`) @@ -169,7 +171,12 @@ export class WorkflowBlockHandler implements BlockHandler { const iterationContext = nodeMetadata ? this.getIterationContext(ctx, nodeMetadata) : undefined - ctx.onChildWorkflowInstanceReady?.(effectiveBlockId, instanceId, iterationContext) + ctx.onChildWorkflowInstanceReady?.( + effectiveBlockId, + instanceId, + iterationContext, + nodeMetadata?.executionOrder + ) } const subExecutor = new Executor({ diff --git a/apps/sim/executor/types.ts b/apps/sim/executor/types.ts index b30dba2e1b..bf672dfd94 100644 --- a/apps/sim/executor/types.ts +++ b/apps/sim/executor/types.ts @@ -264,7 +264,8 @@ export interface ExecutionContext { onChildWorkflowInstanceReady?: ( blockId: string, childWorkflowInstanceId: string, - iterationContext?: IterationContext + iterationContext?: IterationContext, + executionOrder?: number ) => void /** @@ -377,6 +378,7 @@ export interface BlockHandler { branchTotal?: number originalBlockId?: string isLoopNode?: boolean + executionOrder?: number } ) => Promise } diff --git a/apps/sim/lib/workflows/executor/execution-events.ts b/apps/sim/lib/workflows/executor/execution-events.ts index 09044acdc5..a9324d1087 100644 --- a/apps/sim/lib/workflows/executor/execution-events.ts +++ b/apps/sim/lib/workflows/executor/execution-events.ts @@ -155,6 +155,7 @@ export interface BlockChildWorkflowStartedEvent extends BaseExecutionEvent { childWorkflowInstanceId: string iterationCurrent?: number iterationContainerId?: string + executionOrder?: number } } @@ -396,7 +397,8 @@ export function createSSECallbacks(options: SSECallbackOptions) { const onChildWorkflowInstanceReady = ( blockId: string, childWorkflowInstanceId: string, - iterationContext?: IterationContext + iterationContext?: IterationContext, + executionOrder?: number ) => { sendEvent({ type: 'block:childWorkflowStarted', @@ -410,6 +412,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { iterationCurrent: iterationContext.iterationCurrent, iterationContainerId: iterationContext.iterationContainerId, }), + ...(executionOrder !== undefined && { executionOrder }), }, }) } From a0822dcac30a7251892397c16cfb2f04bbfec6d0 Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 25 Feb 2026 17:31:49 -0800 Subject: [PATCH 2/4] ran lint --- apps/sim/executor/execution/block-executor.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/executor/execution/block-executor.ts b/apps/sim/executor/execution/block-executor.ts index 761d089934..9325aa2861 100644 --- a/apps/sim/executor/execution/block-executor.ts +++ b/apps/sim/executor/execution/block-executor.ts @@ -80,7 +80,10 @@ export class BlockExecutor { const startTime = performance.now() let resolvedInputs: Record = {} - const nodeMetadata = { ...this.buildNodeMetadata(node), executionOrder: blockLog?.executionOrder } + const nodeMetadata = { + ...this.buildNodeMetadata(node), + executionOrder: blockLog?.executionOrder, + } let cleanupSelfReference: (() => void) | undefined if (block.metadata?.id === BlockType.HUMAN_IN_THE_LOOP) { From 289e5f9b8b980bade308dde26ad564134dd818c9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 17:35:56 -0800 Subject: [PATCH 3/4] fix(terminal): render iteration children through EntryNodeRow for workflow block expansion IterationNodeRow was rendering all children as flat BlockRow components, ignoring nodeType. Workflow blocks inside loop iterations were never rendered as WorkflowNodeRow, so they had no expand chevron or child tree. --- .../components/terminal/terminal.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx index 9d9b206b79..204ca166c8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx @@ -160,12 +160,16 @@ const IterationNodeRow = memo(function IterationNodeRow({ onSelectEntry, isExpanded, onToggle, + expandedNodes, + onToggleNode, }: { node: EntryNode selectedEntryId: string | null onSelectEntry: (entry: ConsoleEntry) => void isExpanded: boolean onToggle: () => void + expandedNodes: Set + onToggleNode: (nodeId: string) => void }) { const { entry, children, iterationInfo } = node const hasError = Boolean(entry.error) || children.some((c) => c.entry.error) @@ -226,11 +230,13 @@ const IterationNodeRow = memo(function IterationNodeRow({ {isExpanded && hasChildren && (
{children.map((child) => ( - ))}
@@ -346,6 +352,8 @@ const SubflowNodeRow = memo(function SubflowNodeRow({ onSelectEntry={onSelectEntry} isExpanded={expandedNodes.has(iterNode.entry.id)} onToggle={() => onToggleNode(iterNode.entry.id)} + expandedNodes={expandedNodes} + onToggleNode={onToggleNode} /> ))} @@ -520,6 +528,8 @@ const EntryNodeRow = memo(function EntryNodeRow({ onSelectEntry={onSelectEntry} isExpanded={expandedNodes.has(node.entry.id)} onToggle={() => onToggleNode(node.entry.id)} + expandedNodes={expandedNodes} + onToggleNode={onToggleNode} /> ) } From 42127d280b392bdf222642e51dde57380295699e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 17:41:39 -0800 Subject: [PATCH 4/4] fix(terminal): add childWorkflowBlockId to matchesEntryForUpdate Sub-executors reset executionOrderCounter, so child blocks across loop iterations share the same blockId + executionOrder. Without checking childWorkflowBlockId, updateConsole for iteration N overwrites entries from iterations 0..N-1, causing all child blocks to be grouped under the last iteration's workflow instance. --- apps/sim/stores/terminal/console/store.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/sim/stores/terminal/console/store.ts b/apps/sim/stores/terminal/console/store.ts index e3b7f3ea10..3468e4d212 100644 --- a/apps/sim/stores/terminal/console/store.ts +++ b/apps/sim/stores/terminal/console/store.ts @@ -91,6 +91,13 @@ const matchesEntryForUpdate = ( return false } + if ( + update.childWorkflowBlockId !== undefined && + entry.childWorkflowBlockId !== update.childWorkflowBlockId + ) { + return false + } + return true }