(
nodeId: string,
output: NormalizedBlockOutput,
isFinalOutput: boolean
)
| 430 | } |
| 431 | |
| 432 | private async handleNodeCompletion( |
| 433 | nodeId: string, |
| 434 | output: NormalizedBlockOutput, |
| 435 | isFinalOutput: boolean |
| 436 | ): Promise<void> { |
| 437 | const node = this.dag.nodes.get(nodeId) |
| 438 | if (!node) { |
| 439 | this.execLogger.error('Node not found during completion', { nodeId }) |
| 440 | return |
| 441 | } |
| 442 | |
| 443 | if (this.stoppedEarlyFlag && this.responseOutputLocked) { |
| 444 | // Workflow already ended via Response block. Skip state persistence (setBlockOutput), |
| 445 | // parallel/loop scope tracking, and edge propagation — no downstream blocks will run. |
| 446 | return |
| 447 | } |
| 448 | |
| 449 | if (output._pauseMetadata) { |
| 450 | await this.nodeOrchestrator.handleNodeCompletion(this.context, nodeId, output) |
| 451 | |
| 452 | const pauseMetadata = output._pauseMetadata |
| 453 | this.pausedBlocks.set(pauseMetadata.contextId, pauseMetadata) |
| 454 | this.context.metadata.status = 'paused' |
| 455 | this.context.metadata.pausePoints = Array.from(this.pausedBlocks.keys()) |
| 456 | |
| 457 | return |
| 458 | } |
| 459 | |
| 460 | await this.nodeOrchestrator.handleNodeCompletion(this.context, nodeId, output) |
| 461 | |
| 462 | const isResponseBlock = node.block.metadata?.id === BlockType.RESPONSE |
| 463 | if (isResponseBlock) { |
| 464 | if (!this.responseOutputLocked) { |
| 465 | this.finalOutput = output |
| 466 | this.responseOutputLocked = true |
| 467 | } |
| 468 | this.stoppedEarlyFlag = true |
| 469 | return |
| 470 | } |
| 471 | |
| 472 | if (isFinalOutput && !this.responseOutputLocked) { |
| 473 | this.finalOutput = output |
| 474 | } |
| 475 | |
| 476 | if (this.context.stopAfterBlockId === nodeId) { |
| 477 | // For loop/parallel sentinels, only stop if the subflow has fully exited (all iterations done) |
| 478 | // shouldContinue: true means more iterations, shouldExit: true means loop is done |
| 479 | const shouldContinue = |
| 480 | output.shouldContinue === true || output.selectedRoute === EDGE.PARALLEL_CONTINUE |
| 481 | if (!shouldContinue) { |
| 482 | this.execLogger.info('Stopping execution after target block', { nodeId }) |
| 483 | this.stoppedEarlyFlag = true |
| 484 | return |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | const readyNodes = this.edgeManager.processOutgoingEdges(node, output, false) |
| 489 |
no test coverage detected