griptape-nodes 0.52.1__py3-none-any.whl → 0.54.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.
- griptape_nodes/__init__.py +8 -942
- griptape_nodes/__main__.py +6 -0
- griptape_nodes/app/app.py +48 -86
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
- griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes/cli/commands/config.py +74 -0
- griptape_nodes/cli/commands/engine.py +80 -0
- griptape_nodes/cli/commands/init.py +550 -0
- griptape_nodes/cli/commands/libraries.py +96 -0
- griptape_nodes/cli/commands/models.py +504 -0
- griptape_nodes/cli/commands/self.py +120 -0
- griptape_nodes/cli/main.py +56 -0
- griptape_nodes/cli/shared.py +75 -0
- griptape_nodes/common/__init__.py +1 -0
- griptape_nodes/common/directed_graph.py +71 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
- griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
- griptape_nodes/exe_types/core_types.py +60 -2
- griptape_nodes/exe_types/node_types.py +257 -38
- griptape_nodes/exe_types/param_components/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
- griptape_nodes/machines/control_flow.py +195 -94
- griptape_nodes/machines/dag_builder.py +207 -0
- griptape_nodes/machines/fsm.py +10 -1
- griptape_nodes/machines/parallel_resolution.py +558 -0
- griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
- griptape_nodes/node_library/library_registry.py +34 -1
- griptape_nodes/retained_mode/events/app_events.py +5 -1
- griptape_nodes/retained_mode/events/base_events.py +9 -9
- griptape_nodes/retained_mode/events/config_events.py +30 -0
- griptape_nodes/retained_mode/events/execution_events.py +2 -2
- griptape_nodes/retained_mode/events/model_events.py +296 -0
- griptape_nodes/retained_mode/events/node_events.py +4 -3
- griptape_nodes/retained_mode/griptape_nodes.py +34 -12
- griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
- griptape_nodes/retained_mode/managers/config_manager.py +44 -3
- griptape_nodes/retained_mode/managers/context_manager.py +6 -5
- griptape_nodes/retained_mode/managers/event_manager.py +8 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +35 -25
- griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
- griptape_nodes/retained_mode/managers/node_manager.py +102 -220
- griptape_nodes/retained_mode/managers/object_manager.py +11 -5
- griptape_nodes/retained_mode/managers/os_manager.py +28 -13
- griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
- griptape_nodes/retained_mode/managers/settings.py +116 -7
- griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
- griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
- griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
- griptape_nodes/retained_mode/retained_mode.py +19 -0
- griptape_nodes/servers/__init__.py +1 -0
- griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
- griptape_nodes/{app/api.py → servers/static.py} +43 -40
- griptape_nodes/traits/add_param_button.py +1 -1
- griptape_nodes/traits/button.py +334 -6
- griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes/traits/multi_options.py +188 -0
- griptape_nodes/traits/numbers_selector.py +77 -0
- griptape_nodes/traits/options.py +93 -2
- griptape_nodes/traits/traits.json +4 -0
- griptape_nodes/utils/async_utils.py +31 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
- /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
|
@@ -29,6 +29,9 @@ from griptape_nodes.retained_mode.events.app_events import (
|
|
|
29
29
|
GetEngineVersionRequest,
|
|
30
30
|
GetEngineVersionResultSuccess,
|
|
31
31
|
)
|
|
32
|
+
|
|
33
|
+
# Runtime imports for ResultDetails since it's used at runtime
|
|
34
|
+
from griptape_nodes.retained_mode.events.base_events import ResultDetail, ResultDetails
|
|
32
35
|
from griptape_nodes.retained_mode.events.flow_events import (
|
|
33
36
|
CreateFlowRequest,
|
|
34
37
|
GetTopLevelFlowRequest,
|
|
@@ -314,17 +317,22 @@ class WorkflowManager:
|
|
|
314
317
|
|
|
315
318
|
def on_libraries_initialization_complete(self) -> None:
|
|
316
319
|
# All of the libraries have loaded, and any workflows they came with have been registered.
|
|
317
|
-
#
|
|
320
|
+
# Discover workflows from both config and workspace.
|
|
318
321
|
default_workflow_section = "app_events.on_app_initialization_complete.workflows_to_register"
|
|
322
|
+
config_mgr = GriptapeNodes.ConfigManager()
|
|
319
323
|
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
workflows_to_register = []
|
|
325
|
+
|
|
326
|
+
# Add from config
|
|
327
|
+
config_workflows = config_mgr.get_config_value(default_workflow_section, default=[])
|
|
328
|
+
workflows_to_register.extend(config_workflows)
|
|
322
329
|
|
|
323
|
-
|
|
324
|
-
|
|
330
|
+
# Add from workspace (avoiding duplicates)
|
|
331
|
+
workspace_path = config_mgr.workspace_path
|
|
332
|
+
workflows_to_register.extend([workspace_path])
|
|
325
333
|
|
|
326
|
-
if
|
|
327
|
-
|
|
334
|
+
# Register all discovered workflows at once if any were found
|
|
335
|
+
self._process_workflows_for_registration(workflows_to_register)
|
|
328
336
|
|
|
329
337
|
# Print it all out nicely.
|
|
330
338
|
self.print_workflow_load_status()
|
|
@@ -337,7 +345,6 @@ class WorkflowManager:
|
|
|
337
345
|
paths_to_remove.add(workflow_path.lower())
|
|
338
346
|
|
|
339
347
|
if paths_to_remove:
|
|
340
|
-
config_mgr = GriptapeNodes.ConfigManager()
|
|
341
348
|
workflows_to_register = config_mgr.get_config_value(default_workflow_section)
|
|
342
349
|
if workflows_to_register:
|
|
343
350
|
workflows_to_register = [
|
|
@@ -535,7 +542,6 @@ class WorkflowManager:
|
|
|
535
542
|
complete_file_path = WorkflowRegistry.get_complete_file_path(relative_file_path=relative_file_path)
|
|
536
543
|
if not Path(complete_file_path).is_file():
|
|
537
544
|
details = f"Failed to find file. Path '{complete_file_path}' doesn't exist."
|
|
538
|
-
logger.error(details)
|
|
539
545
|
return RunWorkflowFromScratchResultFailure(result_details=details)
|
|
540
546
|
|
|
541
547
|
# Start with a clean slate.
|
|
@@ -543,14 +549,12 @@ class WorkflowManager:
|
|
|
543
549
|
clear_all_result = GriptapeNodes.handle_request(clear_all_request)
|
|
544
550
|
if not clear_all_result.succeeded():
|
|
545
551
|
details = f"Failed to clear the existing object state when trying to run '{complete_file_path}'."
|
|
546
|
-
logger.error(details)
|
|
547
552
|
return RunWorkflowFromScratchResultFailure(result_details=details)
|
|
548
553
|
|
|
549
554
|
# Run the file, goddamn it
|
|
550
555
|
execution_result = self.run_workflow(relative_file_path=relative_file_path)
|
|
551
556
|
if execution_result.execution_successful:
|
|
552
|
-
|
|
553
|
-
return RunWorkflowFromScratchResultSuccess()
|
|
557
|
+
return RunWorkflowFromScratchResultSuccess(result_details=execution_result.execution_details)
|
|
554
558
|
|
|
555
559
|
logger.error(execution_result.execution_details)
|
|
556
560
|
return RunWorkflowFromScratchResultFailure(result_details=execution_result.execution_details)
|
|
@@ -560,13 +564,11 @@ class WorkflowManager:
|
|
|
560
564
|
complete_file_path = WorkflowRegistry.get_complete_file_path(relative_file_path=relative_file_path)
|
|
561
565
|
if not Path(complete_file_path).is_file():
|
|
562
566
|
details = f"Failed to find file. Path '{complete_file_path}' doesn't exist."
|
|
563
|
-
logger.error(details)
|
|
564
567
|
return RunWorkflowWithCurrentStateResultFailure(result_details=details)
|
|
565
568
|
execution_result = self.run_workflow(relative_file_path=relative_file_path)
|
|
566
569
|
|
|
567
570
|
if execution_result.execution_successful:
|
|
568
|
-
|
|
569
|
-
return RunWorkflowWithCurrentStateResultSuccess()
|
|
571
|
+
return RunWorkflowWithCurrentStateResultSuccess(result_details=execution_result.execution_details)
|
|
570
572
|
logger.error(execution_result.execution_details)
|
|
571
573
|
return RunWorkflowWithCurrentStateResultFailure(result_details=execution_result.execution_details)
|
|
572
574
|
|
|
@@ -576,13 +578,12 @@ class WorkflowManager:
|
|
|
576
578
|
workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
577
579
|
except KeyError:
|
|
578
580
|
details = f"Failed to get workflow '{request.workflow_name}' from registry."
|
|
579
|
-
logger.error(details)
|
|
580
581
|
return RunWorkflowFromRegistryResultFailure(result_details=details)
|
|
581
582
|
|
|
582
583
|
# Update current context for workflow.
|
|
584
|
+
context_warning = None
|
|
583
585
|
if GriptapeNodes.ContextManager().has_current_workflow():
|
|
584
|
-
|
|
585
|
-
logger.warning(details)
|
|
586
|
+
context_warning = f"Started a new workflow '{request.workflow_name}' but a workflow '{GriptapeNodes.ContextManager().get_current_workflow_name()}' was already in the Current Context. Replacing the old with the new."
|
|
586
587
|
|
|
587
588
|
# get file_path from workflow
|
|
588
589
|
relative_file_path = workflow.file_path
|
|
@@ -595,7 +596,6 @@ class WorkflowManager:
|
|
|
595
596
|
clear_all_result = GriptapeNodes.handle_request(clear_all_request)
|
|
596
597
|
if not clear_all_result.succeeded():
|
|
597
598
|
details = f"Failed to clear the existing object state when preparing to run workflow '{request.workflow_name}'."
|
|
598
|
-
logger.error(details)
|
|
599
599
|
return RunWorkflowFromRegistryResultFailure(result_details=details)
|
|
600
600
|
|
|
601
601
|
# Let's run under the assumption that this Workflow will become our Current Context; if we fail, it will revert.
|
|
@@ -604,18 +604,24 @@ class WorkflowManager:
|
|
|
604
604
|
execution_result = self.run_workflow(relative_file_path=relative_file_path)
|
|
605
605
|
|
|
606
606
|
if not execution_result.execution_successful:
|
|
607
|
-
|
|
607
|
+
result_messages = []
|
|
608
|
+
if context_warning:
|
|
609
|
+
result_messages.append(ResultDetail(message=context_warning, level=logging.WARNING))
|
|
610
|
+
result_messages.append(ResultDetail(message=execution_result.execution_details, level=logging.ERROR))
|
|
608
611
|
|
|
609
612
|
# Attempt to clear everything out, as we modified the engine state getting here.
|
|
610
613
|
clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
|
|
611
614
|
clear_all_result = GriptapeNodes.handle_request(clear_all_request)
|
|
612
615
|
|
|
613
616
|
# The clear-all above here wipes the ContextManager, so no need to do a pop_workflow().
|
|
614
|
-
return RunWorkflowFromRegistryResultFailure(result_details=
|
|
617
|
+
return RunWorkflowFromRegistryResultFailure(result_details=ResultDetails(*result_messages))
|
|
615
618
|
|
|
616
619
|
# Success!
|
|
617
|
-
|
|
618
|
-
|
|
620
|
+
result_messages = []
|
|
621
|
+
if context_warning:
|
|
622
|
+
result_messages.append(ResultDetail(message=context_warning, level=logging.WARNING))
|
|
623
|
+
result_messages.append(ResultDetail(message=execution_result.execution_details, level=logging.DEBUG))
|
|
624
|
+
return RunWorkflowFromRegistryResultSuccess(result_details=ResultDetails(*result_messages))
|
|
619
625
|
|
|
620
626
|
def on_register_workflow_request(self, request: RegisterWorkflowRequest) -> ResultPayload:
|
|
621
627
|
try:
|
|
@@ -625,9 +631,14 @@ class WorkflowManager:
|
|
|
625
631
|
workflow = WorkflowRegistry.generate_new_workflow(metadata=request.metadata, file_path=request.file_name)
|
|
626
632
|
except Exception as e:
|
|
627
633
|
details = f"Failed to register workflow with name '{request.metadata.name}'. Error: {e}"
|
|
628
|
-
logger.error(details)
|
|
629
634
|
return RegisterWorkflowResultFailure(result_details=details)
|
|
630
|
-
return RegisterWorkflowResultSuccess(
|
|
635
|
+
return RegisterWorkflowResultSuccess(
|
|
636
|
+
workflow_name=workflow.metadata.name,
|
|
637
|
+
result_details=ResultDetails(
|
|
638
|
+
message=f"Successfully registered workflow: {workflow.metadata.name}",
|
|
639
|
+
level=logging.DEBUG,
|
|
640
|
+
),
|
|
641
|
+
)
|
|
631
642
|
|
|
632
643
|
def on_import_workflow_request(self, request: ImportWorkflowRequest) -> ResultPayload:
|
|
633
644
|
# First, attempt to load metadata from the file
|
|
@@ -641,7 +652,10 @@ class WorkflowManager:
|
|
|
641
652
|
workflow_name = load_metadata_result.metadata.name
|
|
642
653
|
if WorkflowRegistry.has_workflow_with_name(workflow_name):
|
|
643
654
|
# Workflow already exists - no need to re-register
|
|
644
|
-
return ImportWorkflowResultSuccess(
|
|
655
|
+
return ImportWorkflowResultSuccess(
|
|
656
|
+
workflow_name=workflow_name,
|
|
657
|
+
result_details=f"Workflow '{workflow_name}' already exists - no need to re-import.",
|
|
658
|
+
)
|
|
645
659
|
|
|
646
660
|
# Now register the workflow with the extracted metadata
|
|
647
661
|
register_request = RegisterWorkflowRequest(metadata=load_metadata_result.metadata, file_name=request.file_path)
|
|
@@ -659,30 +673,34 @@ class WorkflowManager:
|
|
|
659
673
|
|
|
660
674
|
return ImportWorkflowResultFailure(result_details=details)
|
|
661
675
|
|
|
662
|
-
return ImportWorkflowResultSuccess(
|
|
676
|
+
return ImportWorkflowResultSuccess(
|
|
677
|
+
workflow_name=register_result.workflow_name,
|
|
678
|
+
result_details=ResultDetails(
|
|
679
|
+
message=f"Successfully imported workflow: {register_result.workflow_name}", level=logging.INFO
|
|
680
|
+
),
|
|
681
|
+
)
|
|
663
682
|
|
|
664
683
|
def on_list_all_workflows_request(self, _request: ListAllWorkflowsRequest) -> ResultPayload:
|
|
665
684
|
try:
|
|
666
685
|
workflows = WorkflowRegistry.list_workflows()
|
|
667
686
|
except Exception:
|
|
668
687
|
details = "Failed to list all workflows."
|
|
669
|
-
logger.error(details)
|
|
670
688
|
return ListAllWorkflowsResultFailure(result_details=details)
|
|
671
|
-
return ListAllWorkflowsResultSuccess(
|
|
689
|
+
return ListAllWorkflowsResultSuccess(
|
|
690
|
+
workflows=workflows, result_details=f"Successfully retrieved {len(workflows)} workflows."
|
|
691
|
+
)
|
|
672
692
|
|
|
673
693
|
def on_delete_workflows_request(self, request: DeleteWorkflowRequest) -> ResultPayload:
|
|
674
694
|
try:
|
|
675
695
|
workflow = WorkflowRegistry.delete_workflow_by_name(request.name)
|
|
676
696
|
except Exception as e:
|
|
677
697
|
details = f"Failed to remove workflow from registry with name '{request.name}'. Exception: {e}"
|
|
678
|
-
logger.error(details)
|
|
679
698
|
return DeleteWorkflowResultFailure(result_details=details)
|
|
680
699
|
config_manager = GriptapeNodes.ConfigManager()
|
|
681
700
|
try:
|
|
682
701
|
config_manager.delete_user_workflow(workflow.file_path)
|
|
683
702
|
except Exception as e:
|
|
684
703
|
details = f"Failed to remove workflow from user config with name '{request.name}'. Exception: {e}"
|
|
685
|
-
logger.error(details)
|
|
686
704
|
return DeleteWorkflowResultFailure(result_details=details)
|
|
687
705
|
# delete the actual file
|
|
688
706
|
full_path = config_manager.workspace_path.joinpath(workflow.file_path)
|
|
@@ -690,25 +708,28 @@ class WorkflowManager:
|
|
|
690
708
|
full_path.unlink()
|
|
691
709
|
except Exception as e:
|
|
692
710
|
details = f"Failed to delete workflow file with path '{workflow.file_path}'. Exception: {e}"
|
|
693
|
-
logger.error(details)
|
|
694
711
|
return DeleteWorkflowResultFailure(result_details=details)
|
|
695
|
-
return DeleteWorkflowResultSuccess(
|
|
712
|
+
return DeleteWorkflowResultSuccess(
|
|
713
|
+
result_details=ResultDetails(message=f"Successfully deleted workflow: {request.name}", level=logging.INFO)
|
|
714
|
+
)
|
|
696
715
|
|
|
697
716
|
def on_rename_workflow_request(self, request: RenameWorkflowRequest) -> ResultPayload:
|
|
698
717
|
save_workflow_request = GriptapeNodes.handle_request(SaveWorkflowRequest(file_name=request.requested_name))
|
|
699
718
|
|
|
700
719
|
if isinstance(save_workflow_request, SaveWorkflowResultFailure):
|
|
701
720
|
details = f"Attempted to rename workflow '{request.workflow_name}' to '{request.requested_name}'. Failed while attempting to save."
|
|
702
|
-
logger.error(details)
|
|
703
721
|
return RenameWorkflowResultFailure(result_details=details)
|
|
704
722
|
|
|
705
723
|
delete_workflow_result = GriptapeNodes.handle_request(DeleteWorkflowRequest(name=request.workflow_name))
|
|
706
724
|
if isinstance(delete_workflow_result, DeleteWorkflowResultFailure):
|
|
707
725
|
details = f"Attempted to rename workflow '{request.workflow_name}' to '{request.requested_name}'. Failed while attempting to remove the original file name from the registry."
|
|
708
|
-
logger.error(details)
|
|
709
726
|
return RenameWorkflowResultFailure(result_details=details)
|
|
710
727
|
|
|
711
|
-
return RenameWorkflowResultSuccess(
|
|
728
|
+
return RenameWorkflowResultSuccess(
|
|
729
|
+
result_details=ResultDetails(
|
|
730
|
+
message=f"Successfully renamed workflow to: {request.requested_name}", level=logging.INFO
|
|
731
|
+
)
|
|
732
|
+
)
|
|
712
733
|
|
|
713
734
|
def on_move_workflow_request(self, request: MoveWorkflowRequest) -> ResultPayload: # noqa: PLR0911
|
|
714
735
|
try:
|
|
@@ -716,7 +737,6 @@ class WorkflowManager:
|
|
|
716
737
|
workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
717
738
|
except KeyError:
|
|
718
739
|
details = f"Failed to move workflow '{request.workflow_name}' because it does not exist."
|
|
719
|
-
logger.error(details)
|
|
720
740
|
return MoveWorkflowResultFailure(result_details=details)
|
|
721
741
|
|
|
722
742
|
config_manager = GriptapeNodes.ConfigManager()
|
|
@@ -727,7 +747,6 @@ class WorkflowManager:
|
|
|
727
747
|
details = (
|
|
728
748
|
f"Failed to move workflow '{request.workflow_name}': File path '{current_file_path}' does not exist."
|
|
729
749
|
)
|
|
730
|
-
logger.error(details)
|
|
731
750
|
return MoveWorkflowResultFailure(result_details=details)
|
|
732
751
|
|
|
733
752
|
# Clean and validate target directory
|
|
@@ -742,7 +761,6 @@ class WorkflowManager:
|
|
|
742
761
|
target_dir_path.mkdir(parents=True, exist_ok=True)
|
|
743
762
|
except OSError as e:
|
|
744
763
|
details = f"Failed to create target directory '{target_dir_path}': {e!s}"
|
|
745
|
-
logger.error(details)
|
|
746
764
|
return MoveWorkflowResultFailure(result_details=details)
|
|
747
765
|
|
|
748
766
|
# Create new file path
|
|
@@ -755,7 +773,6 @@ class WorkflowManager:
|
|
|
755
773
|
details = (
|
|
756
774
|
f"Failed to move workflow '{request.workflow_name}': Target file '{new_absolute_path}' already exists."
|
|
757
775
|
)
|
|
758
|
-
logger.error(details)
|
|
759
776
|
return MoveWorkflowResultFailure(result_details=details)
|
|
760
777
|
|
|
761
778
|
try:
|
|
@@ -770,28 +787,29 @@ class WorkflowManager:
|
|
|
770
787
|
config_manager.save_user_workflow_json(str(new_absolute_path))
|
|
771
788
|
|
|
772
789
|
except OSError as e:
|
|
773
|
-
|
|
774
|
-
|
|
790
|
+
error_messages = []
|
|
791
|
+
main_error = f"Failed to move workflow file '{current_file_path}' to '{new_absolute_path}': {e!s}"
|
|
792
|
+
error_messages.append(ResultDetail(message=main_error, level=logging.ERROR))
|
|
775
793
|
|
|
776
794
|
# Attempt to rollback if file was moved but registry update failed
|
|
777
795
|
if new_absolute_path.exists() and not Path(current_file_path).exists():
|
|
778
796
|
try:
|
|
779
797
|
new_absolute_path.rename(current_file_path)
|
|
780
|
-
|
|
781
|
-
|
|
798
|
+
rollback_message = f"Rolled back file move for workflow '{request.workflow_name}'"
|
|
799
|
+
error_messages.append(ResultDetail(message=rollback_message, level=logging.INFO))
|
|
782
800
|
except OSError:
|
|
783
|
-
|
|
784
|
-
|
|
801
|
+
rollback_failure = f"Failed to rollback file move for workflow '{request.workflow_name}'"
|
|
802
|
+
error_messages.append(ResultDetail(message=rollback_failure, level=logging.ERROR))
|
|
785
803
|
|
|
786
|
-
return MoveWorkflowResultFailure(result_details=
|
|
804
|
+
return MoveWorkflowResultFailure(result_details=ResultDetails(*error_messages))
|
|
787
805
|
except Exception as e:
|
|
788
806
|
details = f"Failed to move workflow '{request.workflow_name}': {e!s}"
|
|
789
|
-
logger.error(details)
|
|
790
807
|
return MoveWorkflowResultFailure(result_details=details)
|
|
791
808
|
else:
|
|
792
809
|
details = f"Successfully moved workflow '{request.workflow_name}' to '{new_relative_path}'"
|
|
793
|
-
|
|
794
|
-
|
|
810
|
+
return MoveWorkflowResultSuccess(
|
|
811
|
+
moved_file_path=new_relative_path, result_details=ResultDetails(message=details, level=logging.INFO)
|
|
812
|
+
)
|
|
795
813
|
|
|
796
814
|
def on_load_workflow_metadata_request( # noqa: C901, PLR0912, PLR0915
|
|
797
815
|
self, request: LoadWorkflowMetadata
|
|
@@ -810,7 +828,6 @@ class WorkflowManager:
|
|
|
810
828
|
],
|
|
811
829
|
)
|
|
812
830
|
details = f"Attempted to load workflow metadata for a file at '{complete_file_path}. Failed because no file could be found at that path."
|
|
813
|
-
logger.error(details)
|
|
814
831
|
return LoadWorkflowMetadataResultFailure(result_details=details)
|
|
815
832
|
|
|
816
833
|
# Find the metadata block.
|
|
@@ -827,7 +844,6 @@ class WorkflowManager:
|
|
|
827
844
|
],
|
|
828
845
|
)
|
|
829
846
|
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."
|
|
830
|
-
logger.error(details)
|
|
831
847
|
return LoadWorkflowMetadataResultFailure(result_details=details)
|
|
832
848
|
|
|
833
849
|
# Now attempt to parse out the metadata section, stripped of comment prefixes.
|
|
@@ -847,7 +863,6 @@ class WorkflowManager:
|
|
|
847
863
|
problems=[f"Failed because the metadata was not valid TOML: {err}"],
|
|
848
864
|
)
|
|
849
865
|
details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the metadata was not valid TOML: {err}"
|
|
850
|
-
logger.error(details)
|
|
851
866
|
return LoadWorkflowMetadataResultFailure(result_details=details)
|
|
852
867
|
|
|
853
868
|
tool_header = "tool"
|
|
@@ -863,7 +878,6 @@ class WorkflowManager:
|
|
|
863
878
|
problems=[f"Failed because the '[{tool_header}.{griptape_nodes_header}]' section could not be found."],
|
|
864
879
|
)
|
|
865
880
|
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}"
|
|
866
|
-
logger.error(details)
|
|
867
881
|
return LoadWorkflowMetadataResultFailure(result_details=details)
|
|
868
882
|
|
|
869
883
|
try:
|
|
@@ -881,7 +895,6 @@ class WorkflowManager:
|
|
|
881
895
|
],
|
|
882
896
|
)
|
|
883
897
|
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}"
|
|
884
|
-
logger.error(details)
|
|
885
898
|
return LoadWorkflowMetadataResultFailure(result_details=details)
|
|
886
899
|
|
|
887
900
|
# We have valid dependencies, etc.
|
|
@@ -1037,7 +1050,9 @@ class WorkflowManager:
|
|
|
1037
1050
|
workflow_dependencies=dependency_infos,
|
|
1038
1051
|
problems=problems,
|
|
1039
1052
|
)
|
|
1040
|
-
return LoadWorkflowMetadataResultSuccess(
|
|
1053
|
+
return LoadWorkflowMetadataResultSuccess(
|
|
1054
|
+
metadata=workflow_metadata, result_details="Workflow metadata loaded successfully."
|
|
1055
|
+
)
|
|
1041
1056
|
|
|
1042
1057
|
def register_workflows_from_config(self, config_section: str) -> None:
|
|
1043
1058
|
workflows_to_register = GriptapeNodes.ConfigManager().get_config_value(config_section)
|
|
@@ -1045,21 +1060,7 @@ class WorkflowManager:
|
|
|
1045
1060
|
self.register_list_of_workflows(workflows_to_register)
|
|
1046
1061
|
|
|
1047
1062
|
def register_list_of_workflows(self, workflows_to_register: list[str]) -> None:
|
|
1048
|
-
|
|
1049
|
-
path = Path(workflow_to_register)
|
|
1050
|
-
|
|
1051
|
-
if path.is_dir():
|
|
1052
|
-
# If it's a directory, register all the workflows in it.
|
|
1053
|
-
for workflow_file in path.glob("*.py"):
|
|
1054
|
-
# Check that the python file has script metadata
|
|
1055
|
-
metadata_blocks = self.get_workflow_metadata(
|
|
1056
|
-
workflow_file, block_name=WorkflowManager.WORKFLOW_METADATA_HEADER
|
|
1057
|
-
)
|
|
1058
|
-
if len(metadata_blocks) == 1:
|
|
1059
|
-
self._register_workflow(str(workflow_file))
|
|
1060
|
-
else:
|
|
1061
|
-
# If it's a file, register it directly.
|
|
1062
|
-
self._register_workflow(str(path))
|
|
1063
|
+
self._process_workflows_for_registration(workflows_to_register)
|
|
1063
1064
|
|
|
1064
1065
|
def _register_workflow(self, workflow_to_register: str) -> bool:
|
|
1065
1066
|
"""Registers a workflow from a file.
|
|
@@ -1159,7 +1160,6 @@ class WorkflowManager:
|
|
|
1159
1160
|
engine_version_result = GriptapeNodes.handle_request(request=engine_version_request)
|
|
1160
1161
|
if not isinstance(engine_version_result, GetEngineVersionResultSuccess):
|
|
1161
1162
|
details = f"Failed getting the engine version for workflow '{file_name}'."
|
|
1162
|
-
logger.error(details)
|
|
1163
1163
|
raise TypeError(details)
|
|
1164
1164
|
try:
|
|
1165
1165
|
engine_version_success = cast("GetEngineVersionResultSuccess", engine_version_result)
|
|
@@ -1168,7 +1168,6 @@ class WorkflowManager:
|
|
|
1168
1168
|
)
|
|
1169
1169
|
except Exception as err:
|
|
1170
1170
|
details = f"Failed getting the engine version for workflow '{file_name}': {err}"
|
|
1171
|
-
logger.error(details)
|
|
1172
1171
|
raise ValueError(details) from err
|
|
1173
1172
|
|
|
1174
1173
|
# Keep track of all of the nodes we create and the generated variable names for them.
|
|
@@ -1182,7 +1181,6 @@ class WorkflowManager:
|
|
|
1182
1181
|
top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
|
|
1183
1182
|
if not isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess):
|
|
1184
1183
|
details = f"Failed when requesting to get top level flow for workflow '{file_name}'."
|
|
1185
|
-
logger.error(details)
|
|
1186
1184
|
raise TypeError(details)
|
|
1187
1185
|
top_level_flow_name = top_level_flow_result.flow_name
|
|
1188
1186
|
serialized_flow_request = SerializeFlowToCommandsRequest(
|
|
@@ -1191,7 +1189,6 @@ class WorkflowManager:
|
|
|
1191
1189
|
serialized_flow_result = GriptapeNodes.handle_request(serialized_flow_request)
|
|
1192
1190
|
if not isinstance(serialized_flow_result, SerializeFlowToCommandsResultSuccess):
|
|
1193
1191
|
details = f"Failed when serializing flow for workflow '{file_name}'."
|
|
1194
|
-
logger.error(details)
|
|
1195
1192
|
raise TypeError(details)
|
|
1196
1193
|
serialized_flow_commands = serialized_flow_result.serialized_flow_commands
|
|
1197
1194
|
|
|
@@ -1214,7 +1211,6 @@ class WorkflowManager:
|
|
|
1214
1211
|
)
|
|
1215
1212
|
if workflow_metadata is None:
|
|
1216
1213
|
details = f"Failed to generate metadata for workflow '{file_name}'."
|
|
1217
|
-
logger.error(details)
|
|
1218
1214
|
raise ValueError(details)
|
|
1219
1215
|
|
|
1220
1216
|
# Set the image if provided
|
|
@@ -1224,7 +1220,6 @@ class WorkflowManager:
|
|
|
1224
1220
|
metadata_block = self._generate_workflow_metadata_header(workflow_metadata=workflow_metadata)
|
|
1225
1221
|
if metadata_block is None:
|
|
1226
1222
|
details = f"Failed to generate metadata block for workflow '{file_name}'."
|
|
1227
|
-
logger.error(details)
|
|
1228
1223
|
raise ValueError(details)
|
|
1229
1224
|
|
|
1230
1225
|
import_recorder = ImportRecorder()
|
|
@@ -1423,7 +1418,6 @@ class WorkflowManager:
|
|
|
1423
1418
|
)
|
|
1424
1419
|
except Exception as err:
|
|
1425
1420
|
details = f"Attempted to save workflow '{relative_file_path}', but {err}"
|
|
1426
|
-
logger.error(details)
|
|
1427
1421
|
return SaveWorkflowResultFailure(result_details=details)
|
|
1428
1422
|
|
|
1429
1423
|
# Create the pathing and write the file
|
|
@@ -1431,7 +1425,6 @@ class WorkflowManager:
|
|
|
1431
1425
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1432
1426
|
except OSError as e:
|
|
1433
1427
|
details = f"Attempted to save workflow '{file_name}'. Failed when creating directory: {e}"
|
|
1434
|
-
logger.error(details)
|
|
1435
1428
|
return SaveWorkflowResultFailure(result_details=details)
|
|
1436
1429
|
|
|
1437
1430
|
# Check disk space before writing
|
|
@@ -1440,7 +1433,6 @@ class WorkflowManager:
|
|
|
1440
1433
|
if not OSManager.check_available_disk_space(file_path.parent, min_space_gb):
|
|
1441
1434
|
error_msg = OSManager.format_disk_space_error(file_path.parent)
|
|
1442
1435
|
details = f"Attempted to save workflow '{file_name}' (requires {min_space_gb:.1f} GB). Failed: {error_msg}"
|
|
1443
|
-
logger.error(details)
|
|
1444
1436
|
return SaveWorkflowResultFailure(result_details=details)
|
|
1445
1437
|
|
|
1446
1438
|
try:
|
|
@@ -1448,7 +1440,6 @@ class WorkflowManager:
|
|
|
1448
1440
|
file.write(final_code_output)
|
|
1449
1441
|
except OSError as e:
|
|
1450
1442
|
details = f"Attempted to save workflow '{file_name}'. Failed when writing file: {e}"
|
|
1451
|
-
logger.error(details)
|
|
1452
1443
|
return SaveWorkflowResultFailure(result_details=details)
|
|
1453
1444
|
|
|
1454
1445
|
# save the created workflow as an entry in the JSON config file.
|
|
@@ -1460,15 +1451,15 @@ class WorkflowManager:
|
|
|
1460
1451
|
GriptapeNodes.ConfigManager().save_user_workflow_json(str(file_path))
|
|
1461
1452
|
except OSError as e:
|
|
1462
1453
|
details = f"Attempted to save workflow '{file_name}'. Failed when saving configuration: {e}"
|
|
1463
|
-
logger.error(details)
|
|
1464
1454
|
return SaveWorkflowResultFailure(result_details=details)
|
|
1465
1455
|
WorkflowRegistry.generate_new_workflow(metadata=workflow_metadata, file_path=relative_file_path)
|
|
1466
1456
|
# Update existing workflow's metadata in the registry
|
|
1467
1457
|
existing_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1468
1458
|
existing_workflow.metadata = workflow_metadata
|
|
1469
1459
|
details = f"Successfully saved workflow to: {file_path}"
|
|
1470
|
-
|
|
1471
|
-
|
|
1460
|
+
return SaveWorkflowResultSuccess(
|
|
1461
|
+
file_path=str(file_path), result_details=ResultDetails(message=details, level=logging.INFO)
|
|
1462
|
+
)
|
|
1472
1463
|
|
|
1473
1464
|
def _generate_workflow_metadata( # noqa: PLR0913
|
|
1474
1465
|
self,
|
|
@@ -1615,7 +1606,7 @@ class WorkflowManager:
|
|
|
1615
1606
|
),
|
|
1616
1607
|
)
|
|
1617
1608
|
|
|
1618
|
-
# Create conditional logic: workflow_executor = workflow_executor or LocalWorkflowExecutor()
|
|
1609
|
+
# Create conditional logic: workflow_executor = workflow_executor or LocalWorkflowExecutor(storage_backend=storage_backend_enum)
|
|
1619
1610
|
executor_assign = ast.Assign(
|
|
1620
1611
|
targets=[ast.Name(id="workflow_executor", ctx=ast.Store())],
|
|
1621
1612
|
value=ast.BoolOp(
|
|
@@ -1625,31 +1616,45 @@ class WorkflowManager:
|
|
|
1625
1616
|
ast.Call(
|
|
1626
1617
|
func=ast.Name(id="LocalWorkflowExecutor", ctx=ast.Load()),
|
|
1627
1618
|
args=[],
|
|
1628
|
-
keywords=[
|
|
1619
|
+
keywords=[
|
|
1620
|
+
ast.keyword(
|
|
1621
|
+
arg="storage_backend", value=ast.Name(id="storage_backend_enum", ctx=ast.Load())
|
|
1622
|
+
),
|
|
1623
|
+
],
|
|
1629
1624
|
),
|
|
1630
1625
|
],
|
|
1631
1626
|
),
|
|
1632
1627
|
)
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
ctx=ast.Load(),
|
|
1640
|
-
),
|
|
1641
|
-
args=[],
|
|
1642
|
-
keywords=[
|
|
1643
|
-
ast.keyword(arg="workflow_name", value=ast.Constant(flow_name)),
|
|
1644
|
-
ast.keyword(arg="flow_input", value=ast.Name(id="input", ctx=ast.Load())),
|
|
1645
|
-
ast.keyword(arg="storage_backend", value=ast.Name(id="storage_backend_enum", ctx=ast.Load())),
|
|
1646
|
-
],
|
|
1628
|
+
# Use async context manager for workflow execution
|
|
1629
|
+
with_stmt = ast.AsyncWith(
|
|
1630
|
+
items=[
|
|
1631
|
+
ast.withitem(
|
|
1632
|
+
context_expr=ast.Name(id="workflow_executor", ctx=ast.Load()),
|
|
1633
|
+
optional_vars=ast.Name(id="executor", ctx=ast.Store()),
|
|
1647
1634
|
)
|
|
1648
|
-
|
|
1635
|
+
],
|
|
1636
|
+
body=[
|
|
1637
|
+
ast.Expr(
|
|
1638
|
+
value=ast.Await(
|
|
1639
|
+
value=ast.Call(
|
|
1640
|
+
func=ast.Attribute(
|
|
1641
|
+
value=ast.Name(id="executor", ctx=ast.Load()),
|
|
1642
|
+
attr="arun",
|
|
1643
|
+
ctx=ast.Load(),
|
|
1644
|
+
),
|
|
1645
|
+
args=[],
|
|
1646
|
+
keywords=[
|
|
1647
|
+
ast.keyword(arg="workflow_name", value=ast.Constant(flow_name)),
|
|
1648
|
+
ast.keyword(arg="flow_input", value=ast.Name(id="input", ctx=ast.Load())),
|
|
1649
|
+
],
|
|
1650
|
+
)
|
|
1651
|
+
)
|
|
1652
|
+
)
|
|
1653
|
+
],
|
|
1649
1654
|
)
|
|
1650
1655
|
return_stmt = ast.Return(
|
|
1651
1656
|
value=ast.Attribute(
|
|
1652
|
-
value=ast.Name(id="
|
|
1657
|
+
value=ast.Name(id="executor", ctx=ast.Load()),
|
|
1653
1658
|
attr="output",
|
|
1654
1659
|
ctx=ast.Load(),
|
|
1655
1660
|
)
|
|
@@ -1659,7 +1664,7 @@ class WorkflowManager:
|
|
|
1659
1664
|
async_func_def = ast.AsyncFunctionDef(
|
|
1660
1665
|
name="aexecute_workflow",
|
|
1661
1666
|
args=args,
|
|
1662
|
-
body=[ensure_context_call, storage_backend_convert, executor_assign,
|
|
1667
|
+
body=[ensure_context_call, storage_backend_convert, executor_assign, with_stmt, return_stmt],
|
|
1663
1668
|
decorator_list=[],
|
|
1664
1669
|
returns=return_annotation,
|
|
1665
1670
|
type_params=[],
|
|
@@ -3122,12 +3127,10 @@ class WorkflowManager:
|
|
|
3122
3127
|
result = flow_manager.on_get_top_level_flow_request(GetTopLevelFlowRequest())
|
|
3123
3128
|
if result.failed():
|
|
3124
3129
|
details = f"Workflow '{workflow_name}' does not have a top-level flow."
|
|
3125
|
-
logger.error(details)
|
|
3126
3130
|
raise ValueError(details)
|
|
3127
3131
|
flow_name = cast("GetTopLevelFlowResultSuccess", result).flow_name
|
|
3128
3132
|
if flow_name is None:
|
|
3129
3133
|
details = f"Workflow '{workflow_name}' does not have a top-level flow."
|
|
3130
|
-
logger.error(details)
|
|
3131
3134
|
raise ValueError(details)
|
|
3132
3135
|
|
|
3133
3136
|
control_flow = flow_manager.get_flow_by_name(flow_name)
|
|
@@ -3195,22 +3198,21 @@ class WorkflowManager:
|
|
|
3195
3198
|
file_name=workflow_file.name,
|
|
3196
3199
|
)
|
|
3197
3200
|
)
|
|
3201
|
+
result_messages = []
|
|
3198
3202
|
if isinstance(register_workflow_result, RegisterWorkflowResultSuccess):
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
workflow_file.name,
|
|
3202
|
-
)
|
|
3203
|
+
success_message = f"Successfully registered new workflow with file '{workflow_file.name}'."
|
|
3204
|
+
result_messages.append(ResultDetail(message=success_message, level=logging.INFO))
|
|
3203
3205
|
else:
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
workflow_file.name,
|
|
3207
|
-
cast("RegisterWorkflowResultFailure", register_workflow_result).exception,
|
|
3208
|
-
)
|
|
3206
|
+
failure_message = f"Failed to register workflow with file '{workflow_file.name}': {cast('RegisterWorkflowResultFailure', register_workflow_result).exception}"
|
|
3207
|
+
result_messages.append(ResultDetail(message=failure_message, level=logging.WARNING))
|
|
3209
3208
|
else:
|
|
3210
|
-
|
|
3211
|
-
"Failed to load metadata for workflow file '
|
|
3212
|
-
workflow_file.name,
|
|
3209
|
+
metadata_failure_message = (
|
|
3210
|
+
f"Failed to load metadata for workflow file '{workflow_file.name}'. Not registering workflow."
|
|
3213
3211
|
)
|
|
3212
|
+
result_messages = [ResultDetail(message=metadata_failure_message, level=logging.WARNING)]
|
|
3213
|
+
|
|
3214
|
+
# Log all messages through consolidated ResultDetails
|
|
3215
|
+
ResultDetails(*result_messages)
|
|
3214
3216
|
|
|
3215
3217
|
def on_import_workflow_as_referenced_sub_flow_request(
|
|
3216
3218
|
self, request: ImportWorkflowAsReferencedSubFlowRequest
|
|
@@ -3240,7 +3242,6 @@ class WorkflowManager:
|
|
|
3240
3242
|
workflow = self._get_workflow_by_name(request.workflow_name)
|
|
3241
3243
|
except KeyError:
|
|
3242
3244
|
details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because workflow is not registered"
|
|
3243
|
-
logger.error(details)
|
|
3244
3245
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3245
3246
|
|
|
3246
3247
|
# Check workflow version - Schema version 0.6.0+ required for referenced workflow imports
|
|
@@ -3249,7 +3250,6 @@ class WorkflowManager:
|
|
|
3249
3250
|
workflow_version = Version.from_string(workflow.metadata.schema_version)
|
|
3250
3251
|
if workflow_version is None or workflow_version < required_version:
|
|
3251
3252
|
details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because workflow version '{workflow.metadata.schema_version}' is less than required version '0.6.0'. To remedy, open the workflow you are attempting to import and save it again to upgrade it to the latest version."
|
|
3252
|
-
logger.error(details)
|
|
3253
3253
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3254
3254
|
|
|
3255
3255
|
# Check target flow
|
|
@@ -3257,7 +3257,6 @@ class WorkflowManager:
|
|
|
3257
3257
|
if flow_name is None:
|
|
3258
3258
|
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
3259
3259
|
details = f"Attempted to import workflow '{request.workflow_name}' into Current Context. Failed because Current Context was empty"
|
|
3260
|
-
logger.error(details)
|
|
3261
3260
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3262
3261
|
else:
|
|
3263
3262
|
# Validate that the specified flow exists
|
|
@@ -3266,7 +3265,6 @@ class WorkflowManager:
|
|
|
3266
3265
|
flow_manager.get_flow_by_name(flow_name)
|
|
3267
3266
|
except KeyError:
|
|
3268
3267
|
details = f"Attempted to import workflow '{request.workflow_name}' into flow '{flow_name}'. Failed because target flow does not exist"
|
|
3269
|
-
logger.error(details)
|
|
3270
3268
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3271
3269
|
|
|
3272
3270
|
return None
|
|
@@ -3290,7 +3288,6 @@ class WorkflowManager:
|
|
|
3290
3288
|
|
|
3291
3289
|
if not workflow_result.execution_successful:
|
|
3292
3290
|
details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because workflow execution failed: {workflow_result.execution_details}"
|
|
3293
|
-
logger.error(details)
|
|
3294
3291
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3295
3292
|
|
|
3296
3293
|
# Get flows after importing to find the new referenced sub flow
|
|
@@ -3299,7 +3296,6 @@ class WorkflowManager:
|
|
|
3299
3296
|
|
|
3300
3297
|
if not new_flows:
|
|
3301
3298
|
details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because no new flow was created"
|
|
3302
|
-
logger.error(details)
|
|
3303
3299
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3304
3300
|
|
|
3305
3301
|
# For now, use the first created flow as the main imported flow
|
|
@@ -3323,17 +3319,18 @@ class WorkflowManager:
|
|
|
3323
3319
|
|
|
3324
3320
|
if not isinstance(set_metadata_result, SetFlowMetadataResultSuccess):
|
|
3325
3321
|
details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because metadata could not be applied to created flow '{created_flow_name}'"
|
|
3326
|
-
logger.error(details)
|
|
3327
3322
|
return ImportWorkflowAsReferencedSubFlowResultFailure(result_details=details)
|
|
3328
3323
|
|
|
3329
3324
|
logger.debug(
|
|
3330
3325
|
"Applied imported flow metadata to '%s': %s", created_flow_name, request.imported_flow_metadata
|
|
3331
3326
|
)
|
|
3332
3327
|
|
|
3333
|
-
|
|
3334
|
-
"Successfully imported workflow '
|
|
3328
|
+
details = (
|
|
3329
|
+
f"Successfully imported workflow '{request.workflow_name}' as referenced sub flow '{created_flow_name}'"
|
|
3330
|
+
)
|
|
3331
|
+
return ImportWorkflowAsReferencedSubFlowResultSuccess(
|
|
3332
|
+
created_flow_name=created_flow_name, result_details=details
|
|
3335
3333
|
)
|
|
3336
|
-
return ImportWorkflowAsReferencedSubFlowResultSuccess(created_flow_name=created_flow_name)
|
|
3337
3334
|
|
|
3338
3335
|
def on_branch_workflow_request(self, request: BranchWorkflowRequest) -> ResultPayload:
|
|
3339
3336
|
"""Create a branch (copy) of an existing workflow with branch tracking."""
|
|
@@ -3342,7 +3339,6 @@ class WorkflowManager:
|
|
|
3342
3339
|
source_workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
3343
3340
|
except KeyError:
|
|
3344
3341
|
details = f"Failed to branch workflow '{request.workflow_name}' because it does not exist"
|
|
3345
|
-
logger.error(details)
|
|
3346
3342
|
return BranchWorkflowResultFailure(result_details=details)
|
|
3347
3343
|
|
|
3348
3344
|
# Generate branch name if not provided
|
|
@@ -3358,7 +3354,6 @@ class WorkflowManager:
|
|
|
3358
3354
|
# Check if branch name already exists
|
|
3359
3355
|
if WorkflowRegistry.has_workflow_with_name(branch_name):
|
|
3360
3356
|
details = f"Failed to branch workflow '{request.workflow_name}' because branch name '{branch_name}' already exists"
|
|
3361
|
-
logger.error(details)
|
|
3362
3357
|
return BranchWorkflowResultFailure(result_details=details)
|
|
3363
3358
|
|
|
3364
3359
|
try:
|
|
@@ -3387,7 +3382,6 @@ class WorkflowManager:
|
|
|
3387
3382
|
source_file_path = WorkflowRegistry.get_complete_file_path(source_workflow.file_path)
|
|
3388
3383
|
if not Path(source_file_path).exists():
|
|
3389
3384
|
details = f"Failed to branch workflow '{request.workflow_name}': File path '{source_file_path}' does not exist. The workflow may have been moved or the workspace configuration may have changed."
|
|
3390
|
-
logger.error(details)
|
|
3391
3385
|
return BranchWorkflowResultFailure(result_details=details)
|
|
3392
3386
|
|
|
3393
3387
|
source_content = Path(source_file_path).read_text(encoding="utf-8")
|
|
@@ -3396,7 +3390,6 @@ class WorkflowManager:
|
|
|
3396
3390
|
branch_content = self._replace_workflow_metadata_header(source_content, branch_metadata)
|
|
3397
3391
|
if branch_content is None:
|
|
3398
3392
|
details = f"Failed to replace metadata header for branch workflow '{branch_name}'"
|
|
3399
|
-
logger.error(details)
|
|
3400
3393
|
return BranchWorkflowResultFailure(result_details=details)
|
|
3401
3394
|
|
|
3402
3395
|
# Write branch workflow file to disk BEFORE registering in registry
|
|
@@ -3410,14 +3403,15 @@ class WorkflowManager:
|
|
|
3410
3403
|
config_manager = GriptapeNodes.ConfigManager()
|
|
3411
3404
|
config_manager.save_user_workflow_json(branch_full_path)
|
|
3412
3405
|
|
|
3413
|
-
|
|
3406
|
+
details = f"Successfully branched workflow '{request.workflow_name}' as '{branch_name}'"
|
|
3414
3407
|
return BranchWorkflowResultSuccess(
|
|
3415
|
-
branched_workflow_name=branch_name,
|
|
3408
|
+
branched_workflow_name=branch_name,
|
|
3409
|
+
original_workflow_name=request.workflow_name,
|
|
3410
|
+
result_details=ResultDetails(message=details, level=logging.INFO),
|
|
3416
3411
|
)
|
|
3417
3412
|
|
|
3418
3413
|
except Exception as e:
|
|
3419
3414
|
details = f"Failed to branch workflow '{request.workflow_name}': {e!s}"
|
|
3420
|
-
logger.error(details)
|
|
3421
3415
|
import traceback
|
|
3422
3416
|
|
|
3423
3417
|
traceback.print_exc()
|
|
@@ -3430,14 +3424,12 @@ class WorkflowManager:
|
|
|
3430
3424
|
branch_workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
3431
3425
|
except KeyError as e:
|
|
3432
3426
|
details = f"Failed to merge workflow branch because it does not exist: {e!s}"
|
|
3433
|
-
logger.error(details)
|
|
3434
3427
|
return MergeWorkflowBranchResultFailure(result_details=details)
|
|
3435
3428
|
|
|
3436
3429
|
# Get source workflow name from branch metadata
|
|
3437
3430
|
source_workflow_name = branch_workflow.metadata.branched_from
|
|
3438
3431
|
if not source_workflow_name:
|
|
3439
3432
|
details = f"Failed to merge workflow branch '{request.workflow_name}' because it has no source workflow"
|
|
3440
|
-
logger.error(details)
|
|
3441
3433
|
return MergeWorkflowBranchResultFailure(result_details=details)
|
|
3442
3434
|
|
|
3443
3435
|
# Validate source workflow exists
|
|
@@ -3445,7 +3437,6 @@ class WorkflowManager:
|
|
|
3445
3437
|
source_workflow = WorkflowRegistry.get_workflow_by_name(source_workflow_name)
|
|
3446
3438
|
except KeyError:
|
|
3447
3439
|
details = f"Failed to merge workflow branch '{request.workflow_name}' because source workflow '{source_workflow_name}' does not exist"
|
|
3448
|
-
logger.error(details)
|
|
3449
3440
|
return MergeWorkflowBranchResultFailure(result_details=details)
|
|
3450
3441
|
|
|
3451
3442
|
try:
|
|
@@ -3475,7 +3466,6 @@ class WorkflowManager:
|
|
|
3475
3466
|
merged_content = self._replace_workflow_metadata_header(branch_content, merged_metadata)
|
|
3476
3467
|
if merged_content is None:
|
|
3477
3468
|
details = f"Failed to replace metadata header for merged workflow '{source_workflow_name}'"
|
|
3478
|
-
logger.error(details)
|
|
3479
3469
|
return MergeWorkflowBranchResultFailure(result_details=details)
|
|
3480
3470
|
|
|
3481
3471
|
# Write the updated content to the source workflow file
|
|
@@ -3486,28 +3476,28 @@ class WorkflowManager:
|
|
|
3486
3476
|
source_workflow.metadata = merged_metadata
|
|
3487
3477
|
|
|
3488
3478
|
# Remove the branch workflow from registry and delete file
|
|
3479
|
+
result_messages = []
|
|
3489
3480
|
try:
|
|
3490
3481
|
WorkflowRegistry.delete_workflow_by_name(request.workflow_name)
|
|
3491
3482
|
Path(branch_content_file_path).unlink()
|
|
3492
|
-
|
|
3483
|
+
cleanup_message = f"Deleted branch workflow file and registry entry for '{request.workflow_name}'"
|
|
3484
|
+
result_messages.append(ResultDetail(message=cleanup_message, level=logging.INFO))
|
|
3493
3485
|
except Exception as delete_error:
|
|
3494
|
-
|
|
3495
|
-
"Failed to fully clean up branch workflow '
|
|
3496
|
-
request.workflow_name,
|
|
3497
|
-
str(delete_error),
|
|
3486
|
+
warning_message = (
|
|
3487
|
+
f"Failed to fully clean up branch workflow '{request.workflow_name}': {delete_error!s}"
|
|
3498
3488
|
)
|
|
3489
|
+
result_messages.append(ResultDetail(message=warning_message, level=logging.WARNING))
|
|
3499
3490
|
# Continue anyway - the merge was successful even if cleanup failed
|
|
3500
3491
|
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3492
|
+
success_message = f"Successfully merged branch workflow '{request.workflow_name}' into source workflow '{source_workflow_name}'"
|
|
3493
|
+
result_messages.append(ResultDetail(message=success_message, level=logging.INFO))
|
|
3494
|
+
|
|
3495
|
+
return MergeWorkflowBranchResultSuccess(
|
|
3496
|
+
merged_workflow_name=source_workflow_name, result_details=ResultDetails(*result_messages)
|
|
3505
3497
|
)
|
|
3506
|
-
return MergeWorkflowBranchResultSuccess(merged_workflow_name=source_workflow_name)
|
|
3507
3498
|
|
|
3508
3499
|
except Exception as e:
|
|
3509
3500
|
details = f"Failed to merge branch workflow '{request.workflow_name}' into source workflow '{source_workflow_name}': {e!s}"
|
|
3510
|
-
logger.error(details)
|
|
3511
3501
|
return MergeWorkflowBranchResultFailure(result_details=details)
|
|
3512
3502
|
|
|
3513
3503
|
def on_reset_workflow_branch_request(self, request: ResetWorkflowBranchRequest) -> ResultPayload:
|
|
@@ -3517,14 +3507,12 @@ class WorkflowManager:
|
|
|
3517
3507
|
branch_workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
3518
3508
|
except KeyError as e:
|
|
3519
3509
|
details = f"Failed to reset workflow branch because it does not exist: {e!s}"
|
|
3520
|
-
logger.error(details)
|
|
3521
3510
|
return ResetWorkflowBranchResultFailure(result_details=details)
|
|
3522
3511
|
|
|
3523
3512
|
# Get source workflow name from branch metadata
|
|
3524
3513
|
source_workflow_name = branch_workflow.metadata.branched_from
|
|
3525
3514
|
if not source_workflow_name:
|
|
3526
3515
|
details = f"Failed to reset workflow branch '{request.workflow_name}' because it has no source workflow"
|
|
3527
|
-
logger.error(details)
|
|
3528
3516
|
return ResetWorkflowBranchResultFailure(result_details=details)
|
|
3529
3517
|
|
|
3530
3518
|
# Validate source workflow exists
|
|
@@ -3532,7 +3520,6 @@ class WorkflowManager:
|
|
|
3532
3520
|
source_workflow = WorkflowRegistry.get_workflow_by_name(source_workflow_name)
|
|
3533
3521
|
except KeyError:
|
|
3534
3522
|
details = f"Failed to reset workflow branch '{request.workflow_name}' because source workflow '{source_workflow_name}' does not exist"
|
|
3535
|
-
logger.error(details)
|
|
3536
3523
|
return ResetWorkflowBranchResultFailure(result_details=details)
|
|
3537
3524
|
|
|
3538
3525
|
try:
|
|
@@ -3562,7 +3549,6 @@ class WorkflowManager:
|
|
|
3562
3549
|
reset_content = self._replace_workflow_metadata_header(source_content, reset_metadata)
|
|
3563
3550
|
if reset_content is None:
|
|
3564
3551
|
details = f"Failed to replace metadata header for reset branch workflow '{request.workflow_name}'"
|
|
3565
|
-
logger.error(details)
|
|
3566
3552
|
return ResetWorkflowBranchResultFailure(result_details=details)
|
|
3567
3553
|
|
|
3568
3554
|
# Write the updated content to the branch workflow file
|
|
@@ -3574,15 +3560,13 @@ class WorkflowManager:
|
|
|
3574
3560
|
|
|
3575
3561
|
except Exception as e:
|
|
3576
3562
|
details = f"Failed to reset branch workflow '{request.workflow_name}' to source workflow '{source_workflow_name}': {e!s}"
|
|
3577
|
-
logger.error(details)
|
|
3578
3563
|
return ResetWorkflowBranchResultFailure(result_details=details)
|
|
3579
3564
|
else:
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
request.workflow_name,
|
|
3583
|
-
|
|
3565
|
+
details = f"Successfully reset branch workflow '{request.workflow_name}' to match source workflow '{source_workflow_name}'"
|
|
3566
|
+
return ResetWorkflowBranchResultSuccess(
|
|
3567
|
+
reset_workflow_name=request.workflow_name,
|
|
3568
|
+
result_details=ResultDetails(message=details, level=logging.INFO),
|
|
3584
3569
|
)
|
|
3585
|
-
return ResetWorkflowBranchResultSuccess(reset_workflow_name=request.workflow_name)
|
|
3586
3570
|
|
|
3587
3571
|
def on_compare_workflows_request(self, request: CompareWorkflowsRequest) -> ResultPayload:
|
|
3588
3572
|
"""Compare two workflows to determine if one is ahead, behind, or up-to-date relative to the other."""
|
|
@@ -3591,7 +3575,6 @@ class WorkflowManager:
|
|
|
3591
3575
|
workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
|
|
3592
3576
|
except KeyError:
|
|
3593
3577
|
details = f"Failed to compare workflow '{request.workflow_name}' because it does not exist"
|
|
3594
|
-
logger.error(details)
|
|
3595
3578
|
return CompareWorkflowsResultFailure(result_details=details)
|
|
3596
3579
|
|
|
3597
3580
|
# Use the provided compare_workflow_name
|
|
@@ -3612,6 +3595,7 @@ class WorkflowManager:
|
|
|
3612
3595
|
else None,
|
|
3613
3596
|
source_last_modified=None,
|
|
3614
3597
|
details=details,
|
|
3598
|
+
result_details="Workflow comparison completed successfully.",
|
|
3615
3599
|
)
|
|
3616
3600
|
|
|
3617
3601
|
# Compare last modified dates
|
|
@@ -3629,6 +3613,7 @@ class WorkflowManager:
|
|
|
3629
3613
|
workflow_last_modified=workflow_last_modified.isoformat() if workflow_last_modified else None,
|
|
3630
3614
|
source_last_modified=source_last_modified.isoformat() if source_last_modified else None,
|
|
3631
3615
|
details=details,
|
|
3616
|
+
result_details="Workflow comparison completed successfully.",
|
|
3632
3617
|
)
|
|
3633
3618
|
|
|
3634
3619
|
# Compare timestamps to determine status
|
|
@@ -3651,6 +3636,7 @@ class WorkflowManager:
|
|
|
3651
3636
|
workflow_last_modified=workflow_last_modified.isoformat(),
|
|
3652
3637
|
source_last_modified=source_last_modified.isoformat(),
|
|
3653
3638
|
details=details,
|
|
3639
|
+
result_details="Workflow comparison completed successfully.",
|
|
3654
3640
|
)
|
|
3655
3641
|
|
|
3656
3642
|
def _walk_object_tree(
|
|
@@ -3810,18 +3796,26 @@ class WorkflowManager:
|
|
|
3810
3796
|
try:
|
|
3811
3797
|
workflows_to_register = GriptapeNodes.ConfigManager().get_config_value(request.config_section)
|
|
3812
3798
|
if not workflows_to_register:
|
|
3813
|
-
|
|
3814
|
-
return RegisterWorkflowsFromConfigResultSuccess(
|
|
3799
|
+
details = f"No workflows found in configuration section '{request.config_section}'"
|
|
3800
|
+
return RegisterWorkflowsFromConfigResultSuccess(
|
|
3801
|
+
succeeded_workflows=[], failed_workflows=[], result_details=details
|
|
3802
|
+
)
|
|
3815
3803
|
|
|
3816
3804
|
# Process all workflows and track results
|
|
3817
3805
|
succeeded, failed = self._process_workflows_for_registration(workflows_to_register)
|
|
3818
3806
|
|
|
3819
3807
|
except Exception as e:
|
|
3820
3808
|
details = f"Failed to register workflows from configuration section '{request.config_section}': {e!s}"
|
|
3821
|
-
logger.error(details)
|
|
3822
3809
|
return RegisterWorkflowsFromConfigResultFailure(result_details=details)
|
|
3823
3810
|
else:
|
|
3824
|
-
return RegisterWorkflowsFromConfigResultSuccess(
|
|
3811
|
+
return RegisterWorkflowsFromConfigResultSuccess(
|
|
3812
|
+
succeeded_workflows=succeeded,
|
|
3813
|
+
failed_workflows=failed,
|
|
3814
|
+
result_details=ResultDetails(
|
|
3815
|
+
message=f"Successfully processed workflows: {len(succeeded)} succeeded, {len(failed)} failed.",
|
|
3816
|
+
level=logging.INFO,
|
|
3817
|
+
),
|
|
3818
|
+
)
|
|
3825
3819
|
|
|
3826
3820
|
def _process_workflows_for_registration(self, workflows_to_register: list[str]) -> WorkflowRegistrationResult:
|
|
3827
3821
|
"""Process a list of workflow paths for registration.
|
|
@@ -3832,33 +3826,9 @@ class WorkflowManager:
|
|
|
3832
3826
|
succeeded = []
|
|
3833
3827
|
failed = []
|
|
3834
3828
|
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
if path.is_dir():
|
|
3839
|
-
dir_result = self._process_workflow_directory(path)
|
|
3840
|
-
succeeded.extend(dir_result.succeeded)
|
|
3841
|
-
failed.extend(dir_result.failed)
|
|
3842
|
-
elif path.suffix == ".py":
|
|
3843
|
-
workflow_name = self._process_single_workflow_file(path)
|
|
3844
|
-
if workflow_name:
|
|
3845
|
-
succeeded.append(workflow_name)
|
|
3846
|
-
else:
|
|
3847
|
-
failed.append(str(path))
|
|
3848
|
-
|
|
3849
|
-
return WorkflowRegistrationResult(succeeded=succeeded, failed=failed)
|
|
3850
|
-
|
|
3851
|
-
def _process_workflow_directory(self, directory_path: Path) -> WorkflowRegistrationResult:
|
|
3852
|
-
"""Process all workflow files in a directory.
|
|
3853
|
-
|
|
3854
|
-
Returns:
|
|
3855
|
-
WorkflowRegistrationResult with succeeded and failed workflow names
|
|
3856
|
-
"""
|
|
3857
|
-
succeeded = []
|
|
3858
|
-
failed = []
|
|
3859
|
-
|
|
3860
|
-
for workflow_file in directory_path.glob("*.py"):
|
|
3861
|
-
# Check that the python file has script metadata
|
|
3829
|
+
def process_workflow_file(workflow_file: Path) -> None:
|
|
3830
|
+
"""Process a single workflow file for registration."""
|
|
3831
|
+
# Check if the file has workflow metadata before processing
|
|
3862
3832
|
metadata_blocks = self.get_workflow_metadata(
|
|
3863
3833
|
workflow_file, block_name=WorkflowManager.WORKFLOW_METADATA_HEADER
|
|
3864
3834
|
)
|
|
@@ -3869,6 +3839,18 @@ class WorkflowManager:
|
|
|
3869
3839
|
else:
|
|
3870
3840
|
failed.append(str(workflow_file))
|
|
3871
3841
|
|
|
3842
|
+
def process_path(path: Path) -> None:
|
|
3843
|
+
"""Process a path, handling both files and directories."""
|
|
3844
|
+
if path.is_dir():
|
|
3845
|
+
# Process all Python files recursively in the directory
|
|
3846
|
+
for workflow_file in path.rglob("*.py"):
|
|
3847
|
+
process_workflow_file(workflow_file)
|
|
3848
|
+
elif path.suffix == ".py":
|
|
3849
|
+
process_workflow_file(path)
|
|
3850
|
+
|
|
3851
|
+
for workflow_to_register in workflows_to_register:
|
|
3852
|
+
process_path(Path(workflow_to_register))
|
|
3853
|
+
|
|
3872
3854
|
return WorkflowRegistrationResult(succeeded=succeeded, failed=failed)
|
|
3873
3855
|
|
|
3874
3856
|
def _process_single_workflow_file(self, workflow_file: Path) -> str | None:
|
|
@@ -3877,6 +3859,8 @@ class WorkflowManager:
|
|
|
3877
3859
|
Returns:
|
|
3878
3860
|
Workflow name if registered successfully, None if failed or skipped
|
|
3879
3861
|
"""
|
|
3862
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
3863
|
+
|
|
3880
3864
|
# Parse metadata once and use it for both registration check and actual registration
|
|
3881
3865
|
load_metadata_request = LoadWorkflowMetadata(file_name=str(workflow_file))
|
|
3882
3866
|
load_metadata_result = self.on_load_workflow_metadata_request(load_metadata_request)
|
|
@@ -3892,10 +3876,20 @@ class WorkflowManager:
|
|
|
3892
3876
|
logger.debug("Skipping already registered workflow: %s", workflow_file)
|
|
3893
3877
|
return None
|
|
3894
3878
|
|
|
3879
|
+
# Convert to relative path if the workflow is under workspace_path
|
|
3880
|
+
config_mgr = GriptapeNodes.ConfigManager()
|
|
3881
|
+
workspace_path = config_mgr.workspace_path
|
|
3882
|
+
|
|
3883
|
+
if workflow_file.is_relative_to(workspace_path):
|
|
3884
|
+
relative_path = workflow_file.relative_to(workspace_path)
|
|
3885
|
+
file_path_to_register = str(relative_path)
|
|
3886
|
+
else:
|
|
3887
|
+
file_path_to_register = str(workflow_file)
|
|
3888
|
+
|
|
3895
3889
|
# Register workflow using existing method with parsed metadata available
|
|
3896
3890
|
# The _register_workflow method will re-parse metadata, but this is acceptable
|
|
3897
3891
|
# since we've already validated it's parseable and the duplicate work is minimal
|
|
3898
|
-
if self._register_workflow(
|
|
3892
|
+
if self._register_workflow(file_path_to_register):
|
|
3899
3893
|
return workflow_metadata.name
|
|
3900
3894
|
return None
|
|
3901
3895
|
|