griptape-nodes 0.64.11__py3-none-any.whl → 0.65.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. griptape_nodes/app/app.py +25 -5
  2. griptape_nodes/cli/commands/init.py +65 -54
  3. griptape_nodes/cli/commands/libraries.py +92 -85
  4. griptape_nodes/cli/commands/self.py +121 -0
  5. griptape_nodes/common/node_executor.py +2142 -101
  6. griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
  7. griptape_nodes/exe_types/connections.py +114 -19
  8. griptape_nodes/exe_types/core_types.py +225 -7
  9. griptape_nodes/exe_types/flow.py +3 -3
  10. griptape_nodes/exe_types/node_types.py +681 -225
  11. griptape_nodes/exe_types/param_components/README.md +414 -0
  12. griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
  15. griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
  16. griptape_nodes/machines/control_flow.py +84 -38
  17. griptape_nodes/machines/dag_builder.py +148 -70
  18. griptape_nodes/machines/parallel_resolution.py +61 -35
  19. griptape_nodes/machines/sequential_resolution.py +11 -113
  20. griptape_nodes/retained_mode/events/app_events.py +1 -0
  21. griptape_nodes/retained_mode/events/base_events.py +16 -13
  22. griptape_nodes/retained_mode/events/connection_events.py +3 -0
  23. griptape_nodes/retained_mode/events/execution_events.py +35 -0
  24. griptape_nodes/retained_mode/events/flow_events.py +15 -2
  25. griptape_nodes/retained_mode/events/library_events.py +347 -0
  26. griptape_nodes/retained_mode/events/node_events.py +48 -0
  27. griptape_nodes/retained_mode/events/os_events.py +86 -3
  28. griptape_nodes/retained_mode/events/project_events.py +15 -1
  29. griptape_nodes/retained_mode/events/workflow_events.py +48 -1
  30. griptape_nodes/retained_mode/griptape_nodes.py +6 -2
  31. griptape_nodes/retained_mode/managers/config_manager.py +10 -8
  32. griptape_nodes/retained_mode/managers/event_manager.py +168 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
  35. griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
  36. griptape_nodes/retained_mode/managers/library_manager.py +1142 -138
  37. griptape_nodes/retained_mode/managers/model_manager.py +2 -3
  38. griptape_nodes/retained_mode/managers/node_manager.py +148 -25
  39. griptape_nodes/retained_mode/managers/object_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
  41. griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
  42. griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
  43. griptape_nodes/retained_mode/managers/settings.py +21 -1
  44. griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
  45. griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
  46. griptape_nodes/retained_mode/retained_mode.py +3 -3
  47. griptape_nodes/traits/button.py +44 -2
  48. griptape_nodes/traits/file_system_picker.py +2 -2
  49. griptape_nodes/utils/file_utils.py +101 -0
  50. griptape_nodes/utils/git_utils.py +1236 -0
  51. griptape_nodes/utils/library_utils.py +122 -0
  52. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/METADATA +2 -1
  53. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/RECORD +55 -47
  54. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/WHEEL +1 -1
  55. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,11 @@
1
1
  import logging
2
2
  from dataclasses import dataclass
3
3
  from enum import StrEnum
4
+ from typing import NamedTuple
4
5
 
6
+ from griptape_nodes.exe_types.base_iterative_nodes import BaseIterativeEndNode, BaseIterativeStartNode
5
7
  from griptape_nodes.exe_types.core_types import Parameter, ParameterMode, ParameterTypeBuiltin
6
- from griptape_nodes.exe_types.node_types import BaseNode, Connection, EndLoopNode, NodeResolutionState, StartLoopNode
8
+ from griptape_nodes.exe_types.node_types import BaseNode, Connection, NodeResolutionState
7
9
 
8
10
  logger = logging.getLogger("griptape_nodes")
9
11
 
@@ -13,6 +15,11 @@ class Direction(StrEnum):
13
15
  DOWNSTREAM = "downstream"
14
16
 
15
17
 
18
+ class ConnectionData(NamedTuple):
19
+ node: BaseNode
20
+ parameter: Parameter
21
+
22
+
16
23
  @dataclass
17
24
  class Connections:
18
25
  # store connections as IDs
@@ -33,6 +40,8 @@ class Connections:
33
40
  source_parameter: Parameter,
34
41
  target_node: BaseNode,
35
42
  target_parameter: Parameter,
43
+ *,
44
+ is_node_group_internal: bool = False,
36
45
  ) -> Connection:
37
46
  if ParameterMode.OUTPUT not in source_parameter.get_mode():
38
47
  errormsg = f"Output Connection not allowed on Parameter '{source_parameter.name}'."
@@ -44,7 +53,13 @@ class Connections:
44
53
  if self.connection_allowed(source_node, source_parameter, is_source=True) and self.connection_allowed(
45
54
  target_node, target_parameter, is_source=False
46
55
  ):
47
- connection = Connection(source_node, source_parameter, target_node, target_parameter)
56
+ connection = Connection(
57
+ source_node,
58
+ source_parameter,
59
+ target_node,
60
+ target_parameter,
61
+ is_node_group_internal=is_node_group_internal,
62
+ )
48
63
  # New index management.
49
64
  connection_id = id(connection)
50
65
  # Add connection to our dict here
@@ -106,14 +121,14 @@ class Connections:
106
121
  return connection is None
107
122
 
108
123
  def _get_connected_node_for_end_loop_control(
109
- self, end_loop_node: EndLoopNode, control_parameter: Parameter
110
- ) -> tuple[BaseNode, Parameter] | None:
111
- """For an EndLoopNode and its control parameter, finds the connected node and parameter.
124
+ self, end_loop_node: BaseIterativeEndNode, control_parameter: Parameter
125
+ ) -> ConnectionData | None:
126
+ """For a BaseIterativeEndNode and its control parameter, finds the connected node and parameter.
112
127
 
113
- It checks both outgoing connections (where EndLoopNode's parameter is a source)
114
- and incoming connections (where EndLoopNode's parameter is a target).
128
+ It checks both outgoing connections (where BaseIterativeEndNode's parameter is a source)
129
+ and incoming connections (where BaseIterativeEndNode's parameter is a target).
115
130
  """
116
- # Check if the EndLoopNode's control parameter is a source for an outgoing connection
131
+ # Check if the BaseIterativeEndNode's control parameter is a source for an outgoing connection
117
132
  if ParameterMode.OUTPUT in control_parameter.allowed_modes:
118
133
  outgoing_connections_for_node = self.outgoing_index.get(end_loop_node.name, {})
119
134
  connection_ids_as_source = outgoing_connections_for_node.get(control_parameter.name, [])
@@ -121,23 +136,23 @@ class Connections:
121
136
  connection_id = connection_ids_as_source[0]
122
137
  connection = self.connections.get(connection_id)
123
138
  if connection:
124
- return connection.target_node, connection.target_parameter
139
+ return ConnectionData(connection.target_node, connection.target_parameter)
125
140
  elif ParameterMode.INPUT in control_parameter.allowed_modes:
126
- # Check if the EndLoopNode's control parameter is a target for an incoming connection
141
+ # Check if the BaseIterativeEndNode's control parameter is a target for an incoming connection
127
142
  incoming_connections_for_node = self.incoming_index.get(end_loop_node.name, {})
128
143
  connection_ids_as_target = incoming_connections_for_node.get(control_parameter.name, [])
129
144
  if connection_ids_as_target:
130
145
  for connection_id in connection_ids_as_target:
131
146
  connection = self.connections.get(connection_id)
132
- if connection and isinstance(connection.source_node, StartLoopNode):
133
- return connection.source_node, connection.source_parameter
147
+ if connection and isinstance(connection.source_node, BaseIterativeStartNode):
148
+ return ConnectionData(connection.source_node, connection.source_parameter)
134
149
  return None # No connection found for this control parameter
135
150
 
136
- def get_connected_node(
137
- self, node: BaseNode, parameter: Parameter, direction: Direction | None = None
138
- ) -> tuple[BaseNode, Parameter] | None:
151
+ def get_connected_node( # noqa: C901, Need if checks.
152
+ self, node: BaseNode, parameter: Parameter, direction: Direction | None = None, *, include_internal: bool = True
153
+ ) -> ConnectionData | None:
139
154
  # Check to see if we should be getting the next connection or the previous connection based on the parameter.
140
- # Override this method for EndLoopNodes - these might have to go backwards or forwards.
155
+ # Override this method for BaseIterativeEndNodes - these might have to go backwards or forwards.
141
156
  if direction is not None:
142
157
  # We've added direction as an override, since we sometimes need to get connections in a certain direction regardless of parameter types.
143
158
  if direction == Direction.UPSTREAM:
@@ -145,7 +160,10 @@ class Connections:
145
160
  elif direction == Direction.DOWNSTREAM:
146
161
  connections = self.outgoing_index
147
162
  else:
148
- if isinstance(node, EndLoopNode) and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
163
+ if (
164
+ isinstance(node, BaseIterativeEndNode)
165
+ and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type
166
+ ):
149
167
  return self._get_connected_node_for_end_loop_control(node, parameter)
150
168
  if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
151
169
  connections = self.outgoing_index
@@ -176,11 +194,14 @@ class Connections:
176
194
  connection_id = connection_id[0]
177
195
  if connection_id in self.connections:
178
196
  connection = self.connections[connection_id]
197
+ # We don't traverse internal NodeGroup connections when include_internal is False.
198
+ if connection.is_node_group_internal and not include_internal:
199
+ return None
179
200
  if direction == Direction.DOWNSTREAM:
180
201
  # Return the target (next place to go)
181
- return connection.target_node, connection.target_parameter
202
+ return ConnectionData(connection.target_node, connection.target_parameter)
182
203
  # Return the source (next place to chain back to)
183
- return connection.source_node, connection.source_parameter
204
+ return ConnectionData(connection.source_node, connection.source_parameter)
184
205
  return None
185
206
 
186
207
  def remove_connection_by_object(self, conn: Connection) -> bool:
@@ -308,3 +329,77 @@ class Connections:
308
329
  )
309
330
  )
310
331
  self.unresolve_future_nodes(target_node)
332
+
333
+ def get_outgoing_connections_to_node(self, node: BaseNode, to_node: BaseNode) -> dict[str, list[Connection]]:
334
+ connections = {}
335
+ if node.name in self.outgoing_index:
336
+ parameters = self.outgoing_index[node.name]
337
+ for parameter, connection_ids in parameters.items():
338
+ for conn_id in connection_ids:
339
+ connection = self.connections[conn_id]
340
+ if connection.target_node.name == to_node.name:
341
+ if parameter not in connections:
342
+ connections[parameter] = [connection]
343
+ else:
344
+ connections[parameter].append(connection)
345
+ return connections
346
+
347
+ def get_incoming_connections_from_node(self, node: BaseNode, from_node: BaseNode) -> dict[str, list[Connection]]:
348
+ connections = {}
349
+ if node.name in self.incoming_index:
350
+ parameters = self.incoming_index[node.name]
351
+ for parameter, connection_ids in parameters.items():
352
+ for conn_id in connection_ids:
353
+ connection = self.connections[conn_id]
354
+ if connection.source_node.name == from_node.name:
355
+ if parameter not in connections:
356
+ connections[parameter] = [connection]
357
+ else:
358
+ connections[parameter].append(connection)
359
+ return connections
360
+
361
+ def get_outgoing_connections_from_parameter(self, node: BaseNode, parameter: Parameter) -> list[Connection]:
362
+ connections = []
363
+ if node.name in self.outgoing_index and parameter.name in self.outgoing_index[node.name]:
364
+ for conn_id in self.outgoing_index[node.name][parameter.name]:
365
+ connections.append(self.connections[conn_id]) # noqa: PERF401
366
+ return connections
367
+
368
+ def get_incoming_connections_to_parameter(self, node: BaseNode, parameter: Parameter) -> list[Connection]:
369
+ connections = []
370
+ if node.name in self.incoming_index and parameter.name in self.incoming_index[node.name]:
371
+ for conn_id in self.incoming_index[node.name][parameter.name]:
372
+ connections.append(self.connections[conn_id]) # noqa: PERF401
373
+ return connections
374
+
375
+ def get_all_outgoing_connections(self, node: BaseNode) -> list[Connection]:
376
+ """Get all outgoing connections from a node.
377
+
378
+ Args:
379
+ node: The node to get outgoing connections for
380
+
381
+ Returns:
382
+ List of all outgoing connections from the node
383
+ """
384
+ connections = []
385
+ if node.name in self.outgoing_index:
386
+ for connection_ids in self.outgoing_index[node.name].values():
387
+ for conn_id in connection_ids:
388
+ connections.append(self.connections[conn_id]) # noqa: PERF401, Keeping loop for understanding.
389
+ return connections
390
+
391
+ def get_all_incoming_connections(self, node: BaseNode) -> list[Connection]:
392
+ """Get all incoming connections to a node.
393
+
394
+ Args:
395
+ node: The node to get incoming connections for
396
+
397
+ Returns:
398
+ List of all incoming connections to the node
399
+ """
400
+ connections = []
401
+ if node.name in self.incoming_index:
402
+ for connection_ids in self.incoming_index[node.name].values():
403
+ for conn_id in connection_ids:
404
+ connections.append(self.connections[conn_id]) # noqa: PERF401, Keeping loop for understanding.
405
+ return connections
@@ -479,10 +479,56 @@ class BaseNodeElement:
479
479
  # No child handled it, return None (indicating no handler)
480
480
  return None
481
481
 
482
+ def get_node(self) -> BaseNode | None:
483
+ """Get the node context associated with this element.
484
+
485
+ Returns:
486
+ BaseNode | None: The parent node that owns this element, or None if no node context is set.
487
+ """
488
+ return self._node_context
489
+
482
490
 
483
491
  class UIOptionsMixin:
484
492
  """Mixin providing UI options update functionality for classes with ui_options."""
485
493
 
494
+ def _validate_ui_option_conflict(
495
+ self,
496
+ ui_options_dict: dict,
497
+ param_name: str,
498
+ param_value: Any,
499
+ ) -> None:
500
+ """Validate that explicit parameter doesn't conflict with ui_options dict.
501
+
502
+ Logs a warning if there's a conflict and the ui_options value will be used.
503
+
504
+ Args:
505
+ ui_options_dict: The ui_options dictionary to check
506
+ param_name: Name of the parameter (e.g., "hide", "markdown")
507
+ param_value: Value of the explicit parameter
508
+ """
509
+ if param_name not in ui_options_dict:
510
+ return
511
+
512
+ dict_value = ui_options_dict[param_name]
513
+
514
+ if param_value != dict_value:
515
+ # Get element name for better error messages
516
+ element_name = getattr(self, "name", None)
517
+ class_name = self.__class__.__name__
518
+
519
+ # Build element part
520
+ if element_name:
521
+ element_part = f"{class_name} '{element_name}'"
522
+ else:
523
+ element_part = class_name
524
+
525
+ msg = (
526
+ f"{element_part}: Conflicting values for '{param_name}'. "
527
+ f'Explicit parameter {param_name}={param_value!r} conflicts with ui_options["{param_name}"]={dict_value!r}. '
528
+ f"The value from ui_options will be used. Please contact the library author to fix this issue."
529
+ )
530
+ logger.warning(msg)
531
+
486
532
  def update_ui_options_key(self, key: str, value: Any) -> None:
487
533
  """Update a single UI option key."""
488
534
  ui_options = self.ui_options
@@ -558,10 +604,15 @@ class ParameterMessage(BaseNodeElement, UIOptionsMixin):
558
604
  button_variant: ButtonVariantType = "outline",
559
605
  button_align: ButtonAlignType = "full-width",
560
606
  full_width: bool = False,
607
+ markdown: bool | None = None,
608
+ hide: bool | None = None,
561
609
  ui_options: dict | None = None,
562
610
  traits: set[Trait.__class__ | Trait] | None = None,
563
611
  **kwargs,
564
612
  ):
613
+ # Remove markdown and hide from kwargs to prevent passing them to parent class
614
+ kwargs.pop("markdown", None)
615
+ kwargs.pop("hide", None)
565
616
  super().__init__(element_type=ParameterMessage.__name__, **kwargs)
566
617
  self._variant = variant
567
618
  self._title = title
@@ -575,6 +626,21 @@ class ParameterMessage(BaseNodeElement, UIOptionsMixin):
575
626
  self._full_width = full_width
576
627
  self._ui_options = ui_options or {}
577
628
 
629
+ # Validate that explicit parameters don't conflict with ui_options (only if not None)
630
+ if markdown is not None:
631
+ self._validate_ui_option_conflict(
632
+ ui_options_dict=self._ui_options, param_name="markdown", param_value=markdown
633
+ )
634
+ if hide is not None:
635
+ self._validate_ui_option_conflict(ui_options_dict=self._ui_options, param_name="hide", param_value=hide)
636
+
637
+ # Add common UI options if explicitly provided (not None) and NOT already in ui_options
638
+ # (ui_options always wins in case of conflict)
639
+ if markdown is not None and "markdown" not in self._ui_options:
640
+ self._ui_options["markdown"] = markdown
641
+ if hide is not None and "hide" not in self._ui_options:
642
+ self._ui_options["hide"] = hide
643
+
578
644
  # Handle traits if provided
579
645
  if traits:
580
646
  for trait in traits:
@@ -676,6 +742,44 @@ class ParameterMessage(BaseNodeElement, UIOptionsMixin):
676
742
  def button_align(self, value: ButtonAlignType) -> None:
677
743
  self._button_align = value
678
744
 
745
+ @property
746
+ def markdown(self) -> bool:
747
+ """Get whether markdown rendering is enabled.
748
+
749
+ Returns:
750
+ True if markdown rendering is enabled, False otherwise
751
+ """
752
+ return self.ui_options.get("markdown", False)
753
+
754
+ @markdown.setter
755
+ @BaseNodeElement.emits_update_on_write
756
+ def markdown(self, value: bool) -> None:
757
+ """Set whether to enable markdown rendering.
758
+
759
+ Args:
760
+ value: True to enable markdown rendering, False to disable it
761
+ """
762
+ self.update_ui_options_key("markdown", value)
763
+
764
+ @property
765
+ def hide(self) -> bool:
766
+ """Get whether the message is hidden in the UI.
767
+
768
+ Returns:
769
+ True if the message should be hidden, False otherwise
770
+ """
771
+ return self.ui_options.get("hide", False)
772
+
773
+ @hide.setter
774
+ @BaseNodeElement.emits_update_on_write
775
+ def hide(self, value: bool) -> None:
776
+ """Set whether to hide the message in the UI.
777
+
778
+ Args:
779
+ value: True to hide the message, False to show it
780
+ """
781
+ self.update_ui_options_key("hide", value)
782
+
679
783
  @property
680
784
  def ui_options(self) -> dict:
681
785
  return self._ui_options
@@ -907,6 +1011,103 @@ class ParameterGroup(BaseNodeElement, UIOptionsMixin):
907
1011
  return super().remove_child(child)
908
1012
 
909
1013
 
1014
+ class ParameterButtonGroup(BaseNodeElement, UIOptionsMixin):
1015
+ """UI element for grouping buttons together in a row (similar to shadcn ButtonGroup).
1016
+
1017
+ This class creates a button group container that displays buttons horizontally
1018
+ with proper spacing and styling, similar to shadcn/ui's ButtonGroup component.
1019
+
1020
+ Example:
1021
+ with ParameterButtonGroup(name="actions", orientation="horizontal") as button_group:
1022
+ ParameterButton(
1023
+ name="save",
1024
+ label="Save",
1025
+ variant="default",
1026
+ )
1027
+ ParameterButton(
1028
+ name="cancel",
1029
+ label="Cancel",
1030
+ variant="secondary",
1031
+ )
1032
+ """
1033
+
1034
+ def __init__(
1035
+ self,
1036
+ name: str,
1037
+ ui_options: dict | None = None,
1038
+ *,
1039
+ orientation: Literal["horizontal", "vertical"] = "horizontal",
1040
+ **kwargs,
1041
+ ):
1042
+ super().__init__(name=name, element_type="ParameterButtonGroup", **kwargs)
1043
+ if ui_options is None:
1044
+ ui_options = {}
1045
+ else:
1046
+ ui_options = ui_options.copy()
1047
+
1048
+ # Set button group specific UI options
1049
+ ui_options["button_group"] = True
1050
+ ui_options["orientation"] = orientation
1051
+
1052
+ self._ui_options = ui_options
1053
+ self._orientation: Literal["horizontal", "vertical"] = orientation
1054
+
1055
+ @property
1056
+ def ui_options(self) -> dict:
1057
+ return self._ui_options
1058
+
1059
+ @ui_options.setter
1060
+ @BaseNodeElement.emits_update_on_write
1061
+ def ui_options(self, value: dict) -> None:
1062
+ self._ui_options = value
1063
+
1064
+ @property
1065
+ def orientation(self) -> Literal["horizontal", "vertical"]:
1066
+ """Get the button group orientation.
1067
+
1068
+ Returns:
1069
+ "horizontal" for buttons in a row, "vertical" for buttons in a column
1070
+ """
1071
+ return self._orientation
1072
+
1073
+ @orientation.setter
1074
+ @BaseNodeElement.emits_update_on_write
1075
+ def orientation(self, value: Literal["horizontal", "vertical"]) -> None:
1076
+ """Set the button group orientation.
1077
+
1078
+ Args:
1079
+ value: "horizontal" for buttons in a row, "vertical" for buttons in a column
1080
+ """
1081
+ self._orientation = value
1082
+ self.update_ui_options_key("orientation", value)
1083
+
1084
+ def to_dict(self) -> dict[str, Any]:
1085
+ """Returns a nested dictionary representation of this button group and its children."""
1086
+ our_dict = super().to_dict()
1087
+ our_dict["name"] = self.name
1088
+ our_dict["ui_options"] = self.ui_options
1089
+ return our_dict
1090
+
1091
+ def to_event(self, node: BaseNode) -> dict:
1092
+ event_data = super().to_event(node)
1093
+ event_data["ui_options"] = self.ui_options
1094
+ return event_data
1095
+
1096
+ def add_child(self, child: BaseNodeElement) -> None:
1097
+ child.parent_group_name = self.name
1098
+ return super().add_child(child)
1099
+
1100
+ def remove_child(self, child: BaseNodeElement | str) -> None:
1101
+ if isinstance(child, str):
1102
+ child_from_str = self.find_element_by_name(child)
1103
+ if child_from_str is not None and isinstance(child_from_str, BaseNodeElement):
1104
+ child_from_str.parent_group_name = None
1105
+ return super().remove_child(child_from_str)
1106
+ else:
1107
+ child.parent_group_name = None
1108
+ return super().remove_child(child)
1109
+
1110
+
910
1111
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/856
911
1112
  class ParameterBase(BaseNodeElement, ABC):
912
1113
  @property
@@ -1012,9 +1213,9 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
1012
1213
  traits: set[Trait.__class__ | Trait] | None = None, # We are going to make these children.
1013
1214
  ui_options: dict | None = None,
1014
1215
  *,
1015
- hide: bool = False,
1016
- hide_label: bool = False,
1017
- hide_property: bool = False,
1216
+ hide: bool | None = None,
1217
+ hide_label: bool | None = None,
1218
+ hide_property: bool | None = None,
1018
1219
  allow_input: bool = True,
1019
1220
  allow_property: bool = True,
1020
1221
  allow_output: bool = True,
@@ -1092,12 +1293,25 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
1092
1293
  else:
1093
1294
  self._ui_options = ui_options.copy()
1094
1295
 
1095
- # Add common UI options if they have truthy values
1096
- if hide:
1296
+ # Validate that explicit parameters don't conflict with ui_options (only if not None)
1297
+ if hide is not None:
1298
+ self._validate_ui_option_conflict(ui_options_dict=self._ui_options, param_name="hide", param_value=hide)
1299
+ if hide_label is not None:
1300
+ self._validate_ui_option_conflict(
1301
+ ui_options_dict=self._ui_options, param_name="hide_label", param_value=hide_label
1302
+ )
1303
+ if hide_property is not None:
1304
+ self._validate_ui_option_conflict(
1305
+ ui_options_dict=self._ui_options, param_name="hide_property", param_value=hide_property
1306
+ )
1307
+
1308
+ # Add common UI options if explicitly provided (not None) and NOT already in ui_options
1309
+ # (ui_options always wins in case of conflict)
1310
+ if hide is not None and "hide" not in self._ui_options:
1097
1311
  self._ui_options["hide"] = hide
1098
- if hide_label:
1312
+ if hide_label is not None and "hide_label" not in self._ui_options:
1099
1313
  self._ui_options["hide_label"] = hide_label
1100
- if hide_property:
1314
+ if hide_property is not None and "hide_property" not in self._ui_options:
1101
1315
  self._ui_options["hide_property"] = hide_property
1102
1316
  if traits:
1103
1317
  for trait in traits:
@@ -1753,6 +1967,7 @@ class ParameterContainer(Parameter, ABC):
1753
1967
  converters: list[Callable[[Any], Any]] | None = None,
1754
1968
  validators: list[Callable[[Parameter, Any], None]] | None = None,
1755
1969
  *,
1970
+ hide: bool = False,
1756
1971
  settable: bool = True,
1757
1972
  user_defined: bool = False,
1758
1973
  element_id: str | None = None,
@@ -1773,6 +1988,7 @@ class ParameterContainer(Parameter, ABC):
1773
1988
  traits=traits,
1774
1989
  converters=converters,
1775
1990
  validators=validators,
1991
+ hide=hide,
1776
1992
  settable=settable,
1777
1993
  user_defined=user_defined,
1778
1994
  element_id=element_id,
@@ -1821,6 +2037,7 @@ class ParameterList(ParameterContainer):
1821
2037
  converters: list[Callable[[Any], Any]] | None = None,
1822
2038
  validators: list[Callable[[Parameter, Any], None]] | None = None,
1823
2039
  *,
2040
+ hide: bool = False,
1824
2041
  settable: bool = True,
1825
2042
  user_defined: bool = False,
1826
2043
  element_id: str | None = None,
@@ -1860,6 +2077,7 @@ class ParameterList(ParameterContainer):
1860
2077
  traits=traits,
1861
2078
  converters=converters,
1862
2079
  validators=validators,
2080
+ hide=hide,
1863
2081
  settable=settable,
1864
2082
  user_defined=user_defined,
1865
2083
  element_id=element_id,
@@ -81,17 +81,17 @@ class ControlFlow:
81
81
  def clear_execution_queue(self) -> None:
82
82
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
83
83
 
84
- GriptapeNodes.FlowManager().clear_execution_queue()
84
+ GriptapeNodes.FlowManager().clear_execution_queue(self)
85
85
 
86
86
  def get_connections_on_node(self, node: BaseNode) -> list[BaseNode] | None:
87
87
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
88
88
 
89
- return GriptapeNodes.FlowManager().get_connections_on_node(self, node)
89
+ return GriptapeNodes.FlowManager().get_connections_on_node(node)
90
90
 
91
91
  def get_all_connected_nodes(self, node: BaseNode) -> list[BaseNode]:
92
92
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
93
93
 
94
- return GriptapeNodes.FlowManager().get_all_connected_nodes(self, node)
94
+ return GriptapeNodes.FlowManager().get_all_connected_nodes(node)
95
95
 
96
96
  def get_node_dependencies(self, node: BaseNode) -> list[BaseNode]:
97
97
  """Get all upstream nodes that the given node depends on.