griptape-nodes 0.62.2__py3-none-any.whl → 0.63.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 (74) hide show
  1. griptape_nodes/cli/commands/libraries.py +6 -21
  2. griptape_nodes/drivers/thread_storage/__init__.py +15 -0
  3. griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
  4. griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
  5. griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
  6. griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
  7. griptape_nodes/node_library/library_registry.py +16 -9
  8. griptape_nodes/node_library/workflow_registry.py +1 -1
  9. griptape_nodes/retained_mode/events/agent_events.py +232 -9
  10. griptape_nodes/retained_mode/events/app_events.py +38 -0
  11. griptape_nodes/retained_mode/events/library_events.py +32 -3
  12. griptape_nodes/retained_mode/events/os_events.py +101 -1
  13. griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
  14. griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
  15. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
  16. griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
  17. griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
  18. griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
  19. griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
  20. griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
  21. griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
  22. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
  23. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
  24. griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
  25. griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
  26. griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
  27. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
  28. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
  29. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
  30. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
  31. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
  32. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
  35. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
  36. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
  37. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
  38. griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
  39. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
  40. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
  41. griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
  42. griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
  43. griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
  44. griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
  45. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
  46. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
  47. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
  48. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
  49. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
  50. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
  51. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
  52. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
  53. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
  54. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
  55. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
  56. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
  57. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
  58. griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
  59. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
  60. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
  61. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
  62. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
  63. griptape_nodes/retained_mode/managers/library_manager.py +226 -77
  64. griptape_nodes/retained_mode/managers/os_manager.py +172 -1
  65. griptape_nodes/retained_mode/managers/settings.py +5 -0
  66. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
  67. griptape_nodes/retained_mode/managers/workflow_manager.py +237 -159
  68. griptape_nodes/servers/static.py +18 -19
  69. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
  70. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
  71. {griptape_nodes-0.62.2.dist-info → griptape_nodes-0.63.0.dist-info}/METADATA +2 -1
  72. {griptape_nodes-0.62.2.dist-info → griptape_nodes-0.63.0.dist-info}/RECORD +74 -21
  73. {griptape_nodes-0.62.2.dist-info → griptape_nodes-0.63.0.dist-info}/WHEEL +0 -0
  74. {griptape_nodes-0.62.2.dist-info → griptape_nodes-0.63.0.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ import asyncio
5
5
  import logging
6
6
  import pickle
7
7
  import re
8
+ from collections import defaultdict
8
9
  from dataclasses import dataclass, field, fields, is_dataclass
9
10
  from datetime import UTC, datetime
10
11
  from enum import StrEnum
@@ -32,12 +33,15 @@ from griptape_nodes.node_library.workflow_registry import (
32
33
  WorkflowShape,
33
34
  )
34
35
  from griptape_nodes.retained_mode.events.app_events import (
36
+ EngineInitializationProgress,
35
37
  GetEngineVersionRequest,
36
38
  GetEngineVersionResultSuccess,
39
+ InitializationPhase,
40
+ InitializationStatus,
37
41
  )
38
42
 
39
43
  # Runtime imports for ResultDetails since it's used at runtime
40
- from griptape_nodes.retained_mode.events.base_events import ResultDetail, ResultDetails
44
+ from griptape_nodes.retained_mode.events.base_events import AppEvent, ResultDetail, ResultDetails
41
45
  from griptape_nodes.retained_mode.events.flow_events import (
42
46
  CreateFlowRequest,
43
47
  GetTopLevelFlowRequest,
@@ -51,6 +55,8 @@ from griptape_nodes.retained_mode.events.flow_events import (
51
55
  from griptape_nodes.retained_mode.events.library_events import (
52
56
  GetLibraryMetadataRequest,
53
57
  GetLibraryMetadataResultSuccess,
58
+ ListRegisteredLibrariesRequest,
59
+ ListRegisteredLibrariesResultSuccess,
54
60
  )
55
61
  from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
56
62
  from griptape_nodes.retained_mode.events.workflow_events import (
@@ -115,6 +121,22 @@ from griptape_nodes.retained_mode.events.workflow_events import (
115
121
  from griptape_nodes.retained_mode.griptape_nodes import (
116
122
  GriptapeNodes,
117
123
  )
124
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows import (
125
+ InvalidDependencyVersionStringProblem,
126
+ InvalidLibraryVersionStringProblem,
127
+ InvalidMetadataSchemaProblem,
128
+ InvalidMetadataSectionCountProblem,
129
+ InvalidTomlFormatProblem,
130
+ LibraryNotRegisteredProblem,
131
+ LibraryVersionBelowRequiredProblem,
132
+ LibraryVersionLargeDifferenceProblem,
133
+ LibraryVersionMajorMismatchProblem,
134
+ LibraryVersionMinorDifferenceProblem,
135
+ MissingCreationDateProblem,
136
+ MissingLastModifiedDateProblem,
137
+ MissingTomlSectionProblem,
138
+ WorkflowNotFoundProblem,
139
+ )
118
140
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
119
141
 
120
142
  if TYPE_CHECKING:
@@ -125,6 +147,7 @@ if TYPE_CHECKING:
125
147
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
126
148
  from griptape_nodes.retained_mode.events.node_events import SerializedNodeCommands, SetLockNodeStateRequest
127
149
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
150
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
128
151
 
129
152
 
130
153
  T = TypeVar("T")
@@ -186,7 +209,7 @@ class WorkflowManager:
186
209
  workflow_path: str
187
210
  workflow_name: str | None = None
188
211
  workflow_dependencies: list[WorkflowManager.WorkflowDependencyInfo] = field(default_factory=list)
189
- problems: list[str] = field(default_factory=list)
212
+ problems: list[WorkflowProblem] = field(default_factory=list)
190
213
 
191
214
  _workflow_file_path_to_info: dict[str, WorkflowInfo]
192
215
 
@@ -416,30 +439,48 @@ class WorkflowManager:
416
439
 
417
440
  return matches
418
441
 
419
- def print_workflow_load_status(self) -> None:
442
+ def print_workflow_load_status(self, min_status: WorkflowStatus = WorkflowStatus.FLAWED) -> None: # noqa: PLR0915
420
443
  workflow_file_paths = self.get_workflows_attempted_to_load()
421
444
  workflow_infos = []
422
445
  for workflow_file_path in workflow_file_paths:
423
446
  workflow_info = self.get_workflow_info_for_attempted_load(workflow_file_path)
424
447
  workflow_infos.append(workflow_info)
425
448
 
449
+ # Filter workflows to only show those at or worse than min_status
450
+ all_statuses = list(self.WorkflowStatus)
451
+ min_status_index = all_statuses.index(min_status)
452
+ filtered_workflow_infos = [
453
+ wf_info for wf_info in workflow_infos if all_statuses.index(wf_info.status) >= min_status_index
454
+ ]
455
+
456
+ # Sort workflows by severity (worst to best)
457
+ filtered_workflow_infos.sort(key=lambda wf: all_statuses.index(wf.status), reverse=True)
458
+
426
459
  console = Console()
427
460
 
428
461
  # Check if the list is empty
429
- if not workflow_infos:
430
- # Display a message indicating no workflows are available
462
+ if not filtered_workflow_infos:
431
463
  empty_message = Text("No workflow information available", style="italic")
432
464
  panel = Panel(empty_message, title="Workflow Information", border_style="blue")
433
465
  console.print(panel)
434
466
  return
435
467
 
436
- # Create a table with five columns and row dividers
468
+ # Add filter message if not showing all workflows
469
+ if min_status != self.WorkflowStatus.GOOD:
470
+ statuses_shown = all_statuses[min_status_index:]
471
+ status_names = ", ".join(s.value for s in statuses_shown)
472
+ filter_message = Text(
473
+ f"Only displaying workflows with a fitness of {status_names}",
474
+ style="italic yellow",
475
+ )
476
+ console.print(filter_message)
477
+ console.print()
478
+
479
+ # Create a table with three columns and row dividers
437
480
  table = Table(show_header=True, box=HEAVY_EDGE, show_lines=True, expand=True)
438
- table.add_column("Workflow Name", style="green")
439
- table.add_column("Status", style="green")
440
- table.add_column("File Path", style="cyan")
441
- table.add_column("Problems", style="yellow")
442
- table.add_column("Dependencies", style="magenta")
481
+ table.add_column("Workflow", style="green", ratio=2)
482
+ table.add_column("Problems", style="yellow", ratio=3)
483
+ table.add_column("Dependencies", style="magenta", ratio=2)
443
484
 
444
485
  # Status emojis mapping
445
486
  status_emoji = {
@@ -449,6 +490,14 @@ class WorkflowManager:
449
490
  self.WorkflowStatus.MISSING: "[red]?[/red]",
450
491
  }
451
492
 
493
+ # Status text mapping (colored)
494
+ status_text = {
495
+ self.WorkflowStatus.GOOD: "[green](GOOD)[/green]",
496
+ self.WorkflowStatus.FLAWED: "[yellow](FLAWED)[/yellow]",
497
+ self.WorkflowStatus.UNUSABLE: "[red](UNUSABLE)[/red]",
498
+ self.WorkflowStatus.MISSING: "[red](MISSING)[/red]",
499
+ }
500
+
452
501
  dependency_status_emoji = {
453
502
  self.WorkflowDependencyStatus.PERFECT: "[green]OK[/green]",
454
503
  self.WorkflowDependencyStatus.GOOD: "[green]GOOD[/green]",
@@ -459,19 +508,37 @@ class WorkflowManager:
459
508
  }
460
509
 
461
510
  # Add rows for each workflow info
462
- for wf_info in workflow_infos:
463
- # File path column
464
- file_path = wf_info.workflow_path
465
- file_path_text = Text(file_path, style="cyan")
466
- file_path_text.overflow = "fold" # Force wrapping
467
-
468
- # Workflow name column with emoji based on status
511
+ for wf_info in filtered_workflow_infos:
512
+ # Workflow name column with emoji, name, colored status, and file path underneath
469
513
  emoji = status_emoji.get(wf_info.status, "ERR: Unknown/Unexpected Workflow Status")
514
+ colored_status = status_text.get(wf_info.status, "(UNKNOWN)")
470
515
  name = wf_info.workflow_name if wf_info.workflow_name else "*UNKNOWN*"
471
- workflow_name = f"{emoji} - {name}"
516
+ file_path = wf_info.workflow_path
517
+ workflow_name_with_path = Text.from_markup(
518
+ f"{emoji} - {name} {colored_status}\n[cyan dim]{file_path}[/cyan dim]"
519
+ )
520
+ workflow_name_with_path.overflow = "fold"
472
521
 
473
- # Problems column - format with numbers if there's more than one
474
- problems = "\n".join(wf_info.problems) if wf_info.problems else "No problems detected."
522
+ # Problems column - collate by type
523
+ if not wf_info.problems:
524
+ problems = "No problems detected."
525
+ else:
526
+ # Group problems by type
527
+ problems_by_type = defaultdict(list)
528
+ for problem in wf_info.problems:
529
+ problems_by_type[type(problem)].append(problem)
530
+
531
+ # Collate each group
532
+ collated_strings = []
533
+ for problem_class, instances in problems_by_type.items():
534
+ collated_display = problem_class.collate_problems_for_display(instances)
535
+ collated_strings.append(collated_display)
536
+
537
+ # Format for display
538
+ if len(collated_strings) == 1:
539
+ problems = collated_strings[0]
540
+ else:
541
+ problems = "\n".join([f"{j + 1}. {problem}" for j, problem in enumerate(collated_strings)])
475
542
 
476
543
  # Dependencies column
477
544
  if wf_info.status == self.WorkflowStatus.MISSING or (
@@ -489,9 +556,7 @@ class WorkflowManager:
489
556
  )
490
557
 
491
558
  table.add_row(
492
- workflow_name,
493
- wf_info.status.value,
494
- file_path_text,
559
+ workflow_name_with_path,
495
560
  problems,
496
561
  dependencies,
497
562
  )
@@ -870,9 +935,7 @@ class WorkflowManager:
870
935
  workflow_path=str_path,
871
936
  workflow_name=None,
872
937
  workflow_dependencies=[],
873
- problems=[
874
- "Workflow could not be found at the file path specified. It will be removed from the configuration."
875
- ],
938
+ problems=[WorkflowNotFoundProblem()],
876
939
  )
877
940
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}. Failed because no file could be found at that path."
878
941
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -886,9 +949,7 @@ class WorkflowManager:
886
949
  workflow_path=str_path,
887
950
  workflow_name=None,
888
951
  workflow_dependencies=[],
889
- problems=[
890
- f"Failed as it had {len(matches)} sections titled '{block_name}', and we expect exactly 1 such section."
891
- ],
952
+ problems=[InvalidMetadataSectionCountProblem(section_name=block_name, count=len(matches))],
892
953
  )
893
954
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed as it had {len(matches)} sections titled '{block_name}', and we expect exactly 1 such section."
894
955
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -907,7 +968,7 @@ class WorkflowManager:
907
968
  workflow_path=str_path,
908
969
  workflow_name=None,
909
970
  workflow_dependencies=[],
910
- problems=[f"Failed because the metadata was not valid TOML: {err}"],
971
+ problems=[InvalidTomlFormatProblem(error_message=str(err))],
911
972
  )
912
973
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the metadata was not valid TOML: {err}"
913
974
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -922,7 +983,7 @@ class WorkflowManager:
922
983
  workflow_path=str_path,
923
984
  workflow_name=None,
924
985
  workflow_dependencies=[],
925
- problems=[f"Failed because the '[{tool_header}.{griptape_nodes_header}]' section could not be found."],
986
+ problems=[MissingTomlSectionProblem(section_path=f"[{tool_header}.{griptape_nodes_header}]")],
926
987
  )
927
988
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the '[{tool_header}.{griptape_nodes_header}]' section could not be found: {err}"
928
989
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -938,7 +999,9 @@ class WorkflowManager:
938
999
  workflow_name=None,
939
1000
  workflow_dependencies=[],
940
1001
  problems=[
941
- f"Failed because the metadata in the '[{tool_header}.{griptape_nodes_header}]' section did not match the requisite schema with error: {err}"
1002
+ InvalidMetadataSchemaProblem(
1003
+ section_path=f"[{tool_header}.{griptape_nodes_header}]", error_message=str(err)
1004
+ )
942
1005
  ],
943
1006
  )
944
1007
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the metadata in the '[{tool_header}.{griptape_nodes_header}]' section did not match the requisite schema with error: {err}"
@@ -953,15 +1016,23 @@ class WorkflowManager:
953
1016
  if workflow_metadata.creation_date is None:
954
1017
  # Assign it to the epoch start and flag it as a warning.
955
1018
  workflow_metadata.creation_date = WorkflowManager.EPOCH_START
956
- problems.append(
957
- f"Workflow metadata was missing a creation date. Defaulting to {WorkflowManager.EPOCH_START}. This value will be replaced with the current date the first time it is saved."
958
- )
1019
+ problems.append(MissingCreationDateProblem(default_date=str(WorkflowManager.EPOCH_START)))
959
1020
  if workflow_metadata.last_modified_date is None:
960
1021
  # Assign it to the epoch start and flag it as a warning.
961
1022
  workflow_metadata.last_modified_date = WorkflowManager.EPOCH_START
962
- problems.append(
963
- f"Workflow metadata was missing a last modified date. Defaulting to {WorkflowManager.EPOCH_START}. This value will be replaced with the current date the first time it is saved."
964
- )
1023
+ problems.append(MissingLastModifiedDateProblem(default_date=str(WorkflowManager.EPOCH_START)))
1024
+
1025
+ # Get list of registered libraries once (silent check - no error logging)
1026
+ list_libraries_request = ListRegisteredLibrariesRequest()
1027
+ list_libraries_result = GriptapeNodes.LibraryManager().on_list_registered_libraries_request(
1028
+ list_libraries_request
1029
+ )
1030
+
1031
+ if not isinstance(list_libraries_result, ListRegisteredLibrariesResultSuccess):
1032
+ # Should not happen, but handle gracefully - treat as no libraries registered
1033
+ registered_libraries = []
1034
+ else:
1035
+ registered_libraries = list_libraries_result.libraries
965
1036
 
966
1037
  dependency_infos = []
967
1038
  for node_library_referenced in workflow_metadata.node_libraries_referenced:
@@ -972,7 +1043,7 @@ class WorkflowManager:
972
1043
  except Exception:
973
1044
  had_critical_error = True
974
1045
  problems.append(
975
- f"Workflow cited an invalid version string '{desired_version_str}' for library '{library_name}'. Must be specified in major.minor.patch format."
1046
+ InvalidDependencyVersionStringProblem(library_name=library_name, version_string=desired_version_str)
976
1047
  )
977
1048
  dependency_infos.append(
978
1049
  WorkflowManager.WorkflowDependencyInfo(
@@ -985,20 +1056,32 @@ class WorkflowManager:
985
1056
  # SKIP IT.
986
1057
  continue
987
1058
  # See how our desired version compares against the actual library we (may) have.
988
- # See if the library exists.
1059
+ # Check if library is registered (silent check - no error logging)
1060
+ if library_name not in registered_libraries:
1061
+ # Library not registered
1062
+ had_critical_error = True
1063
+ problems.append(LibraryNotRegisteredProblem(library_name=library_name))
1064
+ dependency_infos.append(
1065
+ WorkflowManager.WorkflowDependencyInfo(
1066
+ library_name=library_name,
1067
+ version_requested=desired_version_str,
1068
+ version_present=None,
1069
+ status=WorkflowManager.WorkflowDependencyStatus.MISSING,
1070
+ )
1071
+ )
1072
+ # SKIP IT.
1073
+ continue
1074
+
1075
+ # Get library metadata (we know library is registered, so no error logging)
989
1076
  library_metadata_request = GetLibraryMetadataRequest(library=library_name)
990
- # NOTE: Per https://github.com/griptape-ai/griptape-vsl-gui/issues/1123, we
991
- # generate a FLOOD of error messages here that can swamp the GUI. We'll call
992
- # directly instead of the usual handle_request() path so we don't generate those.
993
1077
  library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
994
1078
  library_metadata_request
995
1079
  )
1080
+
996
1081
  if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
997
- # Metadata failed to be found.
1082
+ # Should not happen since we verified library is registered, but handle gracefully
998
1083
  had_critical_error = True
999
- problems.append(
1000
- f"Library '{library_name}' was not successfully registered. It may have other problems that prevented it from loading."
1001
- )
1084
+ problems.append(LibraryNotRegisteredProblem(library_name=library_name))
1002
1085
  dependency_infos.append(
1003
1086
  WorkflowManager.WorkflowDependencyInfo(
1004
1087
  library_name=library_name,
@@ -1018,7 +1101,7 @@ class WorkflowManager:
1018
1101
  except Exception:
1019
1102
  had_critical_error = True
1020
1103
  problems.append(
1021
- f"Library an invalid version string '{library_version_str}' for library '{library_name}'. Must be specified in major.minor.patch format."
1104
+ InvalidLibraryVersionStringProblem(library_name=library_name, version_string=library_version_str)
1022
1105
  )
1023
1106
  dependency_infos.append(
1024
1107
  WorkflowManager.WorkflowDependencyInfo(
@@ -1043,24 +1126,40 @@ class WorkflowManager:
1043
1126
  delta = library_version.minor - desired_version.minor
1044
1127
  if delta < 0:
1045
1128
  problems.append(
1046
- f"Library '{library_name}' is at version '{library_version}', which is below the desired version."
1129
+ LibraryVersionBelowRequiredProblem(
1130
+ library_name=library_name,
1131
+ current_version=str(library_version),
1132
+ required_version=str(desired_version),
1133
+ )
1047
1134
  )
1048
1135
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1049
1136
  had_critical_error = True
1050
1137
  elif delta > WorkflowManager.MAX_MINOR_VERSION_DEVIATION:
1051
1138
  problems.append(
1052
- f"This workflow was built with library '{library_name}' v{desired_version}, but you have v{library_version}. This large version difference may cause compatibility issues. You can update the library to a compatible version or save this workflow to update it to your current library versions."
1139
+ LibraryVersionLargeDifferenceProblem(
1140
+ library_name=library_name,
1141
+ workflow_version=str(desired_version),
1142
+ current_version=str(library_version),
1143
+ )
1053
1144
  )
1054
1145
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1055
1146
  had_critical_error = True
1056
1147
  else:
1057
1148
  problems.append(
1058
- f"This workflow was built with library '{library_name}' v{desired_version}, but you have v{library_version}. Minor differences are usually compatible. If you experience issues, you can update the library or save this workflow to update it to your current library versions."
1149
+ LibraryVersionMinorDifferenceProblem(
1150
+ library_name=library_name,
1151
+ workflow_version=str(desired_version),
1152
+ current_version=str(library_version),
1153
+ )
1059
1154
  )
1060
1155
  status = WorkflowManager.WorkflowDependencyStatus.CAUTION
1061
1156
  else:
1062
1157
  problems.append(
1063
- f"This workflow requires library '{library_name}' v{desired_version}, but you have v{library_version}. Major version changes may include breaking changes. Consider updating the library to match, or save this workflow to update it to your current library versions."
1158
+ LibraryVersionMajorMismatchProblem(
1159
+ library_name=library_name,
1160
+ workflow_version=str(desired_version),
1161
+ current_version=str(library_version),
1162
+ )
1064
1163
  )
1065
1164
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1066
1165
  had_critical_error = True
@@ -1080,7 +1179,7 @@ class WorkflowManager:
1080
1179
  workflow_metadata
1081
1180
  )
1082
1181
  for issue in workflow_version_issues:
1083
- problems.append(issue.message)
1182
+ problems.append(issue.problem)
1084
1183
  if issue.severity == WorkflowManager.WorkflowStatus.UNUSABLE:
1085
1184
  had_critical_error = True
1086
1185
 
@@ -2383,82 +2482,11 @@ class WorkflowManager:
2383
2482
  workflow_name: str,
2384
2483
  import_recorder: ImportRecorder,
2385
2484
  ) -> list[ast.AST]:
2386
- import_recorder.add_from_import(
2387
- "griptape_nodes.retained_mode.events.library_events", "GetAllInfoForAllLibrariesRequest"
2388
- )
2389
- import_recorder.add_from_import(
2390
- "griptape_nodes.retained_mode.events.library_events", "GetAllInfoForAllLibrariesResultSuccess"
2391
- )
2392
- import_recorder.add_from_import(
2393
- "griptape_nodes.retained_mode.events.library_events", "ReloadAllLibrariesRequest"
2394
- )
2485
+ import_recorder.add_from_import("griptape_nodes.retained_mode.events.library_events", "LoadLibrariesRequest")
2395
2486
 
2396
2487
  code_blocks: list[ast.AST] = []
2397
2488
 
2398
- response_assign = ast.Assign(
2399
- targets=[ast.Name(id="response", ctx=ast.Store())],
2400
- value=ast.Call(
2401
- func=ast.Attribute(
2402
- value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
2403
- attr="handle_request",
2404
- ctx=ast.Load(),
2405
- ),
2406
- args=[
2407
- ast.Call(
2408
- func=ast.Name(id="GetAllInfoForAllLibrariesRequest", ctx=ast.Load()),
2409
- args=[],
2410
- keywords=[],
2411
- )
2412
- ],
2413
- keywords=[],
2414
- ),
2415
- )
2416
- ast.fix_missing_locations(response_assign)
2417
- code_blocks.append(response_assign)
2418
-
2419
- isinstance_test = ast.Call(
2420
- func=ast.Name(id="isinstance", ctx=ast.Load()),
2421
- args=[
2422
- ast.Name(id="response", ctx=ast.Load()),
2423
- ast.Name(id="GetAllInfoForAllLibrariesResultSuccess", ctx=ast.Load()),
2424
- ],
2425
- keywords=[],
2426
- )
2427
- ast.fix_missing_locations(isinstance_test)
2428
-
2429
- len_call = ast.Call(
2430
- func=ast.Name(id="len", ctx=ast.Load()),
2431
- args=[
2432
- ast.Call(
2433
- func=ast.Attribute(
2434
- value=ast.Attribute(
2435
- value=ast.Name(id="response", ctx=ast.Load()),
2436
- attr="library_name_to_library_info",
2437
- ctx=ast.Load(),
2438
- ),
2439
- attr="keys",
2440
- ctx=ast.Load(),
2441
- ),
2442
- args=[],
2443
- keywords=[],
2444
- )
2445
- ],
2446
- keywords=[],
2447
- )
2448
- compare_len = ast.Compare(
2449
- left=len_call,
2450
- ops=[ast.Lt()],
2451
- comparators=[ast.Constant(value=1)],
2452
- )
2453
- ast.fix_missing_locations(compare_len)
2454
-
2455
- test = ast.BoolOp(
2456
- op=ast.And(),
2457
- values=[isinstance_test, compare_len],
2458
- )
2459
- ast.fix_missing_locations(test)
2460
-
2461
- # 3) the body: GriptapeNodes.handle_request(ReloadAllLibrariesRequest())
2489
+ # Generate load libraries request call
2462
2490
  # TODO (https://github.com/griptape-ai/griptape-nodes/issues/1615): Generate requests to load ONLY the libraries used in this workflow
2463
2491
  load_call = ast.Expr(
2464
2492
  value=ast.Call(
@@ -2469,7 +2497,7 @@ class WorkflowManager:
2469
2497
  ),
2470
2498
  args=[
2471
2499
  ast.Call(
2472
- func=ast.Name(id="ReloadAllLibrariesRequest", ctx=ast.Load()),
2500
+ func=ast.Name(id="LoadLibrariesRequest", ctx=ast.Load()),
2473
2501
  args=[],
2474
2502
  keywords=[],
2475
2503
  )
@@ -2478,17 +2506,9 @@ class WorkflowManager:
2478
2506
  )
2479
2507
  )
2480
2508
  ast.fix_missing_locations(load_call)
2509
+ code_blocks.append(load_call)
2481
2510
 
2482
- # 4) assemble the `if` statement
2483
- if_node = ast.If(
2484
- test=test,
2485
- body=[load_call],
2486
- orelse=[],
2487
- )
2488
- ast.fix_missing_locations(if_node)
2489
- code_blocks.append(if_node)
2490
-
2491
- # 5) context_manager = GriptapeNodes.ContextManager()
2511
+ # Generate context manager assignment
2492
2512
  assign_context_manager = ast.Assign(
2493
2513
  targets=[ast.Name(id="context_manager", ctx=ast.Store())],
2494
2514
  value=ast.Call(
@@ -4097,40 +4117,98 @@ class WorkflowManager:
4097
4117
  Returns:
4098
4118
  WorkflowRegistrationResult with succeeded and failed workflow names
4099
4119
  """
4120
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
4121
+
4100
4122
  succeeded = []
4101
4123
  failed = []
4102
4124
 
4103
- def process_workflow_file(workflow_file: Path) -> None:
4104
- """Process a single workflow file for registration."""
4105
- # Check if the file has workflow metadata before processing
4106
- metadata_blocks = self.get_workflow_metadata(
4107
- workflow_file, block_name=WorkflowManager.WORKFLOW_METADATA_HEADER
4108
- )
4109
- if len(metadata_blocks) == 1:
4110
- workflow_name = self._process_single_workflow_file(workflow_file)
4111
- if workflow_name:
4112
- succeeded.append(workflow_name)
4113
- else:
4114
- failed.append(str(workflow_file))
4125
+ # First pass: collect all workflow files to determine total count
4126
+ all_workflow_files: list[Path] = []
4115
4127
 
4116
- def process_path(path: Path) -> None:
4117
- """Process a path, handling both files and directories."""
4128
+ def collect_workflow_files(path: Path) -> None:
4129
+ """Collect workflow files from a path."""
4118
4130
  if not path.exists():
4119
- failed.append(str(path))
4120
4131
  return
4121
4132
  if path.is_dir():
4122
- # Process all Python files recursively in the directory
4123
- # Exclude .venv directories to avoid encoding issues with test files
4124
4133
  for workflow_file in path.rglob("*.py"):
4125
- # Skip files in .venv directories
4126
4134
  if ".venv" in workflow_file.parts:
4127
4135
  continue
4128
- process_workflow_file(workflow_file)
4136
+ # Check if file has workflow metadata
4137
+ try:
4138
+ metadata_blocks = self.get_workflow_metadata(
4139
+ workflow_file, block_name=WorkflowManager.WORKFLOW_METADATA_HEADER
4140
+ )
4141
+ if len(metadata_blocks) == 1:
4142
+ all_workflow_files.append(workflow_file)
4143
+ except Exception as e:
4144
+ # Skip files that can't be read or parsed
4145
+ logger.debug("Skipping workflow file %s due to error: %s", workflow_file, e)
4146
+ continue
4129
4147
  elif path.suffix == ".py":
4130
- process_workflow_file(path)
4148
+ try:
4149
+ metadata_blocks = self.get_workflow_metadata(
4150
+ path, block_name=WorkflowManager.WORKFLOW_METADATA_HEADER
4151
+ )
4152
+ if len(metadata_blocks) == 1:
4153
+ all_workflow_files.append(path)
4154
+ except Exception as e:
4155
+ logger.debug("Skipping workflow file %s due to error: %s", path, e)
4131
4156
 
4157
+ # Collect all workflow files first
4132
4158
  for workflow_to_register in workflows_to_register:
4133
- process_path(Path(workflow_to_register))
4159
+ collect_workflow_files(Path(workflow_to_register))
4160
+
4161
+ # Track progress
4162
+ total_workflows = len(all_workflow_files)
4163
+
4164
+ # Second pass: process each workflow file with progress events
4165
+ for current_index, workflow_file in enumerate(all_workflow_files, start=1):
4166
+ workflow_name = str(workflow_file.name)
4167
+
4168
+ # Emit loading event
4169
+ GriptapeNodes.EventManager().put_event(
4170
+ AppEvent(
4171
+ payload=EngineInitializationProgress(
4172
+ phase=InitializationPhase.WORKFLOWS,
4173
+ item_name=workflow_name,
4174
+ status=InitializationStatus.LOADING,
4175
+ current=current_index,
4176
+ total=total_workflows,
4177
+ )
4178
+ )
4179
+ )
4180
+
4181
+ # Process the workflow
4182
+ result_name = self._process_single_workflow_file(workflow_file)
4183
+ if result_name:
4184
+ succeeded.append(result_name)
4185
+ # Emit success event
4186
+ GriptapeNodes.EventManager().put_event(
4187
+ AppEvent(
4188
+ payload=EngineInitializationProgress(
4189
+ phase=InitializationPhase.WORKFLOWS,
4190
+ item_name=workflow_name,
4191
+ status=InitializationStatus.COMPLETE,
4192
+ current=current_index,
4193
+ total=total_workflows,
4194
+ )
4195
+ )
4196
+ )
4197
+ else:
4198
+ failed.append(str(workflow_file))
4199
+ # Emit failure event
4200
+ GriptapeNodes.EventManager().put_event(
4201
+ AppEvent(
4202
+ payload=EngineInitializationProgress(
4203
+ phase=InitializationPhase.WORKFLOWS,
4204
+ item_name=workflow_name,
4205
+ status=InitializationStatus.FAILED,
4206
+ current=current_index,
4207
+ total=total_workflows,
4208
+ error="Failed to process workflow file",
4209
+ )
4210
+ )
4211
+ )
4134
4212
 
4135
4213
  return WorkflowRegistrationResult(succeeded=succeeded, failed=failed)
4136
4214