Skip to content

Commit 0ead5aa

Browse files
committed
Fix
1 parent 28fbd0c commit 0ead5aa

File tree

4 files changed

+78
-18
lines changed

4 files changed

+78
-18
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ export function BlockMenu({
9191
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
9292
const isSubflow =
9393
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')
94+
const isInsideSubflow =
95+
isSingleBlock &&
96+
(selectedBlocks[0]?.parentType === 'loop' || selectedBlocks[0]?.parentType === 'parallel')
9497

9598
const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock
9699

@@ -212,8 +215,8 @@ export function BlockMenu({
212215
</PopoverItem>
213216
)}
214217

215-
{/* Run from/until block - only for single non-note block selection */}
216-
{isSingleBlock && !allNoteBlocks && (
218+
{/* Run from/until block - only for single non-note block, not inside subflows */}
219+
{isSingleBlock && !allNoteBlocks && !isInsideSubflow && (
217220
<>
218221
<PopoverDivider />
219222
<PopoverItem
@@ -227,17 +230,20 @@ export function BlockMenu({
227230
>
228231
Run from block
229232
</PopoverItem>
230-
<PopoverItem
231-
disabled={isExecuting}
232-
onClick={() => {
233-
if (!isExecuting) {
234-
onRunUntilBlock?.()
235-
onClose()
236-
}
237-
}}
238-
>
239-
Run until block
240-
</PopoverItem>
233+
{/* Hide "Run until" for triggers - they're always at the start */}
234+
{!hasTriggerBlock && (
235+
<PopoverItem
236+
disabled={isExecuting}
237+
onClick={() => {
238+
if (!isExecuting) {
239+
onRunUntilBlock?.()
240+
onClose()
241+
}
242+
}}
243+
>
244+
Run until block
245+
</PopoverItem>
246+
)}
241247
</>
242248
)}
243249

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,8 +1128,22 @@ const WorkflowContent = React.memo(() => {
11281128
const snapshot = getLastExecutionSnapshot(workflowIdParam)
11291129
const incomingEdges = edges.filter((edge) => edge.target === block.id)
11301130
const isTriggerBlock = incomingEdges.length === 0
1131+
const isSubflow = block.type === 'loop' || block.type === 'parallel'
1132+
1133+
// For subflows, check if the sentinel-end was executed (meaning the subflow completed at least once)
1134+
// Sentinel IDs follow the pattern: loop-{id}-sentinel-end or parallel-{id}-sentinel-end
1135+
const subflowWasExecuted =
1136+
isSubflow &&
1137+
snapshot &&
1138+
snapshot.executedBlocks.some(
1139+
(executedId) =>
1140+
executedId === `loop-${block.id}-sentinel-end` ||
1141+
executedId === `parallel-${block.id}-sentinel-end`
1142+
)
1143+
11311144
const dependenciesSatisfied =
11321145
isTriggerBlock ||
1146+
subflowWasExecuted ||
11331147
(snapshot && incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source)))
11341148
const isNoteBlock = block.type === 'note'
11351149
const isInsideSubflow =

apps/sim/executor/execution/engine.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,14 @@ export class ExecutionEngine {
397397
}
398398

399399
if (this.context.stopAfterBlockId === nodeId) {
400-
logger.info('Stopping execution after target block', { nodeId })
401-
this.stoppedEarlyFlag = true
402-
return
400+
// For loop/parallel sentinels, only stop if the subflow has fully exited (all iterations done)
401+
// shouldContinue: true means more iterations, shouldExit: true means loop is done
402+
const shouldContinueLoop = output.shouldContinue === true
403+
if (!shouldContinueLoop) {
404+
logger.info('Stopping execution after target block', { nodeId })
405+
this.stoppedEarlyFlag = true
406+
return
407+
}
403408
}
404409

405410
const readyNodes = this.edgeManager.processOutgoingEdges(node, output, false)

apps/sim/executor/execution/executor.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import {
2626
buildStartBlockOutput,
2727
resolveExecutorStartBlock,
2828
} from '@/executor/utils/start-block'
29+
import {
30+
extractLoopIdFromSentinel,
31+
extractParallelIdFromSentinel,
32+
} from '@/executor/utils/subflow-utils'
2933
import { VariableResolver } from '@/executor/variables/resolver'
3034
import type { SerializedWorkflow } from '@/serializer/types'
3135

@@ -119,19 +123,50 @@ export class DAGExecutor {
119123
const { dirtySet, upstreamSet } = computeExecutionSets(dag, startBlockId)
120124
const effectiveStartBlockId = resolveContainerToSentinelStart(startBlockId, dag) ?? startBlockId
121125

126+
// Extract container IDs from sentinel IDs in upstream set
127+
const upstreamContainerIds = new Set<string>()
128+
for (const nodeId of upstreamSet) {
129+
const loopId = extractLoopIdFromSentinel(nodeId)
130+
if (loopId) upstreamContainerIds.add(loopId)
131+
const parallelId = extractParallelIdFromSentinel(nodeId)
132+
if (parallelId) upstreamContainerIds.add(parallelId)
133+
}
134+
122135
// Filter snapshot to only include upstream blocks - prevents references to non-upstream blocks
123136
const filteredBlockStates: Record<string, any> = {}
124137
for (const [blockId, state] of Object.entries(sourceSnapshot.blockStates)) {
125-
if (upstreamSet.has(blockId)) {
138+
if (upstreamSet.has(blockId) || upstreamContainerIds.has(blockId)) {
126139
filteredBlockStates[blockId] = state
127140
}
128141
}
129-
const filteredExecutedBlocks = sourceSnapshot.executedBlocks.filter((id) => upstreamSet.has(id))
142+
const filteredExecutedBlocks = sourceSnapshot.executedBlocks.filter(
143+
(id) => upstreamSet.has(id) || upstreamContainerIds.has(id)
144+
)
145+
146+
// Filter loop/parallel executions to only include upstream containers
147+
const filteredLoopExecutions: Record<string, any> = {}
148+
if (sourceSnapshot.loopExecutions) {
149+
for (const [loopId, execution] of Object.entries(sourceSnapshot.loopExecutions)) {
150+
if (upstreamContainerIds.has(loopId)) {
151+
filteredLoopExecutions[loopId] = execution
152+
}
153+
}
154+
}
155+
const filteredParallelExecutions: Record<string, any> = {}
156+
if (sourceSnapshot.parallelExecutions) {
157+
for (const [parallelId, execution] of Object.entries(sourceSnapshot.parallelExecutions)) {
158+
if (upstreamContainerIds.has(parallelId)) {
159+
filteredParallelExecutions[parallelId] = execution
160+
}
161+
}
162+
}
130163

131164
const filteredSnapshot: SerializableExecutionState = {
132165
...sourceSnapshot,
133166
blockStates: filteredBlockStates,
134167
executedBlocks: filteredExecutedBlocks,
168+
loopExecutions: filteredLoopExecutions,
169+
parallelExecutions: filteredParallelExecutions,
135170
}
136171

137172
logger.info('Executing from block', {

0 commit comments

Comments
 (0)