Skip to content

Commit ff16b1b

Browse files
fix(hitl): build the full enabled-block DAG so any persisted resume target exists (#5313)
1 parent 44a954d commit ff16b1b

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

apps/sim/executor/execution/executor.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,63 @@ describe('DAGExecutor run-from-block snapshot metadata', () => {
331331
})
332332
})
333333

334+
describe('DAGExecutor resume DAG construction', () => {
335+
it('includes non-starter resume targets when a workflow has a disconnected starter', async () => {
336+
const workflow: SerializedWorkflow = {
337+
version: '1',
338+
blocks: [
339+
createBlock('start-t2-v2', BlockType.STARTER),
340+
createBlock('webhook-start', 'generic_webhook'),
341+
createBlock('hitl', BlockType.HUMAN_IN_THE_LOOP),
342+
createBlock('generate-report', BlockType.FUNCTION),
343+
],
344+
connections: [
345+
{ source: 'webhook-start', target: 'hitl', sourceHandle: 'source' },
346+
{ source: 'hitl', target: 'generate-report', sourceHandle: 'source' },
347+
],
348+
loops: {},
349+
parallels: {},
350+
}
351+
let capturedDag: ReturnType<DAGBuilder['build']> | undefined
352+
const executor = new DAGExecutor({
353+
workflow,
354+
contextExtensions: {
355+
resumeFromSnapshot: true,
356+
remainingEdges: [{ source: 'hitl', target: 'generate-report', sourceHandle: 'source' }],
357+
dagIncomingEdges: { 'start-t2-v2': [] },
358+
snapshotState: {
359+
blockStates: {},
360+
executedBlocks: ['webhook-start', 'hitl'],
361+
blockLogs: [],
362+
decisions: { router: {}, condition: {} },
363+
completedLoops: [],
364+
activeExecutionPath: [],
365+
},
366+
},
367+
}) as unknown as DAGExecutor & {
368+
buildExecutionPipeline: (
369+
context: ExecutionContext,
370+
dag: ReturnType<DAGBuilder['build']>
371+
) => { run: () => Promise<ExecutionResult> }
372+
}
373+
executor.buildExecutionPipeline = vi.fn((_context, dag) => {
374+
capturedDag = dag
375+
return {
376+
run: async (): Promise<ExecutionResult> => ({
377+
success: true,
378+
output: { ok: true },
379+
metadata: {},
380+
}),
381+
}
382+
})
383+
384+
await executor.execute('wf')
385+
386+
expect(capturedDag?.nodes.has('generate-report')).toBe(true)
387+
expect(capturedDag?.nodes.get('generate-report')?.incomingEdges.has('hitl')).toBe(true)
388+
})
389+
})
390+
334391
describe('DAGExecutor createExecutionContext useDraftState', () => {
335392
function buildMetadataUseDraftState(opts: {
336393
metadataUseDraftState?: boolean

apps/sim/executor/execution/executor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export class DAGExecutor {
8888
const dag = this.dagBuilder.build(this.workflow, {
8989
triggerBlockId,
9090
savedIncomingEdges,
91+
includeAllBlocks: this.contextExtensions.resumeFromSnapshot === true,
9192
})
9293
const restoredClonedSubflows = this.restoreSnapshotParallelBatches(
9394
dag,

0 commit comments

Comments
 (0)