Skip to content

Commit c0bb854

Browse files
authored
fix(parallel): variable resolution in collection (#2314)
* Fix var resolution in parallel * Fix parallel * Clean logs * FIx loop error port
1 parent b595273 commit c0bb854

File tree

9 files changed

+367
-8
lines changed

9 files changed

+367
-8
lines changed

apps/sim/executor/execution/edge-manager.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ export class EdgeManager {
8686
this.deactivatedEdges.clear()
8787
}
8888

89+
/**
90+
* Clear deactivated edges for a set of nodes (used when restoring loop state for next iteration).
91+
* This ensures error/success edges can be re-evaluated on each iteration.
92+
*/
93+
clearDeactivatedEdgesForNodes(nodeIds: Set<string>): void {
94+
const edgesToRemove: string[] = []
95+
for (const edgeKey of this.deactivatedEdges) {
96+
// Edge key format is "sourceId-targetId-handle"
97+
// Check if either source or target is in the nodeIds set
98+
for (const nodeId of nodeIds) {
99+
if (edgeKey.startsWith(`${nodeId}-`) || edgeKey.includes(`-${nodeId}-`)) {
100+
edgesToRemove.push(edgeKey)
101+
break
102+
}
103+
}
104+
}
105+
for (const edgeKey of edgesToRemove) {
106+
this.deactivatedEdges.delete(edgeKey)
107+
}
108+
}
109+
89110
private shouldActivateEdge(edge: DAGEdge, output: NormalizedBlockOutput): boolean {
90111
const handle = edge.sourceHandle
91112

@@ -180,7 +201,7 @@ export class EdgeManager {
180201
const sourceNode = this.dag.nodes.get(sourceId)
181202
if (!sourceNode) continue
182203

183-
for (const [_, edge] of sourceNode.outgoingEdges) {
204+
for (const [, edge] of sourceNode.outgoingEdges) {
184205
if (edge.target === node.id) {
185206
const edgeKey = this.createEdgeKey(sourceId, edge.target, edge.sourceHandle)
186207
if (!this.deactivatedEdges.has(edgeKey)) {

apps/sim/executor/execution/engine.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ export class ExecutionEngine {
279279
})
280280

281281
this.addMultipleToQueue(readyNodes)
282+
283+
// Check for dynamically added nodes (e.g., from parallel expansion)
284+
if (this.context.pendingDynamicNodes && this.context.pendingDynamicNodes.length > 0) {
285+
const dynamicNodes = this.context.pendingDynamicNodes
286+
this.context.pendingDynamicNodes = []
287+
logger.info('Adding dynamically expanded parallel nodes', { dynamicNodes })
288+
this.addMultipleToQueue(dynamicNodes)
289+
}
282290
}
283291

284292
private buildPausedResult(startTime: number): ExecutionResult {

apps/sim/executor/execution/executor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ export class DAGExecutor {
6464
const resolver = new VariableResolver(this.workflow, this.workflowVariables, state)
6565
const loopOrchestrator = new LoopOrchestrator(dag, state, resolver)
6666
const parallelOrchestrator = new ParallelOrchestrator(dag, state)
67+
parallelOrchestrator.setResolver(resolver)
6768
const allHandlers = createBlockHandlers()
6869
const blockExecutor = new BlockExecutor(allHandlers, resolver, this.contextExtensions, state)
6970
const edgeManager = new EdgeManager(dag)
71+
loopOrchestrator.setEdgeManager(edgeManager)
7072
const nodeOrchestrator = new NodeExecutionOrchestrator(
7173
dag,
7274
state,

apps/sim/executor/execution/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ParallelScope {
2222
branchOutputs: Map<number, NormalizedBlockOutput[]>
2323
completedCount: number
2424
totalExpectedNodes: number
25+
items?: any[]
2526
}
2627

2728
export class ExecutionState implements BlockStateController {

apps/sim/executor/orchestrators/loop.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createLogger } from '@/lib/logs/console/logger'
22
import { buildLoopIndexCondition, DEFAULTS, EDGE } from '@/executor/constants'
33
import type { DAG } from '@/executor/dag/builder'
4+
import type { EdgeManager } from '@/executor/execution/edge-manager'
45
import type { LoopScope } from '@/executor/execution/state'
56
import type { BlockStateController } from '@/executor/execution/types'
67
import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types'
@@ -26,12 +27,18 @@ export interface LoopContinuationResult {
2627
}
2728

2829
export class LoopOrchestrator {
30+
private edgeManager: EdgeManager | null = null
31+
2932
constructor(
3033
private dag: DAG,
3134
private state: BlockStateController,
3235
private resolver: VariableResolver
3336
) {}
3437

38+
setEdgeManager(edgeManager: EdgeManager): void {
39+
this.edgeManager = edgeManager
40+
}
41+
3542
initializeLoopScope(ctx: ExecutionContext, loopId: string): LoopScope {
3643
const loopConfig = this.dag.loopConfigs.get(loopId) as SerializedLoop | undefined
3744
if (!loopConfig) {
@@ -216,23 +223,26 @@ export class LoopOrchestrator {
216223
const loopNodes = loopConfig.nodes
217224
const allLoopNodeIds = new Set([sentinelStartId, sentinelEndId, ...loopNodes])
218225

219-
let restoredCount = 0
226+
// Clear deactivated edges for loop nodes so error/success edges can be re-evaluated
227+
if (this.edgeManager) {
228+
this.edgeManager.clearDeactivatedEdgesForNodes(allLoopNodeIds)
229+
}
230+
220231
for (const nodeId of allLoopNodeIds) {
221232
const nodeToRestore = this.dag.nodes.get(nodeId)
222233
if (!nodeToRestore) continue
223234

224235
for (const [potentialSourceId, potentialSourceNode] of this.dag.nodes) {
225236
if (!allLoopNodeIds.has(potentialSourceId)) continue
226237

227-
for (const [_, edge] of potentialSourceNode.outgoingEdges) {
238+
for (const [, edge] of potentialSourceNode.outgoingEdges) {
228239
if (edge.target === nodeId) {
229240
const isBackwardEdge =
230241
edge.sourceHandle === EDGE.LOOP_CONTINUE ||
231242
edge.sourceHandle === EDGE.LOOP_CONTINUE_ALT
232243

233244
if (!isBackwardEdge) {
234245
nodeToRestore.incomingEdges.add(potentialSourceId)
235-
restoredCount++
236246
}
237247
}
238248
}

apps/sim/executor/orchestrators/node.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ export class NodeExecutionOrchestrator {
5353
}
5454
}
5555

56+
// Initialize parallel scope BEFORE execution so <parallel.currentItem> can be resolved
57+
const parallelId = node.metadata.parallelId
58+
if (parallelId && !this.parallelOrchestrator.getParallelScope(ctx, parallelId)) {
59+
const totalBranches = node.metadata.branchTotal || 1
60+
const parallelConfig = this.dag.parallelConfigs.get(parallelId)
61+
const nodesInParallel = (parallelConfig as any)?.nodes?.length || 1
62+
this.parallelOrchestrator.initializeParallelScope(
63+
ctx,
64+
parallelId,
65+
totalBranches,
66+
nodesInParallel
67+
)
68+
}
69+
5670
if (node.metadata.isSentinel) {
5771
const output = this.handleSentinel(ctx, node)
5872
const isFinalOutput = node.outgoingEdges.size === 0

0 commit comments

Comments
 (0)