Skip to content

Commit 0fd55db

Browse files
author
Theodore Li
committed
Fix orphaned loop-in-loop if parent id not found
1 parent e0695ea commit 0fd55db

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,42 @@ 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 an unmatched nested container with all descendants and edges', () => {
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',
424+
inputs: { code: 'return 2' },
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(
435+
(edge: any) =>
436+
edge.source === 'inner-loop' ||
437+
edge.target === 'inner-loop' ||
438+
edge.source === 'inner-agent' ||
439+
edge.target === 'inner-agent'
440+
)
441+
).toBe(false)
442+
443+
const replacementBlock = Object.values(state.blocks).find(
444+
(block: any) => block.name === 'Replacement'
445+
) as any
446+
expect(replacementBlock).toBeDefined()
447+
expect(replacementBlock.data?.parentId).toBe('outer-loop')
448+
})
411449
})

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,27 @@ function mergeNestedNodesForParent(
297297
}
298298
})
299299

300+
const collectBlockAndDescendants = (
301+
rootId: string,
302+
collected = new Set<string>()
303+
): Set<string> => {
304+
collected.add(rootId)
305+
Object.entries(modifiedState.blocks).forEach(([childId, block]: [string, any]) => {
306+
if (block.data?.parentId === rootId && !collected.has(childId)) {
307+
collectBlockAndDescendants(childId, collected)
308+
}
309+
})
310+
return collected
311+
}
312+
300313
const removedIds = new Set<string>()
301314
for (const [existingId] of existingChildren) {
302315
if (!matchedExistingIds.has(existingId)) {
303-
delete modifiedState.blocks[existingId]
304-
removedIds.add(existingId)
316+
const subtreeIds = collectBlockAndDescendants(existingId)
317+
subtreeIds.forEach((id) => {
318+
delete modifiedState.blocks[id]
319+
removedIds.add(id)
320+
})
305321
}
306322
}
307323

0 commit comments

Comments
 (0)