Skip to content

Commit ed6ee62

Browse files
committed
Fix recursive nested block cleanup during merge edits
1 parent e0695ea commit ed6ee62

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/operations.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,39 @@ describe('handleEditOperation nestedNodes merge', () => {
408408
const helperBlock = Object.values(state.blocks).find((block: any) => block.name === 'Helper')
409409
expect(helperBlock).toBeDefined()
410410
})
411+
412+
it('removes descendants when a nested container child is removed', () => {
413+
const workflow = makeNestedLoopWorkflow()
414+
415+
const { state } = applyOperationsToWorkflowState(workflow, [
416+
{
417+
operation_type: 'edit',
418+
block_id: 'outer-loop',
419+
params: {
420+
nestedNodes: {
421+
replacement: {
422+
type: 'function',
423+
name: 'Replacement Helper',
424+
inputs: { code: 'return 1' },
425+
},
426+
},
427+
},
428+
},
429+
])
430+
431+
expect(state.blocks['inner-loop']).toBeUndefined()
432+
expect(state.blocks['inner-agent']).toBeUndefined()
433+
expect(
434+
state.edges.some((edge: any) => edge.source === 'inner-loop' || edge.target === 'inner-loop')
435+
).toBe(false)
436+
expect(
437+
state.edges.some((edge: any) => edge.source === 'inner-agent' || edge.target === 'inner-agent')
438+
).toBe(false)
439+
440+
const replacementBlock = Object.values(state.blocks).find(
441+
(block: any) => block.name === 'Replacement Helper'
442+
) as any
443+
expect(replacementBlock).toBeDefined()
444+
expect(replacementBlock.data?.parentId).toBe('outer-loop')
445+
})
411446
})

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/operations.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,23 @@ function mergeNestedNodesForParent(
298298
})
299299

300300
const removedIds = new Set<string>()
301+
const findChildrenToRemove = (parentId: string) => {
302+
Object.entries(modifiedState.blocks).forEach(([childId, child]: [string, any]) => {
303+
if (child.data?.parentId === parentId && !removedIds.has(childId)) {
304+
removedIds.add(childId)
305+
findChildrenToRemove(childId)
306+
}
307+
})
308+
}
301309
for (const [existingId] of existingChildren) {
302310
if (!matchedExistingIds.has(existingId)) {
303-
delete modifiedState.blocks[existingId]
304311
removedIds.add(existingId)
312+
findChildrenToRemove(existingId)
305313
}
306314
}
307315

308316
if (removedIds.size > 0) {
317+
removedIds.forEach((id) => delete modifiedState.blocks[id])
309318
modifiedState.edges = modifiedState.edges.filter(
310319
(edge: any) => !removedIds.has(edge.source) && !removedIds.has(edge.target)
311320
)

0 commit comments

Comments
 (0)