griptape-nodes 0.51.2__py3-none-any.whl → 0.52.0__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 (46) hide show
  1. griptape_nodes/__init__.py +5 -4
  2. griptape_nodes/app/api.py +27 -24
  3. griptape_nodes/app/app.py +243 -221
  4. griptape_nodes/app/watch.py +17 -2
  5. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
  6. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
  7. griptape_nodes/exe_types/core_types.py +16 -4
  8. griptape_nodes/exe_types/node_types.py +74 -16
  9. griptape_nodes/machines/control_flow.py +21 -26
  10. griptape_nodes/machines/fsm.py +16 -16
  11. griptape_nodes/machines/node_resolution.py +30 -119
  12. griptape_nodes/mcp_server/server.py +14 -10
  13. griptape_nodes/mcp_server/ws_request_manager.py +2 -2
  14. griptape_nodes/node_library/workflow_registry.py +5 -0
  15. griptape_nodes/retained_mode/events/base_events.py +12 -7
  16. griptape_nodes/retained_mode/events/execution_events.py +0 -6
  17. griptape_nodes/retained_mode/events/node_events.py +38 -0
  18. griptape_nodes/retained_mode/events/parameter_events.py +11 -0
  19. griptape_nodes/retained_mode/events/variable_events.py +361 -0
  20. griptape_nodes/retained_mode/events/workflow_events.py +35 -0
  21. griptape_nodes/retained_mode/griptape_nodes.py +61 -26
  22. griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
  23. griptape_nodes/retained_mode/managers/event_manager.py +215 -74
  24. griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
  25. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
  26. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
  27. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
  28. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
  29. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
  30. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
  31. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
  32. griptape_nodes/retained_mode/managers/library_manager.py +20 -19
  33. griptape_nodes/retained_mode/managers/node_manager.py +83 -8
  34. griptape_nodes/retained_mode/managers/object_manager.py +4 -0
  35. griptape_nodes/retained_mode/managers/settings.py +1 -0
  36. griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
  37. griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
  38. griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
  39. griptape_nodes/retained_mode/variable_types.py +18 -0
  40. griptape_nodes/utils/__init__.py +4 -0
  41. griptape_nodes/utils/async_utils.py +89 -0
  42. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/METADATA +2 -3
  43. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/RECORD +45 -42
  44. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/WHEEL +1 -1
  45. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
  46. {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/entry_points.txt +0 -0
@@ -95,6 +95,7 @@ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.
95
95
  )
96
96
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
97
97
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
98
+ from griptape_nodes.utils.async_utils import subprocess_run
98
99
  from griptape_nodes.utils.uv_utils import find_uv_bin
99
100
  from griptape_nodes.utils.version_utils import get_complete_version_string
100
101
 
@@ -662,7 +663,7 @@ class LibraryManager:
662
663
  result = ListCategoriesInLibraryResultSuccess(categories=categories)
663
664
  return result
664
665
 
665
- def register_library_from_file_request(self, request: RegisterLibraryFromFileRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915 (complex logic needs branches)
666
+ async def register_library_from_file_request(self, request: RegisterLibraryFromFileRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915 (complex logic needs branches)
666
667
  file_path = request.file_path
667
668
 
668
669
  # Convert to Path object if it's a string
@@ -781,7 +782,7 @@ class LibraryManager:
781
782
 
782
783
  # Only install dependencies if conditions are met
783
784
  try:
784
- library_venv_python_path = self._init_library_venv(venv_path)
785
+ library_venv_python_path = await self._init_library_venv(venv_path)
785
786
  except RuntimeError as e:
786
787
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
787
788
  library_path=file_path,
@@ -816,7 +817,7 @@ class LibraryManager:
816
817
  logger.info(
817
818
  "Installing dependencies for library '%s' with pip in venv at %s", library_data.name, venv_path
818
819
  )
819
- subprocess.run( # noqa: S603
820
+ await subprocess_run(
820
821
  [
821
822
  sys.executable,
822
823
  "-m",
@@ -920,7 +921,7 @@ class LibraryManager:
920
921
  logger.error(details)
921
922
  return RegisterLibraryFromFileResultFailure(result_details=details)
922
923
 
923
- def register_library_from_requirement_specifier_request(
924
+ async def register_library_from_requirement_specifier_request(
924
925
  self, request: RegisterLibraryFromRequirementSpecifierRequest
925
926
  ) -> ResultPayload:
926
927
  try:
@@ -930,7 +931,7 @@ class LibraryManager:
930
931
 
931
932
  # Only install dependencies if conditions are met
932
933
  try:
933
- library_python_venv_path = self._init_library_venv(venv_path)
934
+ library_python_venv_path = await self._init_library_venv(venv_path)
934
935
  except RuntimeError as e:
935
936
  details = f"Attempted to install library '{request.requirement_specifier}'. Failed when creating the virtual environment: {e}"
936
937
  logger.error(details)
@@ -948,14 +949,14 @@ class LibraryManager:
948
949
  uv_path = find_uv_bin()
949
950
 
950
951
  logger.info("Installing dependency '%s' with pip in venv at %s", package_name, venv_path)
951
- subprocess.run( # noqa: S603
952
+ await subprocess_run(
952
953
  [
953
954
  uv_path,
954
955
  "pip",
955
956
  "install",
956
957
  request.requirement_specifier,
957
958
  "--python",
958
- library_python_venv_path,
959
+ str(library_python_venv_path),
959
960
  ],
960
961
  check=True,
961
962
  capture_output=True,
@@ -986,7 +987,7 @@ class LibraryManager:
986
987
 
987
988
  return RegisterLibraryFromRequirementSpecifierResultSuccess(library_name=request.requirement_specifier)
988
989
 
989
- def _init_library_venv(self, library_venv_path: Path) -> Path:
990
+ async def _init_library_venv(self, library_venv_path: Path) -> Path:
990
991
  """Initialize a virtual environment for the library.
991
992
 
992
993
  If the virtual environment already exists, it will not be recreated.
@@ -1022,7 +1023,7 @@ class LibraryManager:
1022
1023
  try:
1023
1024
  uv_path = find_uv_bin()
1024
1025
  logger.info("Creating virtual environment at %s with Python %s", library_venv_path, python_version)
1025
- subprocess.run( # noqa: S603
1026
+ await subprocess_run(
1026
1027
  [uv_path, "venv", str(library_venv_path), "--python", python_version],
1027
1028
  check=True,
1028
1029
  capture_output=True,
@@ -1491,7 +1492,7 @@ class LibraryManager:
1491
1492
 
1492
1493
  return node_class
1493
1494
 
1494
- def load_all_libraries_from_config(self) -> None:
1495
+ async def load_all_libraries_from_config(self) -> None:
1495
1496
  # Comment out lines 1503-1545 and call the _load libraries from provenance system to test the other functionality.
1496
1497
 
1497
1498
  # Load metadata for all libraries to determine which ones can be safely loaded
@@ -1527,7 +1528,7 @@ class LibraryManager:
1527
1528
  register_request = RegisterLibraryFromFileRequest(
1528
1529
  file_path=library_result.file_path, load_as_default_library=False
1529
1530
  )
1530
- register_result = self.register_library_from_file_request(register_request)
1531
+ register_result = await self.register_library_from_file_request(register_request)
1531
1532
  if isinstance(register_result, RegisterLibraryFromFileResultFailure):
1532
1533
  # Registration failed - the failure info is already recorded in _library_file_path_to_info
1533
1534
  # by register_library_from_file_request, so we just log it here for visibility
@@ -1540,12 +1541,12 @@ class LibraryManager:
1540
1541
  user_libraries_section = "app_events.on_app_initialization_complete.libraries_to_register"
1541
1542
  self._remove_missing_libraries_from_config(config_category=user_libraries_section)
1542
1543
 
1543
- def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
1544
+ async def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
1544
1545
  GriptapeNodes.EngineIdentityManager().initialize_engine_id()
1545
1546
  GriptapeNodes.SessionManager().get_saved_session_id()
1546
1547
 
1547
1548
  # App just got init'd. See if there are library JSONs to load!
1548
- self.load_all_libraries_from_config()
1549
+ await self.load_all_libraries_from_config()
1549
1550
 
1550
1551
  # We have to load all libraries before we attempt to load workflows.
1551
1552
 
@@ -1603,7 +1604,7 @@ class LibraryManager:
1603
1604
  )
1604
1605
  console.print(message)
1605
1606
 
1606
- def _load_libraries_from_provenance_system(self) -> None:
1607
+ async def _load_libraries_from_provenance_system(self) -> None:
1607
1608
  """Load libraries using the new provenance-based system with FSM.
1608
1609
 
1609
1610
  This method converts libraries_to_register entries into LibraryProvenanceLocalFile
@@ -1632,7 +1633,7 @@ class LibraryManager:
1632
1633
 
1633
1634
  # Add to directory as user candidate (defaults to active=True)
1634
1635
  # This automatically creates FSM and runs evaluation
1635
- self._library_directory.add_user_candidate(provenance)
1636
+ await self._library_directory.add_user_candidate(provenance)
1636
1637
 
1637
1638
  logger.debug("Added library provenance: %s", provenance.get_display_name())
1638
1639
 
@@ -1661,8 +1662,8 @@ class LibraryManager:
1661
1662
 
1662
1663
  # Process installable candidates through installation and loading
1663
1664
  for candidate in installable_candidates:
1664
- if self._library_directory.install_library(candidate.provenance):
1665
- self._library_directory.load_library(candidate.provenance)
1665
+ if await self._library_directory.install_library(candidate.provenance):
1666
+ await self._library_directory.load_library(candidate.provenance)
1666
1667
 
1667
1668
  def _report_library_name_conflicts(self) -> None:
1668
1669
  """Report on library name conflicts found during evaluation."""
@@ -1997,7 +1998,7 @@ class LibraryManager:
1997
1998
  ]
1998
1999
  config_mgr.set_config_value(config_category, libraries_to_register_category)
1999
2000
 
2000
- def reload_all_libraries_request(self, request: ReloadAllLibrariesRequest) -> ResultPayload: # noqa: ARG002
2001
+ async def reload_all_libraries_request(self, request: ReloadAllLibrariesRequest) -> ResultPayload: # noqa: ARG002
2001
2002
  # Start with a clean slate.
2002
2003
  clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
2003
2004
  clear_all_result = GriptapeNodes.handle_request(clear_all_request)
@@ -2023,7 +2024,7 @@ class LibraryManager:
2023
2024
  return ReloadAllLibrariesResultFailure(result_details=details)
2024
2025
 
2025
2026
  # Load (or reload, which should trigger a hot reload) all libraries
2026
- self.load_all_libraries_from_config()
2027
+ await self.load_all_libraries_from_config()
2027
2028
 
2028
2029
  details = (
2029
2030
  "Successfully reloaded all libraries. All object state was cleared and previous libraries were unloaded."
@@ -75,6 +75,9 @@ from griptape_nodes.retained_mode.events.node_events import (
75
75
  GetAllNodeInfoRequest,
76
76
  GetAllNodeInfoResultFailure,
77
77
  GetAllNodeInfoResultSuccess,
78
+ GetFlowForNodeRequest,
79
+ GetFlowForNodeResultFailure,
80
+ GetFlowForNodeResultSuccess,
78
81
  GetNodeMetadataRequest,
79
82
  GetNodeMetadataResultFailure,
80
83
  GetNodeMetadataResultSuccess,
@@ -199,6 +202,7 @@ class NodeManager:
199
202
  )
200
203
  event_manager.assign_manager_to_request_type(DuplicateSelectedNodesRequest, self.on_duplicate_selected_nodes)
201
204
  event_manager.assign_manager_to_request_type(SetLockNodeStateRequest, self.on_toggle_lock_node_request)
205
+ event_manager.assign_manager_to_request_type(GetFlowForNodeRequest, self.on_get_flow_for_node_request)
202
206
 
203
207
  def handle_node_rename(self, old_name: str, new_name: str) -> None:
204
208
  # Get the node itself
@@ -705,7 +709,7 @@ class NodeManager:
705
709
  else:
706
710
  failed_nodes[actual_node_name] = result.result_details
707
711
 
708
- if not updated_nodes:
712
+ if failed_nodes:
709
713
  return BatchSetNodeMetadataResultFailure(
710
714
  result_details=f"Failed to update any nodes. Failed nodes: {failed_nodes}"
711
715
  )
@@ -961,6 +965,7 @@ class NodeManager:
961
965
  allowed_modes=allowed_modes,
962
966
  ui_options=request.ui_options,
963
967
  parent_container_name=request.parent_container_name,
968
+ settable=request.settable,
964
969
  )
965
970
  try:
966
971
  if request.parent_container_name and request.initial_setup:
@@ -968,7 +973,6 @@ class NodeManager:
968
973
  if parameter_parent is not None:
969
974
  parameter_parent.add_child(new_param)
970
975
  else:
971
- logger.info(new_param.name)
972
976
  node.add_parameter(new_param)
973
977
  except Exception as e:
974
978
  details = f"Couldn't add parameter with name {request.parameter_name} to Node '{node_name}'. Error: {e}"
@@ -1168,6 +1172,7 @@ class NodeManager:
1168
1172
  mode_allowed_property=allows_property,
1169
1173
  mode_allowed_output=allows_output,
1170
1174
  is_user_defined=getattr(element, "user_defined", False),
1175
+ settable=getattr(element, "settable", None),
1171
1176
  ui_options=getattr(element, "ui_options", None),
1172
1177
  )
1173
1178
  return result
@@ -1257,7 +1262,7 @@ class NodeManager:
1257
1262
  if request.ui_options is not None and hasattr(parameter, "ui_options"):
1258
1263
  parameter.ui_options = request.ui_options # type: ignore[attr-defined]
1259
1264
 
1260
- def modify_key_parameter_fields(self, request: AlterParameterDetailsRequest, parameter: Parameter) -> None:
1265
+ def modify_key_parameter_fields(self, request: AlterParameterDetailsRequest, parameter: Parameter) -> None: # noqa: C901, PLR0912
1261
1266
  if request.type is not None:
1262
1267
  parameter.type = request.type
1263
1268
  if request.input_types is not None:
@@ -1282,6 +1287,8 @@ class NodeManager:
1282
1287
  parameter.allowed_modes.add(ParameterMode.OUTPUT)
1283
1288
  else:
1284
1289
  parameter.allowed_modes.discard(ParameterMode.OUTPUT)
1290
+ if request.settable is not None:
1291
+ parameter.settable = request.settable
1285
1292
 
1286
1293
  def _validate_and_break_invalid_connections(
1287
1294
  self, node_name: str, parameter: Parameter, request: AlterParameterDetailsRequest
@@ -1549,7 +1556,50 @@ class NodeManager:
1549
1556
  result = SetParameterValueResultFailure(result_details=details)
1550
1557
  return result
1551
1558
 
1559
+ # Validate incoming connection source fields consistency
1560
+ incoming_node_set = request.incoming_connection_source_node_name is not None
1561
+ incoming_param_set = request.incoming_connection_source_parameter_name is not None
1562
+ if incoming_node_set != incoming_param_set:
1563
+ details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because incoming connection source fields must both be None or both be set. Got incoming_connection_source_node_name={request.incoming_connection_source_node_name}, incoming_connection_source_parameter_name={request.incoming_connection_source_parameter_name}."
1564
+ logger.error(details)
1565
+ result = SetParameterValueResultFailure(result_details=details)
1566
+ return result
1567
+
1568
+ # Prevent manual property setting on parameters that have both INPUT and PROPERTY modes when they have incoming connections
1569
+ # When a parameter can accept both input connections AND manual property values, having an active connection should
1570
+ # make the parameter non-settable as a property to avoid conflicts between connected values and manual values
1571
+ # Skip this check if: initial_setup (workflow loading), or incoming_connection_source fields are set (system passing upstream values)
1572
+ if (
1573
+ not request.initial_setup
1574
+ and not incoming_node_set # If incoming connection source fields are set, this is a legitimate upstream value pass
1575
+ and ParameterMode.INPUT in parameter.allowed_modes
1576
+ and ParameterMode.PROPERTY in parameter.allowed_modes
1577
+ ):
1578
+ # Check if this parameter has any incoming connections
1579
+ connections = GriptapeNodes.FlowManager().get_connections()
1580
+ target_connections = connections.incoming_index.get(node_name)
1581
+ if target_connections is not None:
1582
+ param_connections = target_connections.get(request.parameter_name)
1583
+ if param_connections: # Has incoming connections
1584
+ # TODO: https://github.com/griptape-ai/griptape-nodes/issues/1965 Consider emitting UI events when parameters become settable/unsettable due to connection changes
1585
+ details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because this parameter has incoming connections and cannot be set as a property while connected."
1586
+ logger.error(details)
1587
+ result = SetParameterValueResultFailure(result_details=details)
1588
+ return result
1589
+
1590
+ # Call before_value_set hook (allows nodes to modify values and temporarily control settable state)
1591
+ try:
1592
+ modified_value = node.before_value_set(parameter, request.value)
1593
+ if modified_value is not None:
1594
+ request.value = modified_value
1595
+ except Exception as err:
1596
+ details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because before_value_set hook raised exception: {err}"
1597
+ logger.error(details)
1598
+ result = SetParameterValueResultFailure(result_details=details)
1599
+ return result
1600
+
1552
1601
  # Validate that parameters can be set at all (note: we want the value to be set during initial setup, but not after)
1602
+ # This check comes after before_value_set to allow nodes to temporarily modify settable state
1553
1603
  if not parameter.settable and not request.initial_setup:
1554
1604
  details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because that Parameter was flagged as not settable."
1555
1605
  logger.error(details)
@@ -1596,8 +1646,9 @@ class NodeManager:
1596
1646
  # Mark node as unresolved, broadcast an event
1597
1647
  node.make_node_unresolved(current_states_to_trigger_change_event=set({NodeResolutionState.RESOLVED}))
1598
1648
  # Get the flow
1599
- # Pass the value through!
1600
- # Optional data_type parameter for internal handling!
1649
+ # Pass the value through to connected downstream parameters!
1650
+ # Set incoming_connection_source fields to identify this as legitimate upstream value propagation
1651
+ # (not manual property setting) so it bypasses the INPUT+PROPERTY connection blocking logic
1601
1652
  conn_output_nodes = parent_flow.get_connected_output_parameters(node, parameter)
1602
1653
  for target_node, target_parameter in conn_output_nodes:
1603
1654
  # Skip propagation for Control Parameters as they should not receive values
@@ -1608,6 +1659,8 @@ class NodeManager:
1608
1659
  node_name=target_node.name,
1609
1660
  value=finalized_value,
1610
1661
  data_type=object_type, # Do type instead of output type, because it hasn't been processed.
1662
+ incoming_connection_source_node_name=node.name,
1663
+ incoming_connection_source_parameter_name=parameter.name,
1611
1664
  )
1612
1665
  )
1613
1666
 
@@ -1633,8 +1686,11 @@ class NodeManager:
1633
1686
  node.parameter_output_values[request.parameter_name] = object_created
1634
1687
  return NodeManager.ModifiedReturnValue(object_created, modified)
1635
1688
  # Otherwise use set_parameter_value. This calls our converters and validators.
1689
+ # Skip before_value_set since we already called it earlier in the flow
1636
1690
  old_value = node.get_parameter_value(request.parameter_name)
1637
- node.set_parameter_value(request.parameter_name, object_created, initial_setup=request.initial_setup)
1691
+ node.set_parameter_value(
1692
+ request.parameter_name, object_created, initial_setup=request.initial_setup, skip_before_value_set=True
1693
+ )
1638
1694
  # Get the "converted" value here.
1639
1695
  finalized_value = node.get_parameter_value(request.parameter_name)
1640
1696
  if old_value != finalized_value:
@@ -1867,7 +1923,7 @@ class NodeManager:
1867
1923
  raise KeyError(msg)
1868
1924
  return self._name_to_parent_flow_name[node_name]
1869
1925
 
1870
- def on_resolve_from_node_request(self, request: ResolveNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0915, PLR0912
1926
+ async def on_resolve_from_node_request(self, request: ResolveNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0915, PLR0912
1871
1927
  node_name = request.node_name
1872
1928
  debug_mode = request.debug_mode
1873
1929
 
@@ -1935,7 +1991,7 @@ class NodeManager:
1935
1991
  logger.error(details)
1936
1992
  return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
1937
1993
  try:
1938
- GriptapeNodes.FlowManager().resolve_singular_node(flow, node, debug_mode)
1994
+ await GriptapeNodes.FlowManager().resolve_singular_node(flow, node, debug_mode)
1939
1995
  except Exception as e:
1940
1996
  details = f'Failed to resolve "{node_name}". Error: {e}'
1941
1997
  logger.error(details)
@@ -2636,6 +2692,12 @@ class NodeManager:
2636
2692
  logger.error(details)
2637
2693
  return RenameParameterResultFailure(result_details=details)
2638
2694
 
2695
+ # Only allow parameter rename for user-defined params
2696
+ if not parameter.user_defined:
2697
+ details = f"Attempted to rename Parameter '{request.parameter_name}' on Node '{node_name}'. Failed because the Parameter is not user-defined."
2698
+ logger.error(details)
2699
+ return RenameParameterResultFailure(result_details=details)
2700
+
2639
2701
  # Validate the new parameter name
2640
2702
  if any(char.isspace() for char in request.new_parameter_name):
2641
2703
  details = f"Failed to rename Parameter '{request.parameter_name}' to '{request.new_parameter_name}'. Parameter names cannot contain any whitespace characters."
@@ -2765,3 +2827,16 @@ class NodeManager:
2765
2827
  response=callback_result.response,
2766
2828
  altered_workflow_state=callback_result.altered_workflow_state,
2767
2829
  )
2830
+
2831
+ def on_get_flow_for_node_request(self, request: GetFlowForNodeRequest) -> ResultPayload:
2832
+ """Get the flow name that contains a specific node."""
2833
+ try:
2834
+ flow_name = self.get_node_parent_flow_by_name(request.node_name)
2835
+ return GetFlowForNodeResultSuccess(
2836
+ flow_name=flow_name,
2837
+ result_details=f"Successfully retrieved flow '{flow_name}' for node '{request.node_name}'.",
2838
+ )
2839
+ except KeyError:
2840
+ return GetFlowForNodeResultFailure(
2841
+ result_details=f"Node '{request.node_name}' not found or not assigned to any flow.",
2842
+ )
@@ -143,6 +143,10 @@ class ObjectManager:
143
143
  context_mgr.pop_flow()
144
144
  context_mgr.pop_workflow()
145
145
  context_mgr._clipboard.clear()
146
+
147
+ # Clear all local workflow variables
148
+ GriptapeNodes.VariablesManager().on_clear_object_state()
149
+
146
150
  details = "Successfully cleared all object state (deleted everything)."
147
151
  logger.debug(details)
148
152
  return ClearAllObjectStateResultSuccess()
@@ -75,6 +75,7 @@ class Settings(BaseModel):
75
75
  "Exa": {"EXA_API_KEY": "$EXA_API_KEY"},
76
76
  "Grok": {"GROK_API_KEY": "$GROK_API_KEY"},
77
77
  "Groq": {"GROQ_API_KEY": "$GROQ_API_KEY"},
78
+ "Nvidia": {"NVIDIA_API_KEY": "$NVIDIA_API_KEY"},
78
79
  "Google": {"GOOGLE_API_KEY": "$GOOGLE_API_KEY", "GOOGLE_API_SEARCH_ID": "$GOOGLE_API_SEARCH_ID"},
79
80
  "Huggingface": {"HUGGINGFACE_HUB_ACCESS_TOKEN": "$HUGGINGFACE_HUB_ACCESS_TOKEN"},
80
81
  "LeonardoAI": {"LEONARDO_API_KEY": "$LEONARDO_API_KEY"},
@@ -24,6 +24,7 @@ from griptape_nodes.retained_mode.events.workflow_events import (
24
24
  RegisterWorkflowsFromConfigRequest,
25
25
  RegisterWorkflowsFromConfigResultSuccess,
26
26
  )
27
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
@@ -167,8 +168,6 @@ class SyncManager:
167
168
  sync_request = StartSyncAllCloudWorkflowsRequest()
168
169
 
169
170
  # Use handle_request to process through normal event system
170
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
171
-
172
171
  result = GriptapeNodes.handle_request(sync_request)
173
172
 
174
173
  if isinstance(result, StartSyncAllCloudWorkflowsResultSuccess):
@@ -193,8 +192,6 @@ class SyncManager:
193
192
  Raises:
194
193
  RuntimeError: If required cloud configuration is missing.
195
194
  """
196
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
197
-
198
195
  secrets_manager = GriptapeNodes.SecretsManager()
199
196
 
200
197
  # Get cloud storage configuration from secrets
@@ -428,8 +425,6 @@ class SyncManager:
428
425
  self, sync_id: str, workflow_files: list[str], storage_driver: GriptapeCloudStorageDriver, sync_dir: Path
429
426
  ) -> None:
430
427
  """Background thread function to sync workflows."""
431
- from griptape_nodes.app.app import event_queue
432
-
433
428
  synced_workflows = []
434
429
  failed_downloads = []
435
430
  total_workflows = len(workflow_files)
@@ -470,14 +465,13 @@ class SyncManager:
470
465
  failed_workflows=failed_downloads,
471
466
  total_workflows=total_workflows,
472
467
  )
473
- event_queue.put(AppEvent(payload=sync_complete_event))
468
+
469
+ GriptapeNodes.EventManager().put_event(AppEvent(payload=sync_complete_event))
474
470
 
475
471
  # Register workflows from the synced directory
476
472
  if synced_workflows:
477
473
  logger.info("Registering %d synced workflows from configuration", len(synced_workflows))
478
474
  try:
479
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
480
-
481
475
  register_request = RegisterWorkflowsFromConfigRequest(
482
476
  config_section="app_events.on_app_initialization_complete.workflows_to_register"
483
477
  )