griptape-nodes 0.41.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 (133) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -10
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -222
  6. griptape_nodes/app/watch.py +4 -2
  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 +5 -3
  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 +68 -368
  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 +52 -20
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +16 -14
  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 +2 -2
  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 +70 -8
  46. griptape_nodes/retained_mode/events/app_events.py +137 -12
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
  48. griptape_nodes/retained_mode/events/base_events.py +13 -31
  49. griptape_nodes/retained_mode/events/config_events.py +87 -11
  50. griptape_nodes/retained_mode/events/connection_events.py +56 -5
  51. griptape_nodes/retained_mode/events/context_events.py +27 -4
  52. griptape_nodes/retained_mode/events/execution_events.py +99 -14
  53. griptape_nodes/retained_mode/events/flow_events.py +165 -7
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +195 -17
  56. griptape_nodes/retained_mode/events/logger_events.py +11 -0
  57. griptape_nodes/retained_mode/events/node_events.py +242 -22
  58. griptape_nodes/retained_mode/events/object_events.py +40 -4
  59. griptape_nodes/retained_mode/events/os_events.py +116 -3
  60. griptape_nodes/retained_mode/events/parameter_events.py +212 -8
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +59 -7
  63. griptape_nodes/retained_mode/events/static_file_events.py +57 -4
  64. griptape_nodes/retained_mode/events/validation_events.py +39 -4
  65. griptape_nodes/retained_mode/events/workflow_events.py +188 -17
  66. griptape_nodes/retained_mode/griptape_nodes.py +89 -363
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
  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 +146 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +14 -2
  74. griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
  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 +255 -40
  88. griptape_nodes/retained_mode/managers/node_manager.py +120 -103
  89. griptape_nodes/retained_mode/managers/object_manager.py +11 -3
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +582 -8
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +328 -0
  94. griptape_nodes/retained_mode/managers/settings.py +7 -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 +722 -456
  98. griptape_nodes/retained_mode/retained_mode.py +44 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
  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.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -458
  129. griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
  130. griptape_nodes-0.41.0.dist-info/METADATA +0 -78
  131. griptape_nodes-0.41.0.dist-info/RECORD +0 -112
  132. griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
  133. griptape_nodes-0.41.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
@@ -58,6 +58,9 @@ from griptape_nodes.retained_mode.events.library_events import (
58
58
  GetNodeMetadataFromLibraryRequest,
59
59
  GetNodeMetadataFromLibraryResultFailure,
60
60
  GetNodeMetadataFromLibraryResultSuccess,
61
+ ListCapableLibraryEventHandlersRequest,
62
+ ListCapableLibraryEventHandlersResultFailure,
63
+ ListCapableLibraryEventHandlersResultSuccess,
61
64
  ListCategoriesInLibraryRequest,
62
65
  ListCategoriesInLibraryResultFailure,
63
66
  ListCategoriesInLibraryResultSuccess,
@@ -85,29 +88,44 @@ from griptape_nodes.retained_mode.events.library_events import (
85
88
  UnloadLibraryFromRegistryResultSuccess,
86
89
  )
87
90
  from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
91
+ from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
88
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
89
98
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
90
99
 
91
100
  if TYPE_CHECKING:
101
+ from collections.abc import Callable
92
102
  from types import ModuleType
93
103
 
94
104
  from griptape_nodes.node_library.advanced_node_library import AdvancedNodeLibrary
95
- from griptape_nodes.retained_mode.events.base_events import ResultPayload
105
+ from griptape_nodes.retained_mode.events.base_events import Payload, RequestPayload, ResultPayload
96
106
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
97
107
 
98
108
  logger = logging.getLogger("griptape_nodes")
109
+ console = Console()
99
110
 
100
111
 
101
- class LibraryManager:
102
- SANDBOX_LIBRARY_NAME = "Sandbox Library"
112
+ def _find_griptape_uv_bin() -> str:
113
+ """Find the uv binary, checking dedicated Griptape installation first, then system uv.
103
114
 
104
- class LibraryStatus(StrEnum):
105
- """Status of the library that was attempted to be loaded."""
115
+ Returns:
116
+ Path to the uv binary to use
117
+ """
118
+ # Check for dedicated Griptape uv installation first
119
+ dedicated_uv_path = xdg_data_home() / "griptape_nodes" / "bin" / "uv"
120
+ if dedicated_uv_path.exists():
121
+ return str(dedicated_uv_path)
106
122
 
107
- GOOD = "GOOD" # No errors detected during loading. Registered.
108
- FLAWED = "FLAWED" # Some errors detected, but recoverable. Registered.
109
- UNUSABLE = "UNUSABLE" # Errors detected and not recoverable. Not registered.
110
- MISSING = "MISSING" # File not found. Not registered.
123
+ # Fall back to system uv installation
124
+ return uv.find_uv_bin()
125
+
126
+
127
+ class LibraryManager:
128
+ SANDBOX_LIBRARY_NAME = "Sandbox Library"
111
129
 
112
130
  @dataclass
113
131
  class LibraryInfo:
@@ -116,7 +134,7 @@ class LibraryManager:
116
134
  Includes the status of the library, the file path, and any problems encountered during loading.
117
135
  """
118
136
 
119
- status: LibraryManager.LibraryStatus
137
+ status: LibraryStatus
120
138
  library_path: str
121
139
  library_name: str | None = None
122
140
  library_version: str | None = None
@@ -124,6 +142,13 @@ class LibraryManager:
124
142
 
125
143
  _library_file_path_to_info: dict[str, LibraryInfo]
126
144
 
145
+ @dataclass
146
+ class RegisteredEventHandler:
147
+ """Information regarding an event handler from a registered library."""
148
+
149
+ handler: Callable[[RequestPayload], ResultPayload]
150
+ library_data: LibrarySchema
151
+
127
152
  # Stable module namespace mappings for workflow serialization
128
153
  # These mappings ensure that dynamically loaded modules can be reliably imported
129
154
  # in generated workflow code by providing stable, predictable import paths.
@@ -148,10 +173,16 @@ class LibraryManager:
148
173
  self._dynamic_to_stable_module_mapping = {}
149
174
  self._stable_to_dynamic_module_mapping = {}
150
175
  self._library_to_stable_modules = {}
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()
151
179
 
152
180
  event_manager.assign_manager_to_request_type(
153
181
  ListRegisteredLibrariesRequest, self.on_list_registered_libraries_request
154
182
  )
183
+ event_manager.assign_manager_to_request_type(
184
+ ListCapableLibraryEventHandlersRequest, self.on_list_capable_event_handlers
185
+ )
155
186
  event_manager.assign_manager_to_request_type(
156
187
  ListNodeTypesInLibraryRequest, self.on_list_node_types_in_library_request
157
188
  )
@@ -223,10 +254,10 @@ class LibraryManager:
223
254
 
224
255
  # Status emojis mapping
225
256
  status_emoji = {
226
- LibraryManager.LibraryStatus.GOOD: "✅",
227
- LibraryManager.LibraryStatus.FLAWED: "🟡",
228
- LibraryManager.LibraryStatus.UNUSABLE: "❌",
229
- LibraryManager.LibraryStatus.MISSING: "❓",
257
+ LibraryStatus.GOOD: "✅",
258
+ LibraryStatus.FLAWED: "🟡",
259
+ LibraryStatus.UNUSABLE: "❌",
260
+ LibraryStatus.MISSING: "❓",
230
261
  }
231
262
 
232
263
  # Add rows for each library info
@@ -277,6 +308,33 @@ class LibraryManager:
277
308
  return library_info
278
309
  return None
279
310
 
311
+ def on_register_event_handler(
312
+ self,
313
+ request_type: type[RequestPayload],
314
+ handler: Callable[[RequestPayload], ResultPayload],
315
+ library_data: LibrarySchema,
316
+ ) -> None:
317
+ """Register an event handler for a specific request type from a library."""
318
+ if self._library_event_handler_mappings.get(request_type) is None:
319
+ self._library_event_handler_mappings[request_type] = {}
320
+ self._library_event_handler_mappings[request_type][library_data.name] = LibraryManager.RegisteredEventHandler(
321
+ handler=handler, library_data=library_data
322
+ )
323
+
324
+ def get_registered_event_handlers(self, request_type: type[Payload]) -> dict[str, RegisteredEventHandler]:
325
+ """Get all registered event handlers for a specific request type."""
326
+ return self._library_event_handler_mappings.get(request_type, {})
327
+
328
+ def on_list_capable_event_handlers(self, request: ListCapableLibraryEventHandlersRequest) -> ResultPayload:
329
+ """Get all registered event handlers for a specific request type."""
330
+ request_type = PayloadRegistry.get_type(request.request_type)
331
+ if request_type is None:
332
+ return ListCapableLibraryEventHandlersResultFailure(
333
+ exception=KeyError(f"Request type '{request.request_type}' is not registered in the PayloadRegistry.")
334
+ )
335
+ handler_mappings = self.get_registered_event_handlers(request_type)
336
+ return ListCapableLibraryEventHandlersResultSuccess(handlers=list(handler_mappings.keys()))
337
+
280
338
  def on_list_registered_libraries_request(self, _request: ListRegisteredLibrariesRequest) -> ResultPayload:
281
339
  # Make a COPY of the list
282
340
  snapshot_list = LibraryRegistry.list_libraries()
@@ -350,7 +408,7 @@ class LibraryManager:
350
408
  return LoadLibraryMetadataFromFileResultFailure(
351
409
  library_path=file_path,
352
410
  library_name=None,
353
- status=LibraryManager.LibraryStatus.MISSING,
411
+ status=LibraryStatus.MISSING,
354
412
  problems=[
355
413
  "Library could not be found at the file path specified. It will be removed from the configuration."
356
414
  ],
@@ -366,7 +424,7 @@ class LibraryManager:
366
424
  return LoadLibraryMetadataFromFileResultFailure(
367
425
  library_path=file_path,
368
426
  library_name=None,
369
- status=LibraryManager.LibraryStatus.UNUSABLE,
427
+ status=LibraryStatus.UNUSABLE,
370
428
  problems=["Library file not formatted as proper JSON."],
371
429
  )
372
430
  except Exception as err:
@@ -375,7 +433,7 @@ class LibraryManager:
375
433
  return LoadLibraryMetadataFromFileResultFailure(
376
434
  library_path=file_path,
377
435
  library_name=None,
378
- status=LibraryManager.LibraryStatus.UNUSABLE,
436
+ status=LibraryStatus.UNUSABLE,
379
437
  problems=[f"Exception occurred when attempting to load the library: {err}."],
380
438
  )
381
439
 
@@ -399,7 +457,7 @@ class LibraryManager:
399
457
  return LoadLibraryMetadataFromFileResultFailure(
400
458
  library_path=file_path,
401
459
  library_name=library_name,
402
- status=LibraryManager.LibraryStatus.UNUSABLE,
460
+ status=LibraryStatus.UNUSABLE,
403
461
  problems=problems,
404
462
  )
405
463
  except Exception as err:
@@ -408,7 +466,7 @@ class LibraryManager:
408
466
  return LoadLibraryMetadataFromFileResultFailure(
409
467
  library_path=file_path,
410
468
  library_name=library_name,
411
- status=LibraryManager.LibraryStatus.UNUSABLE,
469
+ status=LibraryStatus.UNUSABLE,
412
470
  problems=[f"Library file did not match the library schema specified due to: {err}"],
413
471
  )
414
472
 
@@ -482,7 +540,7 @@ class LibraryManager:
482
540
  return LoadLibraryMetadataFromFileResultFailure(
483
541
  library_path=sandbox_library_dir_as_posix,
484
542
  library_name=LibraryManager.SANDBOX_LIBRARY_NAME,
485
- status=LibraryManager.LibraryStatus.MISSING,
543
+ status=LibraryStatus.MISSING,
486
544
  problems=["Sandbox directory does not exist."],
487
545
  )
488
546
 
@@ -537,7 +595,7 @@ class LibraryManager:
537
595
  return LoadLibraryMetadataFromFileResultFailure(
538
596
  library_path=sandbox_library_dir_as_posix,
539
597
  library_name=LibraryManager.SANDBOX_LIBRARY_NAME,
540
- status=LibraryManager.LibraryStatus.UNUSABLE,
598
+ status=LibraryStatus.UNUSABLE,
541
599
  problems=["Could not get engine version for sandbox library generation."],
542
600
  )
543
601
 
@@ -621,7 +679,7 @@ class LibraryManager:
621
679
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
622
680
  library_path=file_path,
623
681
  library_name=None,
624
- status=LibraryManager.LibraryStatus.MISSING,
682
+ status=LibraryStatus.MISSING,
625
683
  problems=[
626
684
  "Library could not be found at the file path specified. It will be removed from the configuration."
627
685
  ],
@@ -655,7 +713,7 @@ class LibraryManager:
655
713
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
656
714
  library_path=file_path,
657
715
  library_name=library_data.name,
658
- status=LibraryManager.LibraryStatus.UNUSABLE,
716
+ status=LibraryStatus.UNUSABLE,
659
717
  problems=[
660
718
  f"Library's version string '{library_data.metadata.library_version}' wasn't valid. Must be in major.minor.patch format."
661
719
  ],
@@ -682,7 +740,7 @@ class LibraryManager:
682
740
  library_path=file_path,
683
741
  library_name=library_data.name,
684
742
  library_version=library_version,
685
- status=LibraryManager.LibraryStatus.UNUSABLE,
743
+ status=LibraryStatus.UNUSABLE,
686
744
  problems=[
687
745
  f"Failed to load Advanced Library module from '{library_data.advanced_library_path}': {err}"
688
746
  ],
@@ -706,7 +764,7 @@ class LibraryManager:
706
764
  library_path=file_path,
707
765
  library_name=library_data.name,
708
766
  library_version=library_version,
709
- status=LibraryManager.LibraryStatus.UNUSABLE,
767
+ status=LibraryStatus.UNUSABLE,
710
768
  problems=[
711
769
  "Failed because a library with this name was already registered. Check the Settings to ensure duplicate libraries are not being loaded."
712
770
  ],
@@ -735,13 +793,35 @@ class LibraryManager:
735
793
  library_path=file_path,
736
794
  library_name=library_data.name,
737
795
  library_version=library_version,
738
- status=LibraryManager.LibraryStatus.UNUSABLE,
796
+ status=LibraryStatus.UNUSABLE,
739
797
  problems=[str(e)],
740
798
  )
741
799
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed when creating the virtual environment: {e}."
742
800
  logger.error(details)
743
801
  return RegisterLibraryFromFileResultFailure()
744
802
  if self._can_write_to_venv_location(library_venv_python_path):
803
+ # Check disk space before installing dependencies
804
+ config_manager = GriptapeNodes.ConfigManager()
805
+ min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_libraries")
806
+ if not OSManager.check_available_disk_space(Path(venv_path), min_space_gb):
807
+ error_msg = OSManager.format_disk_space_error(Path(venv_path))
808
+ logger.error(
809
+ "Attempted to load Library JSON from '%s'. Failed when installing dependencies (requires %.1f GB): %s",
810
+ json_path,
811
+ min_space_gb,
812
+ error_msg,
813
+ )
814
+ self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
815
+ library_path=file_path,
816
+ library_name=library_data.name,
817
+ library_version=library_version,
818
+ status=LibraryStatus.UNUSABLE,
819
+ problems=[
820
+ f"Insufficient disk space for dependencies (requires {min_space_gb} GB): {error_msg}"
821
+ ],
822
+ )
823
+ return RegisterLibraryFromFileResultFailure()
824
+
745
825
  # Grab the python executable from the virtual environment so that we can pip install there
746
826
  logger.info(
747
827
  "Installing dependencies for library '%s' with pip in venv at %s", library_data.name, venv_path
@@ -771,11 +851,12 @@ class LibraryManager:
771
851
  except subprocess.CalledProcessError as e:
772
852
  # Failed to create the library
773
853
  error_details = f"return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
854
+
774
855
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
775
856
  library_path=file_path,
776
857
  library_name=library_data.name,
777
858
  library_version=library_version,
778
- status=LibraryManager.LibraryStatus.UNUSABLE,
859
+ status=LibraryStatus.UNUSABLE,
779
860
  problems=[f"Dependency installation failed: {error_details}"],
780
861
  )
781
862
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed when installing dependencies: {error_details}"
@@ -807,7 +888,9 @@ class LibraryManager:
807
888
  else:
808
889
  # We had an existing category. Union our changes into it (not replacing anything that matched).
809
890
  existing_category_contents = get_category_result.contents
810
- 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
+ }
811
894
  set_category_request = SetConfigCategoryRequest(
812
895
  category=library_data_setting.category, contents=existing_category_contents
813
896
  )
@@ -830,15 +913,15 @@ class LibraryManager:
830
913
  self._library_file_path_to_info[file_path] = library_load_results
831
914
 
832
915
  match library_load_results.status:
833
- case LibraryManager.LibraryStatus.GOOD:
916
+ case LibraryStatus.GOOD:
834
917
  details = f"Successfully loaded Library '{library_data.name}' from JSON file at {json_path}"
835
918
  logger.info(details)
836
919
  return RegisterLibraryFromFileResultSuccess(library_name=library_data.name)
837
- case LibraryManager.LibraryStatus.FLAWED:
920
+ case LibraryStatus.FLAWED:
838
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."
839
922
  logger.warning(details)
840
923
  return RegisterLibraryFromFileResultSuccess(library_name=library_data.name)
841
- case LibraryManager.LibraryStatus.UNUSABLE:
924
+ case LibraryStatus.UNUSABLE:
842
925
  details = f"Attempted to load Library JSON file from '{json_path}'. Failed because no nodes were loaded. Check the log for more details."
843
926
  logger.error(details)
844
927
  return RegisterLibraryFromFileResultFailure()
@@ -863,10 +946,23 @@ class LibraryManager:
863
946
  logger.error(details)
864
947
  return RegisterLibraryFromRequirementSpecifierResultFailure()
865
948
  if self._can_write_to_venv_location(library_python_venv_path):
949
+ # Check disk space before installing dependencies
950
+ config_manager = GriptapeNodes.ConfigManager()
951
+ min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_libraries")
952
+ if not OSManager.check_available_disk_space(Path(venv_path), min_space_gb):
953
+ error_msg = OSManager.format_disk_space_error(Path(venv_path))
954
+ logger.error(
955
+ "Attempted to install library '%s'. Failed when installing dependencies (requires %.1f GB): %s",
956
+ request.requirement_specifier,
957
+ min_space_gb,
958
+ error_msg,
959
+ )
960
+ return RegisterLibraryFromRequirementSpecifierResultFailure()
961
+
866
962
  logger.info("Installing dependency '%s' with pip in venv at %s", package_name, venv_path)
867
963
  subprocess.run( # noqa: S603
868
964
  [
869
- uv.find_uv_bin(),
965
+ _find_griptape_uv_bin(),
870
966
  "pip",
871
967
  "install",
872
968
  request.requirement_specifier,
@@ -922,6 +1018,19 @@ class LibraryManager:
922
1018
  if library_venv_path.exists():
923
1019
  logger.debug("Virtual environment already exists at %s", library_venv_path)
924
1020
  else:
1021
+ # Check disk space before creating virtual environment
1022
+ config_manager = GriptapeNodes.ConfigManager()
1023
+ min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_libraries")
1024
+ if not OSManager.check_available_disk_space(library_venv_path.parent, min_space_gb):
1025
+ error_msg = OSManager.format_disk_space_error(library_venv_path.parent)
1026
+ logger.error(
1027
+ "Attempted to create virtual environment (requires %.1f GB). Failed: %s", min_space_gb, error_msg
1028
+ )
1029
+ error_message = (
1030
+ f"Disk space error creating virtual environment (requires {min_space_gb} GB): {error_msg}"
1031
+ )
1032
+ raise RuntimeError(error_message)
1033
+
925
1034
  try:
926
1035
  logger.info("Creating virtual environment at %s with Python %s", library_venv_path, python_version)
927
1036
  subprocess.run( # noqa: S603
@@ -1394,6 +1503,8 @@ class LibraryManager:
1394
1503
  return node_class
1395
1504
 
1396
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
+
1397
1508
  # Load metadata for all libraries to determine which ones can be safely loaded
1398
1509
  metadata_request = LoadMetadataForAllLibrariesRequest()
1399
1510
  metadata_result = self.load_metadata_for_all_libraries_request(metadata_request)
@@ -1441,6 +1552,9 @@ class LibraryManager:
1441
1552
  self._remove_missing_libraries_from_config(config_category=user_libraries_section)
1442
1553
 
1443
1554
  def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
1555
+ GriptapeNodes.EngineIdentityManager().initialize_engine_id()
1556
+ GriptapeNodes.SessionManager().get_saved_session_id()
1557
+
1444
1558
  # App just got init'd. See if there are library JSONs to load!
1445
1559
  self.load_all_libraries_from_config()
1446
1560
 
@@ -1479,6 +1593,107 @@ class LibraryManager:
1479
1593
  # Go tell the Workflow Manager that it's turn is now.
1480
1594
  GriptapeNodes.WorkflowManager().on_libraries_initialization_complete()
1481
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
+
1482
1697
  def _load_advanced_library_module(
1483
1698
  self,
1484
1699
  library_data: LibrarySchema,
@@ -1561,7 +1776,7 @@ class LibraryManager:
1561
1776
  has_disqualifying_issues = False
1562
1777
  for issue in version_issues:
1563
1778
  problems.append(issue.message)
1564
- if issue.severity == LibraryManager.LibraryStatus.UNUSABLE:
1779
+ if issue.severity == LibraryStatus.UNUSABLE:
1565
1780
  has_disqualifying_issues = True
1566
1781
 
1567
1782
  # Early exit if any version issues are disqualifying
@@ -1570,7 +1785,7 @@ class LibraryManager:
1570
1785
  library_path=library_file_path,
1571
1786
  library_name=library_data.name,
1572
1787
  library_version=library_version,
1573
- status=LibraryManager.LibraryStatus.UNUSABLE,
1788
+ status=LibraryStatus.UNUSABLE,
1574
1789
  problems=problems,
1575
1790
  )
1576
1791
 
@@ -1637,13 +1852,13 @@ class LibraryManager:
1637
1852
 
1638
1853
  # Create a LibraryInfo object based on load successes and problem count.
1639
1854
  if not any_nodes_loaded_successfully:
1640
- status = LibraryManager.LibraryStatus.UNUSABLE
1855
+ status = LibraryStatus.UNUSABLE
1641
1856
  elif problems:
1642
1857
  # Success, but errors.
1643
- status = LibraryManager.LibraryStatus.FLAWED
1858
+ status = LibraryStatus.FLAWED
1644
1859
  else:
1645
1860
  # Flawless victory.
1646
- status = LibraryManager.LibraryStatus.GOOD
1861
+ status = LibraryStatus.GOOD
1647
1862
 
1648
1863
  # Create a LibraryInfo object based on load successes and problem count.
1649
1864
  return LibraryManager.LibraryInfo(
@@ -1735,7 +1950,7 @@ class LibraryManager:
1735
1950
  library_path=sandbox_library_dir_as_posix,
1736
1951
  library_name=library_data.name,
1737
1952
  library_version=library_data.metadata.library_version,
1738
- status=LibraryManager.LibraryStatus.UNUSABLE,
1953
+ status=LibraryStatus.UNUSABLE,
1739
1954
  problems=[
1740
1955
  "Failed because a library with this name was already registered. Check the Settings to ensure duplicate libraries are not being loaded."
1741
1956
  ],
@@ -1790,7 +2005,7 @@ class LibraryManager:
1790
2005
 
1791
2006
  paths_to_remove = set()
1792
2007
  for library_path, library_info in self._library_file_path_to_info.items():
1793
- if library_info.status == LibraryManager.LibraryStatus.MISSING:
2008
+ if library_info.status == LibraryStatus.MISSING:
1794
2009
  # Remove this file path from the config.
1795
2010
  paths_to_remove.add(library_path.lower())
1796
2011