@@ -249,8 +249,33 @@ def serialize(self) -> JsonObject:
249249 manual_trigger_edges = [edge for edge in trigger_edges if issubclass (edge .trigger_class , ManualTrigger )]
250250 has_manual_trigger = len (manual_trigger_edges ) > 0
251251
252+ # Determine which nodes have explicit non-trigger entrypoints in the graph
253+ # We need this early to decide whether to create an ENTRYPOINT node
254+ non_trigger_entrypoint_nodes : Set [Type [BaseNode ]] = set ()
255+ for subgraph in self ._workflow .get_subgraphs ():
256+ if any (True for _ in subgraph .trigger_edges ):
257+ continue
258+ for entrypoint in subgraph .entrypoints :
259+ try :
260+ non_trigger_entrypoint_nodes .add (get_unadorned_node (entrypoint ))
261+ except Exception :
262+ continue
263+
264+ # Check if workflow has only non-manual triggers (IntegrationTrigger/ScheduleTrigger)
265+ has_only_non_manual_triggers = len (trigger_edges ) > 0 and not has_manual_trigger
266+
267+ # We need an ENTRYPOINT node if:
268+ # 1. There's a ManualTrigger, OR
269+ # 2. There are no triggers at all (backward compatibility), OR
270+ # 3. There are non-trigger entrypoints (nodes that need ENTRYPOINT edges alongside triggers)
271+ # We skip ENTRYPOINT node only when there are ONLY non-manual triggers with no regular entrypoints
272+ needs_entrypoint_node = (
273+ has_manual_trigger or not has_only_non_manual_triggers or len (non_trigger_entrypoint_nodes ) > 0
274+ )
275+
252276 entrypoint_node_id : Optional [UUID ] = None
253277 entrypoint_node_source_handle_id : Optional [UUID ] = None
278+ entrypoint_node_display = self .display_context .workflow_display .entrypoint_node_display
254279
255280 if has_manual_trigger :
256281 # ManualTrigger: use trigger ID for ENTRYPOINT node (backward compatibility)
@@ -267,28 +292,30 @@ def serialize(self) -> JsonObject:
267292 "label" : "Entrypoint Node" ,
268293 "source_handle_id" : str (entrypoint_node_source_handle_id ),
269294 },
270- "display_data" : self . display_context . workflow_display . entrypoint_node_display .dict (),
295+ "display_data" : entrypoint_node_display . dict () if entrypoint_node_display else NodeDisplayData () .dict (),
271296 "base" : None ,
272297 "definition" : None ,
273298 }
274- else :
275- # All other cases : use workflow_display ENTRYPOINT node
299+ elif needs_entrypoint_node :
300+ # Non-trigger entrypoints exist : use workflow_display ENTRYPOINT node
276301 entrypoint_node_id = self .display_context .workflow_display .entrypoint_node_id
277302 entrypoint_node_source_handle_id = self .display_context .workflow_display .entrypoint_node_source_handle_id
278303
279- serialized_nodes [entrypoint_node_id ] = {
280- "id" : str (entrypoint_node_id ),
281- "type" : "ENTRYPOINT" ,
282- "inputs" : [],
283- "data" : {
284- "label" : "Entrypoint Node" ,
285- "source_handle_id" : str (entrypoint_node_source_handle_id ),
286- },
287- "display_data" : self .display_context .workflow_display .entrypoint_node_display .dict (),
288- "base" : None ,
289- "definition" : None ,
290- }
291- # else: has_only_integration_trigger without explicit entrypoint - no ENTRYPOINT node needed
304+ if entrypoint_node_id is not None and entrypoint_node_source_handle_id is not None :
305+ display_data = entrypoint_node_display .dict () if entrypoint_node_display else NodeDisplayData ().dict ()
306+ serialized_nodes [entrypoint_node_id ] = {
307+ "id" : str (entrypoint_node_id ),
308+ "type" : "ENTRYPOINT" ,
309+ "inputs" : [],
310+ "data" : {
311+ "label" : "Entrypoint Node" ,
312+ "source_handle_id" : str (entrypoint_node_source_handle_id ),
313+ },
314+ "display_data" : display_data ,
315+ "base" : None ,
316+ "definition" : None ,
317+ }
318+ # else: only non-manual triggers with no regular entrypoints - no ENTRYPOINT node needed
292319
293320 # Add all the nodes in the workflows
294321 for node in self ._workflow .get_all_nodes ():
@@ -395,19 +422,8 @@ def serialize(self) -> JsonObject:
395422 except Exception :
396423 continue
397424
398- # Determine which nodes have explicit non-trigger entrypoints in the graph
399- non_trigger_entrypoint_nodes : Set [Type [BaseNode ]] = set ()
400- for subgraph in self ._workflow .get_subgraphs ():
401- # If the subgraph contains trigger edges, its entrypoints were derived from triggers
402- if any (True for _ in subgraph .trigger_edges ):
403- continue
404- for entrypoint in subgraph .entrypoints :
405- try :
406- non_trigger_entrypoint_nodes .add (get_unadorned_node (entrypoint ))
407- except Exception :
408- continue
409-
410425 # Add edges from entrypoint first to preserve expected ordering
426+ # Note: non_trigger_entrypoint_nodes was computed earlier to determine if we need an ENTRYPOINT node
411427
412428 for target_node , entrypoint_display in self .display_context .entrypoint_displays .items ():
413429 unadorned_target_node = get_unadorned_node (target_node )
@@ -1048,9 +1064,12 @@ def _generate_entrypoint_display(
10481064 target_node_display = node_displays [entrypoint_target ]
10491065 target_node_id = target_node_display .node_id
10501066
1051- edge_display = edge_display_overrides or self ._generate_edge_display_from_source (
1052- entrypoint_node_id , target_node_id
1053- )
1067+ if edge_display_overrides :
1068+ edge_display = edge_display_overrides
1069+ elif entrypoint_node_id is not None :
1070+ edge_display = self ._generate_edge_display_from_source (entrypoint_node_id , target_node_id )
1071+ else :
1072+ edge_display = EdgeDisplay (id = uuid4_from_hash (f"{ self .workflow_id } |id|{ target_node_id } " ))
10541073
10551074 return EntrypointDisplay (id = entrypoint_id , edge_display = edge_display )
10561075
0 commit comments