griptape-nodes 0.42.0__py3-none-any.whl → 0.43.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 (132) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -6
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -225
  6. griptape_nodes/app/watch.py +1 -1
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/bootstrap_script.py +0 -0
  9. griptape_nodes/bootstrap/register_libraries_script.py +0 -0
  10. griptape_nodes/bootstrap/structure_config.yaml +0 -0
  11. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  12. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  13. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  14. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
  15. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
  16. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
  17. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
  18. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
  19. griptape_nodes/drivers/__init__.py +0 -0
  20. griptape_nodes/drivers/storage/__init__.py +0 -0
  21. griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  22. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  23. griptape_nodes/drivers/storage/local_storage_driver.py +2 -1
  24. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  25. griptape_nodes/exe_types/__init__.py +0 -0
  26. griptape_nodes/exe_types/connections.py +0 -0
  27. griptape_nodes/exe_types/core_types.py +0 -0
  28. griptape_nodes/exe_types/flow.py +0 -0
  29. griptape_nodes/exe_types/node_types.py +17 -1
  30. griptape_nodes/exe_types/type_validator.py +0 -0
  31. griptape_nodes/machines/__init__.py +0 -0
  32. griptape_nodes/machines/control_flow.py +41 -12
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +0 -0
  35. griptape_nodes/mcp_server/__init__.py +1 -0
  36. griptape_nodes/mcp_server/server.py +126 -0
  37. griptape_nodes/mcp_server/ws_request_manager.py +268 -0
  38. griptape_nodes/node_library/__init__.py +0 -0
  39. griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. griptape_nodes/node_library/library_registry.py +0 -0
  41. griptape_nodes/node_library/workflow_registry.py +1 -1
  42. griptape_nodes/py.typed +0 -0
  43. griptape_nodes/retained_mode/__init__.py +0 -0
  44. griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. griptape_nodes/retained_mode/events/agent_events.py +0 -0
  46. griptape_nodes/retained_mode/events/app_events.py +6 -2
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  48. griptape_nodes/retained_mode/events/base_events.py +6 -6
  49. griptape_nodes/retained_mode/events/config_events.py +0 -0
  50. griptape_nodes/retained_mode/events/connection_events.py +0 -0
  51. griptape_nodes/retained_mode/events/context_events.py +0 -0
  52. griptape_nodes/retained_mode/events/execution_events.py +0 -0
  53. griptape_nodes/retained_mode/events/flow_events.py +0 -0
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +2 -2
  56. griptape_nodes/retained_mode/events/logger_events.py +0 -0
  57. griptape_nodes/retained_mode/events/node_events.py +0 -0
  58. griptape_nodes/retained_mode/events/object_events.py +0 -0
  59. griptape_nodes/retained_mode/events/os_events.py +104 -2
  60. griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  63. griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  64. griptape_nodes/retained_mode/events/validation_events.py +0 -0
  65. griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  66. griptape_nodes/retained_mode/griptape_nodes.py +43 -40
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +48 -22
  69. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  70. griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  71. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  72. griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  74. griptape_nodes/retained_mode/managers/flow_manager.py +2 -0
  75. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
  81. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
  82. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
  83. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
  84. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
  85. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
  86. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
  87. griptape_nodes/retained_mode/managers/library_manager.py +144 -39
  88. griptape_nodes/retained_mode/managers/node_manager.py +86 -72
  89. griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +517 -12
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  94. griptape_nodes/retained_mode/managers/settings.py +0 -0
  95. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  96. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
  97. griptape_nodes/retained_mode/managers/workflow_manager.py +199 -2
  98. griptape_nodes/retained_mode/retained_mode.py +0 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  101. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. griptape_nodes/traits/__init__.py +0 -0
  103. griptape_nodes/traits/add_param_button.py +0 -0
  104. griptape_nodes/traits/button.py +0 -0
  105. griptape_nodes/traits/clamp.py +0 -0
  106. griptape_nodes/traits/compare.py +0 -0
  107. griptape_nodes/traits/compare_images.py +0 -0
  108. griptape_nodes/traits/file_system_picker.py +127 -0
  109. griptape_nodes/traits/minmax.py +0 -0
  110. griptape_nodes/traits/options.py +0 -0
  111. griptape_nodes/traits/slider.py +0 -0
  112. griptape_nodes/traits/trait_registry.py +0 -0
  113. griptape_nodes/traits/traits.json +0 -0
  114. griptape_nodes/updater/__init__.py +2 -2
  115. griptape_nodes/updater/__main__.py +0 -0
  116. griptape_nodes/utils/__init__.py +0 -0
  117. griptape_nodes/utils/dict_utils.py +0 -0
  118. griptape_nodes/utils/image_preview.py +128 -0
  119. griptape_nodes/utils/metaclasses.py +0 -0
  120. griptape_nodes/version_compatibility/__init__.py +0 -0
  121. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  124. griptape_nodes-0.43.0.dist-info/METADATA +90 -0
  125. griptape_nodes-0.43.0.dist-info/RECORD +129 -0
  126. griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
  127. {griptape_nodes-0.42.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -554
  129. griptape_nodes-0.42.0.dist-info/METADATA +0 -78
  130. griptape_nodes-0.42.0.dist-info/RECORD +0 -113
  131. griptape_nodes-0.42.0.dist-info/WHEEL +0 -4
  132. griptape_nodes-0.42.0.dist-info/licenses/LICENSE +0 -201
@@ -9,7 +9,6 @@ import subprocess
9
9
  import sys
10
10
  import sysconfig
11
11
  from dataclasses import dataclass, field
12
- from enum import StrEnum
13
12
  from importlib.resources import files
14
13
  from pathlib import Path
15
14
  from typing import TYPE_CHECKING, cast
@@ -17,6 +16,7 @@ from typing import TYPE_CHECKING, cast
17
16
  import uv
18
17
  from packaging.requirements import InvalidRequirement, Requirement
19
18
  from pydantic import ValidationError
19
+ from rich.align import Align
20
20
  from rich.box import HEAVY_EDGE
21
21
  from rich.console import Console
22
22
  from rich.panel import Panel
@@ -90,6 +90,11 @@ from griptape_nodes.retained_mode.events.library_events import (
90
90
  from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
91
91
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
92
92
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
93
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_directory import LibraryDirectory
94
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.local_file import (
95
+ LibraryProvenanceLocalFile,
96
+ )
97
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
93
98
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
94
99
 
95
100
  if TYPE_CHECKING:
@@ -101,6 +106,7 @@ if TYPE_CHECKING:
101
106
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
102
107
 
103
108
  logger = logging.getLogger("griptape_nodes")
109
+ console = Console()
104
110
 
105
111
 
106
112
  def _find_griptape_uv_bin() -> str:
@@ -121,14 +127,6 @@ def _find_griptape_uv_bin() -> str:
121
127
  class LibraryManager:
122
128
  SANDBOX_LIBRARY_NAME = "Sandbox Library"
123
129
 
124
- class LibraryStatus(StrEnum):
125
- """Status of the library that was attempted to be loaded."""
126
-
127
- GOOD = "GOOD" # No errors detected during loading. Registered.
128
- FLAWED = "FLAWED" # Some errors detected, but recoverable. Registered.
129
- UNUSABLE = "UNUSABLE" # Errors detected and not recoverable. Not registered.
130
- MISSING = "MISSING" # File not found. Not registered.
131
-
132
130
  @dataclass
133
131
  class LibraryInfo:
134
132
  """Information about a library that was attempted to be loaded.
@@ -136,7 +134,7 @@ class LibraryManager:
136
134
  Includes the status of the library, the file path, and any problems encountered during loading.
137
135
  """
138
136
 
139
- status: LibraryManager.LibraryStatus
137
+ status: LibraryStatus
140
138
  library_path: str
141
139
  library_name: str | None = None
142
140
  library_version: str | None = None
@@ -176,6 +174,8 @@ class LibraryManager:
176
174
  self._stable_to_dynamic_module_mapping = {}
177
175
  self._library_to_stable_modules = {}
178
176
  self._library_event_handler_mappings: dict[type[Payload], dict[str, LibraryManager.RegisteredEventHandler]] = {}
177
+ # LibraryDirectory owns the FSMs and manages library lifecycle
178
+ self._library_directory = LibraryDirectory()
179
179
 
180
180
  event_manager.assign_manager_to_request_type(
181
181
  ListRegisteredLibrariesRequest, self.on_list_registered_libraries_request
@@ -254,10 +254,10 @@ class LibraryManager:
254
254
 
255
255
  # Status emojis mapping
256
256
  status_emoji = {
257
- LibraryManager.LibraryStatus.GOOD: "✅",
258
- LibraryManager.LibraryStatus.FLAWED: "🟡",
259
- LibraryManager.LibraryStatus.UNUSABLE: "❌",
260
- LibraryManager.LibraryStatus.MISSING: "❓",
257
+ LibraryStatus.GOOD: "✅",
258
+ LibraryStatus.FLAWED: "🟡",
259
+ LibraryStatus.UNUSABLE: "❌",
260
+ LibraryStatus.MISSING: "❓",
261
261
  }
262
262
 
263
263
  # Add rows for each library info
@@ -408,7 +408,7 @@ class LibraryManager:
408
408
  return LoadLibraryMetadataFromFileResultFailure(
409
409
  library_path=file_path,
410
410
  library_name=None,
411
- status=LibraryManager.LibraryStatus.MISSING,
411
+ status=LibraryStatus.MISSING,
412
412
  problems=[
413
413
  "Library could not be found at the file path specified. It will be removed from the configuration."
414
414
  ],
@@ -424,7 +424,7 @@ class LibraryManager:
424
424
  return LoadLibraryMetadataFromFileResultFailure(
425
425
  library_path=file_path,
426
426
  library_name=None,
427
- status=LibraryManager.LibraryStatus.UNUSABLE,
427
+ status=LibraryStatus.UNUSABLE,
428
428
  problems=["Library file not formatted as proper JSON."],
429
429
  )
430
430
  except Exception as err:
@@ -433,7 +433,7 @@ class LibraryManager:
433
433
  return LoadLibraryMetadataFromFileResultFailure(
434
434
  library_path=file_path,
435
435
  library_name=None,
436
- status=LibraryManager.LibraryStatus.UNUSABLE,
436
+ status=LibraryStatus.UNUSABLE,
437
437
  problems=[f"Exception occurred when attempting to load the library: {err}."],
438
438
  )
439
439
 
@@ -457,7 +457,7 @@ class LibraryManager:
457
457
  return LoadLibraryMetadataFromFileResultFailure(
458
458
  library_path=file_path,
459
459
  library_name=library_name,
460
- status=LibraryManager.LibraryStatus.UNUSABLE,
460
+ status=LibraryStatus.UNUSABLE,
461
461
  problems=problems,
462
462
  )
463
463
  except Exception as err:
@@ -466,7 +466,7 @@ class LibraryManager:
466
466
  return LoadLibraryMetadataFromFileResultFailure(
467
467
  library_path=file_path,
468
468
  library_name=library_name,
469
- status=LibraryManager.LibraryStatus.UNUSABLE,
469
+ status=LibraryStatus.UNUSABLE,
470
470
  problems=[f"Library file did not match the library schema specified due to: {err}"],
471
471
  )
472
472
 
@@ -540,7 +540,7 @@ class LibraryManager:
540
540
  return LoadLibraryMetadataFromFileResultFailure(
541
541
  library_path=sandbox_library_dir_as_posix,
542
542
  library_name=LibraryManager.SANDBOX_LIBRARY_NAME,
543
- status=LibraryManager.LibraryStatus.MISSING,
543
+ status=LibraryStatus.MISSING,
544
544
  problems=["Sandbox directory does not exist."],
545
545
  )
546
546
 
@@ -595,7 +595,7 @@ class LibraryManager:
595
595
  return LoadLibraryMetadataFromFileResultFailure(
596
596
  library_path=sandbox_library_dir_as_posix,
597
597
  library_name=LibraryManager.SANDBOX_LIBRARY_NAME,
598
- status=LibraryManager.LibraryStatus.UNUSABLE,
598
+ status=LibraryStatus.UNUSABLE,
599
599
  problems=["Could not get engine version for sandbox library generation."],
600
600
  )
601
601
 
@@ -679,7 +679,7 @@ class LibraryManager:
679
679
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
680
680
  library_path=file_path,
681
681
  library_name=None,
682
- status=LibraryManager.LibraryStatus.MISSING,
682
+ status=LibraryStatus.MISSING,
683
683
  problems=[
684
684
  "Library could not be found at the file path specified. It will be removed from the configuration."
685
685
  ],
@@ -713,7 +713,7 @@ class LibraryManager:
713
713
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
714
714
  library_path=file_path,
715
715
  library_name=library_data.name,
716
- status=LibraryManager.LibraryStatus.UNUSABLE,
716
+ status=LibraryStatus.UNUSABLE,
717
717
  problems=[
718
718
  f"Library's version string '{library_data.metadata.library_version}' wasn't valid. Must be in major.minor.patch format."
719
719
  ],
@@ -740,7 +740,7 @@ class LibraryManager:
740
740
  library_path=file_path,
741
741
  library_name=library_data.name,
742
742
  library_version=library_version,
743
- status=LibraryManager.LibraryStatus.UNUSABLE,
743
+ status=LibraryStatus.UNUSABLE,
744
744
  problems=[
745
745
  f"Failed to load Advanced Library module from '{library_data.advanced_library_path}': {err}"
746
746
  ],
@@ -764,7 +764,7 @@ class LibraryManager:
764
764
  library_path=file_path,
765
765
  library_name=library_data.name,
766
766
  library_version=library_version,
767
- status=LibraryManager.LibraryStatus.UNUSABLE,
767
+ status=LibraryStatus.UNUSABLE,
768
768
  problems=[
769
769
  "Failed because a library with this name was already registered. Check the Settings to ensure duplicate libraries are not being loaded."
770
770
  ],
@@ -793,7 +793,7 @@ class LibraryManager:
793
793
  library_path=file_path,
794
794
  library_name=library_data.name,
795
795
  library_version=library_version,
796
- status=LibraryManager.LibraryStatus.UNUSABLE,
796
+ status=LibraryStatus.UNUSABLE,
797
797
  problems=[str(e)],
798
798
  )
799
799
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed when creating the virtual environment: {e}."
@@ -815,7 +815,7 @@ class LibraryManager:
815
815
  library_path=file_path,
816
816
  library_name=library_data.name,
817
817
  library_version=library_version,
818
- status=LibraryManager.LibraryStatus.UNUSABLE,
818
+ status=LibraryStatus.UNUSABLE,
819
819
  problems=[
820
820
  f"Insufficient disk space for dependencies (requires {min_space_gb} GB): {error_msg}"
821
821
  ],
@@ -856,7 +856,7 @@ class LibraryManager:
856
856
  library_path=file_path,
857
857
  library_name=library_data.name,
858
858
  library_version=library_version,
859
- status=LibraryManager.LibraryStatus.UNUSABLE,
859
+ status=LibraryStatus.UNUSABLE,
860
860
  problems=[f"Dependency installation failed: {error_details}"],
861
861
  )
862
862
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed when installing dependencies: {error_details}"
@@ -888,7 +888,9 @@ class LibraryManager:
888
888
  else:
889
889
  # We had an existing category. Union our changes into it (not replacing anything that matched).
890
890
  existing_category_contents = get_category_result.contents
891
- existing_category_contents.update(library_data_setting.contents)
891
+ existing_category_contents |= {
892
+ k: v for k, v in library_data_setting.contents.items() if k not in existing_category_contents
893
+ }
892
894
  set_category_request = SetConfigCategoryRequest(
893
895
  category=library_data_setting.category, contents=existing_category_contents
894
896
  )
@@ -911,15 +913,15 @@ class LibraryManager:
911
913
  self._library_file_path_to_info[file_path] = library_load_results
912
914
 
913
915
  match library_load_results.status:
914
- case LibraryManager.LibraryStatus.GOOD:
916
+ case LibraryStatus.GOOD:
915
917
  details = f"Successfully loaded Library '{library_data.name}' from JSON file at {json_path}"
916
918
  logger.info(details)
917
919
  return RegisterLibraryFromFileResultSuccess(library_name=library_data.name)
918
- case LibraryManager.LibraryStatus.FLAWED:
920
+ case LibraryStatus.FLAWED:
919
921
  details = f"Successfully loaded Library JSON file from '{json_path}', but one or more nodes failed to load. Check the log for more details."
920
922
  logger.warning(details)
921
923
  return RegisterLibraryFromFileResultSuccess(library_name=library_data.name)
922
- case LibraryManager.LibraryStatus.UNUSABLE:
924
+ case LibraryStatus.UNUSABLE:
923
925
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed because no nodes were loaded. Check the log for more details."
924
926
  logger.error(details)
925
927
  return RegisterLibraryFromFileResultFailure()
@@ -1501,6 +1503,8 @@ class LibraryManager:
1501
1503
  return node_class
1502
1504
 
1503
1505
  def load_all_libraries_from_config(self) -> None:
1506
+ # Comment out lines 1503-1545 and call the _load libraries from provenance system to test the other functionality.
1507
+
1504
1508
  # Load metadata for all libraries to determine which ones can be safely loaded
1505
1509
  metadata_request = LoadMetadataForAllLibrariesRequest()
1506
1510
  metadata_result = self.load_metadata_for_all_libraries_request(metadata_request)
@@ -1589,6 +1593,107 @@ class LibraryManager:
1589
1593
  # Go tell the Workflow Manager that it's turn is now.
1590
1594
  GriptapeNodes.WorkflowManager().on_libraries_initialization_complete()
1591
1595
 
1596
+ # Print the engine ready message
1597
+ engine_version_request = GetEngineVersionRequest()
1598
+ engine_version_result = GriptapeNodes.get_instance().handle_engine_version_request(engine_version_request)
1599
+ if isinstance(engine_version_result, GetEngineVersionResultSuccess):
1600
+ engine_version = (
1601
+ f"v{engine_version_result.major}.{engine_version_result.minor}.{engine_version_result.patch}"
1602
+ )
1603
+ else:
1604
+ engine_version = "<UNKNOWN ENGINE VERSION>"
1605
+
1606
+ # Get current session ID
1607
+ session_id = GriptapeNodes.get_session_id()
1608
+ session_info = f" | Session: {session_id[:8]}..." if session_id else " | No Session"
1609
+
1610
+ nodes_app_url = os.getenv("GRIPTAPE_NODES_UI_BASE_URL", "https://nodes.griptape.ai")
1611
+ message = Panel(
1612
+ Align.center(
1613
+ f"[bold green]Engine is ready to receive events[/bold green]\n"
1614
+ f"[bold blue]Return to: [link={nodes_app_url}]{nodes_app_url}[/link] to access the Workflow Editor[/bold blue]",
1615
+ vertical="middle",
1616
+ ),
1617
+ title="🚀 Griptape Nodes Engine Started",
1618
+ subtitle=f"[green]{engine_version}{session_info}[/green]",
1619
+ border_style="green",
1620
+ padding=(1, 4),
1621
+ )
1622
+ console.print(message)
1623
+
1624
+ def _load_libraries_from_provenance_system(self) -> None:
1625
+ """Load libraries using the new provenance-based system with FSM.
1626
+
1627
+ This method converts libraries_to_register entries into LibraryProvenanceLocalFile
1628
+ objects and processes them through the LibraryDirectory and LibraryLifecycleFSM systems.
1629
+ """
1630
+ # Get config manager
1631
+ config_mgr = GriptapeNodes.ConfigManager()
1632
+
1633
+ # Get the current libraries_to_register list
1634
+ user_libraries_section = "app_events.on_app_initialization_complete.libraries_to_register"
1635
+ libraries_to_register: list[str] = config_mgr.get_config_value(user_libraries_section)
1636
+
1637
+ if not libraries_to_register:
1638
+ logger.info("No libraries to register from config")
1639
+ return
1640
+
1641
+ # Convert string paths to LibraryProvenanceLocalFile objects
1642
+ for library_path in libraries_to_register:
1643
+ # Skip non-JSON files for now (requirement specifiers will need different handling)
1644
+ if not library_path.endswith(".json"):
1645
+ logger.debug("Skipping non-JSON library path: %s", library_path)
1646
+ continue
1647
+
1648
+ # Create provenance object
1649
+ provenance = LibraryProvenanceLocalFile(file_path=library_path)
1650
+
1651
+ # Add to directory as user candidate (defaults to active=True)
1652
+ # This automatically creates FSM and runs evaluation
1653
+ self._library_directory.add_user_candidate(provenance)
1654
+
1655
+ logger.debug("Added library provenance: %s", provenance.get_display_name())
1656
+
1657
+ # Get all candidates for evaluation
1658
+ all_candidates = self._library_directory.get_all_candidates()
1659
+
1660
+ logger.info("Evaluated %d library candidates through FSM lifecycle", len(all_candidates))
1661
+
1662
+ # Report on conflicts found
1663
+ self._report_library_name_conflicts()
1664
+
1665
+ # Get candidates that are ready for installation
1666
+ installable_candidates = self._library_directory.get_installable_candidates()
1667
+
1668
+ # Log any skipped libraries
1669
+ active_candidates = self._library_directory.get_active_candidates()
1670
+ for candidate in active_candidates:
1671
+ if candidate not in installable_candidates:
1672
+ blockers = self._library_directory.get_installation_blockers(candidate.provenance)
1673
+ if blockers:
1674
+ blocker_messages = [blocker.message for blocker in blockers]
1675
+ combined_message = "; ".join(blocker_messages)
1676
+ logger.info("Skipping library '%s' - %s", candidate.provenance.get_display_name(), combined_message)
1677
+
1678
+ logger.info("Installing and loading %d installable library candidates", len(installable_candidates))
1679
+
1680
+ # Process installable candidates through installation and loading
1681
+ for candidate in installable_candidates:
1682
+ if self._library_directory.install_library(candidate.provenance):
1683
+ self._library_directory.load_library(candidate.provenance)
1684
+
1685
+ def _report_library_name_conflicts(self) -> None:
1686
+ """Report on library name conflicts found during evaluation."""
1687
+ conflicting_names = self._library_directory.get_all_conflicting_library_names()
1688
+ for library_name in conflicting_names:
1689
+ conflicting_provenances = self._library_directory.get_conflicting_provenances(library_name)
1690
+ logger.warning(
1691
+ "Library name conflict detected for '%s' across %d libraries: %s",
1692
+ library_name,
1693
+ len(conflicting_provenances),
1694
+ [p.get_display_name() for p in conflicting_provenances],
1695
+ )
1696
+
1592
1697
  def _load_advanced_library_module(
1593
1698
  self,
1594
1699
  library_data: LibrarySchema,
@@ -1671,7 +1776,7 @@ class LibraryManager:
1671
1776
  has_disqualifying_issues = False
1672
1777
  for issue in version_issues:
1673
1778
  problems.append(issue.message)
1674
- if issue.severity == LibraryManager.LibraryStatus.UNUSABLE:
1779
+ if issue.severity == LibraryStatus.UNUSABLE:
1675
1780
  has_disqualifying_issues = True
1676
1781
 
1677
1782
  # Early exit if any version issues are disqualifying
@@ -1680,7 +1785,7 @@ class LibraryManager:
1680
1785
  library_path=library_file_path,
1681
1786
  library_name=library_data.name,
1682
1787
  library_version=library_version,
1683
- status=LibraryManager.LibraryStatus.UNUSABLE,
1788
+ status=LibraryStatus.UNUSABLE,
1684
1789
  problems=problems,
1685
1790
  )
1686
1791
 
@@ -1747,13 +1852,13 @@ class LibraryManager:
1747
1852
 
1748
1853
  # Create a LibraryInfo object based on load successes and problem count.
1749
1854
  if not any_nodes_loaded_successfully:
1750
- status = LibraryManager.LibraryStatus.UNUSABLE
1855
+ status = LibraryStatus.UNUSABLE
1751
1856
  elif problems:
1752
1857
  # Success, but errors.
1753
- status = LibraryManager.LibraryStatus.FLAWED
1858
+ status = LibraryStatus.FLAWED
1754
1859
  else:
1755
1860
  # Flawless victory.
1756
- status = LibraryManager.LibraryStatus.GOOD
1861
+ status = LibraryStatus.GOOD
1757
1862
 
1758
1863
  # Create a LibraryInfo object based on load successes and problem count.
1759
1864
  return LibraryManager.LibraryInfo(
@@ -1845,7 +1950,7 @@ class LibraryManager:
1845
1950
  library_path=sandbox_library_dir_as_posix,
1846
1951
  library_name=library_data.name,
1847
1952
  library_version=library_data.metadata.library_version,
1848
- status=LibraryManager.LibraryStatus.UNUSABLE,
1953
+ status=LibraryStatus.UNUSABLE,
1849
1954
  problems=[
1850
1955
  "Failed because a library with this name was already registered. Check the Settings to ensure duplicate libraries are not being loaded."
1851
1956
  ],
@@ -1900,7 +2005,7 @@ class LibraryManager:
1900
2005
 
1901
2006
  paths_to_remove = set()
1902
2007
  for library_path, library_info in self._library_file_path_to_info.items():
1903
- if library_info.status == LibraryManager.LibraryStatus.MISSING:
2008
+ if library_info.status == LibraryStatus.MISSING:
1904
2009
  # Remove this file path from the config.
1905
2010
  paths_to_remove.add(library_path.lower())
1906
2011
 
@@ -218,7 +218,7 @@ class NodeManager:
218
218
  if parent_flow_name == old_name:
219
219
  self._name_to_parent_flow_name[node_name] = new_name
220
220
 
221
- def on_create_node_request(self, request: CreateNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
221
+ def on_create_node_request(self, request: CreateNodeRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
222
222
  # Validate as much as possible before we actually create one.
223
223
  parent_flow_name = request.override_parent_flow_name
224
224
  parent_flow = None
@@ -320,6 +320,7 @@ class NodeManager:
320
320
 
321
321
  logger.log(level=log_level, msg=details)
322
322
 
323
+ # Special handling for paired classes (e.g., create a Start node and it automatically creates a corresponding End node already connected).
323
324
  if isinstance(node, StartLoopNode) and not request.initial_setup:
324
325
  # If it's StartLoop, create an EndLoop and connect it to the StartLoop.
325
326
  # Get the class name of the node
@@ -332,43 +333,42 @@ class NodeManager:
332
333
  # Check and see if the class exists
333
334
  libraries_with_node_type = LibraryRegistry.get_libraries_with_node_type(end_class_name)
334
335
  if not libraries_with_node_type:
335
- msg = f"End class '{end_class_name}' does not exist for start class '{node_class_name}'"
336
- logger.error(msg)
337
- return CreateNodeResultFailure()
338
-
339
- # Create the EndNode
340
- end_loop = GriptapeNodes.handle_request(
341
- CreateNodeRequest(
342
- node_type=end_class_name,
343
- metadata={
344
- "position": {"x": node.metadata["position"]["x"] + 650, "y": node.metadata["position"]["y"]}
345
- },
346
- override_parent_flow_name=parent_flow_name,
347
- )
348
- )
349
- if not isinstance(end_loop, CreateNodeResultSuccess):
350
- msg = f"Failed to create EndLoop node for StartLoop node '{node.name}'"
351
- logger.error(msg)
352
- return CreateNodeResultFailure()
353
-
354
- # Create Loop between output and input to the start node.
355
- GriptapeNodes.handle_request(
356
- CreateConnectionRequest(
357
- source_node_name=node.name,
358
- source_parameter_name="loop",
359
- target_node_name=end_loop.node_name,
360
- target_parameter_name="from_start",
336
+ msg = f"Attempted to create a paried set of nodes for Node '{final_node_name}'. Failed because paired class '{end_class_name}' does not exist for start class '{node_class_name}'. The corresponding node will have to be created by hand and attached manually."
337
+ logger.error(msg) # while this is bad, it's not unsalvageable, so we'll consider this a success.
338
+ else:
339
+ # Create the EndNode
340
+ end_loop = GriptapeNodes.handle_request(
341
+ CreateNodeRequest(
342
+ node_type=end_class_name,
343
+ metadata={
344
+ "position": {"x": node.metadata["position"]["x"] + 650, "y": node.metadata["position"]["y"]}
345
+ },
346
+ override_parent_flow_name=parent_flow_name,
347
+ )
361
348
  )
362
- )
363
- end_node = self.get_node_by_name(end_loop.node_name)
364
- if not isinstance(end_node, EndLoopNode):
365
- msg = f"End node '{end_loop.node_name}' is not a valid EndLoopNode"
366
- logger.error(msg)
367
- return CreateNodeResultFailure()
368
-
369
- # create the connection
370
- node.end_node = end_node
371
- end_node.start_node = node
349
+ if not isinstance(end_loop, CreateNodeResultSuccess):
350
+ msg = f"Attempted to create a paried set of nodes for Node '{final_node_name}'. Failed because paired class '{end_class_name}' failed to get created. The corresponding node will have to be created by hand and attached manually."
351
+ logger.error(msg) # while this is bad, it's not unsalvageable, so we'll consider this a success.
352
+ else:
353
+ # Create Loop between output and input to the start node.
354
+ GriptapeNodes.handle_request(
355
+ CreateConnectionRequest(
356
+ source_node_name=node.name,
357
+ source_parameter_name="loop",
358
+ target_node_name=end_loop.node_name,
359
+ target_parameter_name="from_start",
360
+ )
361
+ )
362
+ end_node = self.get_node_by_name(end_loop.node_name)
363
+ if not isinstance(end_node, EndLoopNode):
364
+ msg = f"Attempted to create a paried set of nodes for Node '{final_node_name}'. Failed because paired node '{end_loop.node_name}' was not a proper EndLoop instance. The corresponding node will have to be created by hand and attached manually."
365
+ logger.error(
366
+ msg
367
+ ) # while this is bad, it's not unsalvageable, so we'll consider this a success.
368
+ else:
369
+ # create the connection - only when we've confirmed correct types
370
+ node.end_node = end_node
371
+ end_node.start_node = node
372
372
 
373
373
  return CreateNodeResultSuccess(
374
374
  node_name=node.name, node_type=node.__class__.__name__, specific_library_name=request.specific_library_name
@@ -428,7 +428,7 @@ class NodeManager:
428
428
  parent_flow.clear_execution_queue()
429
429
  return None
430
430
 
431
- def on_delete_node_request(self, request: DeleteNodeRequest) -> ResultPayload: # noqa: C901, PLR0911 (complex logic, lots of edge cases)
431
+ def on_delete_node_request(self, request: DeleteNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915 (complex logic, lots of edge cases)
432
432
  node_name = request.node_name
433
433
  node = None
434
434
  if node_name is None:
@@ -461,41 +461,55 @@ class NodeManager:
461
461
  cancel_result = self.cancel_conditionally(parent_flow, parent_flow_name, node)
462
462
  if cancel_result is not None:
463
463
  return cancel_result
464
- # Remove all connections from this Node.
465
- list_node_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
466
- list_connections_result = GriptapeNodes.handle_request(request=list_node_connections_request)
467
- if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
468
- details = f"Attempted to delete a Node '{node_name}'. Failed because it could not gather Connections to the Node."
469
- logger.error(details)
470
- return DeleteNodeResultFailure()
471
-
472
- # Destroy all the incoming Connections
473
- for incoming_connection in list_connections_result.incoming_connections:
474
- delete_request = DeleteConnectionRequest(
475
- source_node_name=incoming_connection.source_node_name,
476
- source_parameter_name=incoming_connection.source_parameter_name,
477
- target_node_name=node_name,
478
- target_parameter_name=incoming_connection.target_parameter_name,
479
- )
480
- delete_result = GriptapeNodes.handle_request(delete_request)
481
- if isinstance(delete_result, ResultPayloadFailure):
482
- details = f"Attempted to delete a Node '{node_name}'. Failed when attempting to delete Connection."
464
+ # Remove all connections from this Node using a loop to handle cascading deletions
465
+ any_connections_remain = True
466
+ while any_connections_remain:
467
+ # Assume we're done
468
+ any_connections_remain = False
469
+
470
+ list_node_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
471
+ list_connections_result = GriptapeNodes.handle_request(request=list_node_connections_request)
472
+ if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
473
+ details = f"Attempted to delete a Node '{node_name}'. Failed because it could not gather Connections to the Node."
483
474
  logger.error(details)
484
475
  return DeleteNodeResultFailure()
485
476
 
486
- # Destroy all the outgoing Connections
487
- for outgoing_connection in list_connections_result.outgoing_connections:
488
- delete_request = DeleteConnectionRequest(
489
- source_node_name=node_name,
490
- source_parameter_name=outgoing_connection.source_parameter_name,
491
- target_node_name=outgoing_connection.target_node_name,
492
- target_parameter_name=outgoing_connection.target_parameter_name,
493
- )
494
- delete_result = GriptapeNodes.handle_request(delete_request)
495
- if isinstance(delete_result, ResultPayloadFailure):
496
- details = f"Attempted to delete a Node '{node_name}'. Failed when attempting to delete Connection."
497
- logger.error(details)
498
- return DeleteNodeResultFailure()
477
+ # Check incoming connections
478
+ if list_connections_result.incoming_connections:
479
+ any_connections_remain = True
480
+ connection = list_connections_result.incoming_connections[0]
481
+ delete_request = DeleteConnectionRequest(
482
+ source_node_name=connection.source_node_name,
483
+ source_parameter_name=connection.source_parameter_name,
484
+ target_node_name=node_name,
485
+ target_parameter_name=connection.target_parameter_name,
486
+ )
487
+ delete_result = GriptapeNodes.handle_request(delete_request)
488
+ if isinstance(delete_result, ResultPayloadFailure):
489
+ details = (
490
+ f"Attempted to delete a Node '{node_name}'. Failed when attempting to delete Connection."
491
+ )
492
+ logger.error(details)
493
+ return DeleteNodeResultFailure()
494
+ continue # Refresh connection list after cascading deletions
495
+
496
+ # Check outgoing connections
497
+ if list_connections_result.outgoing_connections:
498
+ any_connections_remain = True
499
+ connection = list_connections_result.outgoing_connections[0]
500
+ delete_request = DeleteConnectionRequest(
501
+ source_node_name=node_name,
502
+ source_parameter_name=connection.source_parameter_name,
503
+ target_node_name=connection.target_node_name,
504
+ target_parameter_name=connection.target_parameter_name,
505
+ )
506
+ delete_result = GriptapeNodes.handle_request(delete_request)
507
+ if isinstance(delete_result, ResultPayloadFailure):
508
+ details = (
509
+ f"Attempted to delete a Node '{node_name}'. Failed when attempting to delete Connection."
510
+ )
511
+ logger.error(details)
512
+ return DeleteNodeResultFailure()
499
513
 
500
514
  # Remove from the owning Flow
501
515
  parent_flow.remove_node(node.name)
@@ -2355,7 +2369,7 @@ class NodeManager:
2355
2369
  node_name=node.name,
2356
2370
  )
2357
2371
  if internal_command is None:
2358
- details = f"Attempted to serialize set value for parameter'{parameter.name}' on node '{node.name}'. The set value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializaibilty or mark the parameter as not serializable."
2372
+ details = f"Attempted to serialize set value for parameter '{parameter.name}' on node '{node.name}'. The set value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializability or mark the parameter as not serializable."
2359
2373
  logger.warning(details)
2360
2374
  else:
2361
2375
  commands.append(internal_command)
@@ -2369,7 +2383,7 @@ class NodeManager:
2369
2383
  node_name=node.name,
2370
2384
  )
2371
2385
  if output_command is None:
2372
- details = f"Attempted to serialize output value for parameter '{parameter.name}' on node '{node.name}'. The output value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializaibilty or mark the parameter as not serializable."
2386
+ details = f"Attempted to serialize output value for parameter '{parameter.name}' on node '{node.name}'. The output value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializability or mark the parameter as not serializable."
2373
2387
  logger.warning(details)
2374
2388
  else:
2375
2389
  commands.append(output_command)
File without changes