Skip to content

Commit e07b1ff

Browse files
improvement(executor): subflows, hitl handling cleanup (#4604)
* improvement(subflows): orchestration consolidation * address comments * fix hitl cases * address comments * subflow results output extraction * hitl fallback case * more cleanup * add test * fix type issue * add test case for hitl resume * address comments * fix test * fix snapshot for nested subflows
1 parent bc99c45 commit e07b1ff

54 files changed

Lines changed: 5130 additions & 837 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/preview-workflow.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,24 @@ export function getLeftmostBlockId(workflowState: WorkflowState | null | undefin
108108
/** Execution status for edges/nodes in the preview */
109109
type ExecutionStatus = 'success' | 'error' | 'not-executed'
110110

111-
/** Calculates absolute position, handling nested subflows. */
112-
function calculateAbsolutePosition(
111+
/** Calculates nesting depth, handling nested subflows. */
112+
function calculateNestingDepth(
113113
block: BlockState,
114-
blocks: Record<string, BlockState>
115-
): { x: number; y: number } {
116-
if (!block.data?.parentId) {
117-
return block.position
118-
}
119-
120-
const parentBlock = blocks[block.data.parentId]
114+
blocks: Record<string, BlockState>,
115+
visited: Set<string> = new Set()
116+
): number {
117+
const parentId = block.data?.parentId
118+
if (!parentId) return 0
119+
if (visited.has(parentId)) return 0
120+
121+
const parentBlock = blocks[parentId]
121122
if (!parentBlock) {
122-
logger.warn(`Parent block not found for child block`)
123-
return block.position
123+
logger.warn('Parent block not found for child block')
124+
return 0
124125
}
125126

126-
const parentAbsolutePosition = calculateAbsolutePosition(parentBlock, blocks)
127-
return {
128-
x: parentAbsolutePosition.x + block.position.x,
129-
y: parentAbsolutePosition.y + block.position.y,
130-
}
127+
visited.add(parentId)
128+
return 1 + calculateNestingDepth(parentBlock, blocks, visited)
131129
}
132130

133131
interface PreviewWorkflowProps {
@@ -329,7 +327,8 @@ export function PreviewWorkflow({
329327

330328
if (childStatuses.length === 0) return undefined
331329
if (childStatuses.some((s) => s === 'error')) return 'error'
332-
return 'success'
330+
if (childStatuses.every((s) => s === 'success')) return 'success'
331+
return undefined
333332
}
334333
return derive
335334
}, [subflowChildrenMap, blockExecutionMap, workflowState.blocks])
@@ -367,13 +366,20 @@ export function PreviewWorkflow({
367366

368367
const nodeArray: Node[] = []
369368

370-
Object.entries(workflowState.blocks || {}).forEach(([blockId, block]) => {
369+
const sortedBlocks = Object.entries(workflowState.blocks || {}).sort(
370+
([, left], [, right]) =>
371+
calculateNestingDepth(left, workflowState.blocks) -
372+
calculateNestingDepth(right, workflowState.blocks)
373+
)
374+
375+
sortedBlocks.forEach(([blockId, block]) => {
371376
if (!block || !block.type) {
372377
logger.warn(`Skipping invalid block: ${blockId}`)
373378
return
374379
}
375380

376-
const absolutePosition = calculateAbsolutePosition(block, workflowState.blocks)
381+
const parentId = block.data?.parentId
382+
const nestingDepth = calculateNestingDepth(block, workflowState.blocks)
377383

378384
if (block.type === 'loop' || block.type === 'parallel') {
379385
const isSelected = selectedBlockId === blockId
@@ -391,9 +397,14 @@ export function PreviewWorkflow({
391397
nodeArray.push({
392398
id: blockId,
393399
type: 'subflowNode',
394-
position: absolutePosition,
400+
position: block.position,
401+
parentId,
402+
extent: block.data?.extent || undefined,
395403
draggable: false,
404+
zIndex: nestingDepth,
405+
className: parentId ? 'nested-subflow-node' : undefined,
396406
data: {
407+
...block.data,
397408
name: block.name,
398409
width: dimensions.width,
399410
height: dimensions.height,
@@ -430,9 +441,11 @@ export function PreviewWorkflow({
430441
nodeArray.push({
431442
id: blockId,
432443
type: nodeType,
433-
position: absolutePosition,
444+
position: block.position,
445+
parentId,
446+
extent: block.data?.extent || undefined,
434447
draggable: false,
435-
zIndex: block.data?.parentId ? 10 : undefined,
448+
zIndex: parentId ? 1000 : undefined,
436449
data: {
437450
type: block.type,
438451
name: block.name,

apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ interface BlockExecutionData {
3434
childWorkflowSnapshotId?: string
3535
}
3636

37+
function isPauseOutput(output: unknown): boolean {
38+
return (
39+
output !== null &&
40+
typeof output === 'object' &&
41+
'_pauseMetadata' in output &&
42+
(output as Record<string, unknown>)._pauseMetadata !== undefined
43+
)
44+
}
45+
3746
/** Represents a level in the workflow navigation stack */
3847
interface WorkflowStackEntry {
3948
workflowState: WorkflowState
@@ -91,7 +100,7 @@ export function buildBlockExecutions(spans: TraceSpan[]): Record<string, BlockEx
91100
blockExecutionMap[span.blockId] = {
92101
input: redactApiKeys(span.input || {}),
93102
output: redactApiKeys(span.output || {}),
94-
status: span.status || 'unknown',
103+
status: isPauseOutput(span.output) ? 'pending' : span.status || 'unknown',
95104
durationMs: span.duration || 0,
96105
children: span.children,
97106
childWorkflowSnapshotId: span.childWorkflowSnapshotId,

apps/sim/executor/constants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ export const EDGE = {
7474
DEFAULT: 'default',
7575
} as const
7676

77+
export const SUBFLOW_CONTROL_EDGE_HANDLES = new Set<string>([
78+
EDGE.LOOP_CONTINUE,
79+
EDGE.LOOP_CONTINUE_ALT,
80+
EDGE.LOOP_EXIT,
81+
EDGE.PARALLEL_CONTINUE,
82+
EDGE.PARALLEL_EXIT,
83+
])
84+
85+
export const CONTROL_BACK_EDGE_HANDLES = new Set<string>([
86+
EDGE.LOOP_CONTINUE,
87+
EDGE.LOOP_CONTINUE_ALT,
88+
EDGE.PARALLEL_CONTINUE,
89+
])
90+
7791
export const LOOP = {
7892
TYPE: {
7993
FOR: 'for' as LoopType,

0 commit comments

Comments
 (0)