griptape-nodes 0.55.1__tar.gz → 0.56.0__tar.gz

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 (162) hide show
  1. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/PKG-INFO +1 -1
  2. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/pyproject.toml +1 -1
  3. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/init.py +88 -0
  4. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/models.py +2 -0
  5. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/shared.py +1 -0
  6. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/core_types.py +104 -0
  7. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/node_types.py +9 -12
  8. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/control_flow.py +10 -0
  9. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/dag_builder.py +21 -2
  10. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/parallel_resolution.py +25 -10
  11. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/node_library/workflow_registry.py +73 -3
  12. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/execution_events.py +12 -2
  13. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/flow_events.py +58 -0
  14. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/events/resource_events.py +290 -0
  15. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +57 -2
  16. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +9 -1
  17. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +678 -12
  18. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +13 -19
  19. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/model_manager.py +184 -83
  20. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +3 -3
  21. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +118 -1
  22. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
  23. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
  24. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
  25. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
  26. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
  27. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
  28. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
  29. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
  30. griptape_nodes-0.56.0/src/griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
  31. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/settings.py +5 -0
  32. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
  33. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +359 -261
  34. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/README.md +0 -0
  35. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/__init__.py +0 -0
  36. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/__main__.py +0 -0
  37. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/app/.python-version +0 -0
  38. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/app/__init__.py +0 -0
  39. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/app/app.py +0 -0
  40. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/app/watch.py +0 -0
  41. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
  42. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  43. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  44. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  45. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/__init__.py +0 -0
  46. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/__init__.py +0 -0
  47. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/config.py +0 -0
  48. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/engine.py +0 -0
  49. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/libraries.py +0 -0
  50. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/commands/self.py +0 -0
  51. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/cli/main.py +0 -0
  52. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/common/__init__.py +0 -0
  53. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/common/directed_graph.py +0 -0
  54. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/__init__.py +0 -0
  55. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
  56. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  57. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  58. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +0 -0
  59. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
  60. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
  61. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/connections.py +0 -0
  62. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/flow.py +0 -0
  63. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/param_components/__init__.py +0 -0
  64. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/param_components/execution_status_component.py +0 -0
  65. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
  66. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/__init__.py +0 -0
  67. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/fsm.py +0 -0
  68. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/machines/sequential_resolution.py +0 -0
  69. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
  70. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/node_library/__init__.py +0 -0
  71. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
  72. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
  73. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/py.typed +0 -0
  74. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
  75. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
  76. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
  77. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
  78. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  79. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/base_events.py +0 -0
  80. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
  81. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
  82. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
  83. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  84. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
  85. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
  86. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/model_events.py +0 -0
  87. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/node_events.py +0 -0
  88. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
  89. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
  90. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  91. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  92. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  93. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  94. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
  95. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
  96. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/events/variable_events.py +0 -0
  97. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
  98. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  99. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  100. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  101. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  102. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  103. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  104. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  105. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  106. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  107. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  108. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  109. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  110. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  111. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  112. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  113. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  114. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  115. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  116. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  117. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  118. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  119. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  120. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  121. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/variable_manager.py +0 -0
  122. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  123. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/retained_mode.py +0 -0
  124. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
  125. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  126. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  127. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/retained_mode/variable_types.py +0 -0
  128. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/servers/__init__.py +0 -0
  129. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/servers/mcp.py +0 -0
  130. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/servers/static.py +0 -0
  131. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/servers/ws_request_manager.py +0 -0
  132. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/__init__.py +0 -0
  133. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
  134. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/button.py +0 -0
  135. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/clamp.py +0 -0
  136. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/color_picker.py +0 -0
  137. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/compare.py +0 -0
  138. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/compare_images.py +0 -0
  139. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
  140. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/minmax.py +0 -0
  141. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/multi_options.py +0 -0
  142. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/numbers_selector.py +0 -0
  143. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/options.py +0 -0
  144. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/slider.py +0 -0
  145. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
  146. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/traits/traits.json +0 -0
  147. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/updater/__init__.py +0 -0
  148. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/updater/__main__.py +0 -0
  149. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/__init__.py +0 -0
  150. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/async_utils.py +0 -0
  151. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
  152. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/image_preview.py +0 -0
  153. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
  154. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
  155. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/utils/version_utils.py +0 -0
  156. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
  157. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  158. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  159. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  160. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
  161. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
  162. {griptape_nodes-0.55.1 → griptape_nodes-0.56.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.55.1
3
+ Version: 0.56.0
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.2
6
6
  Requires-Dist: pydantic>=2.10.6
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "griptape-nodes"
3
- version = "0.55.1"
3
+ version = "0.56.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0, <3.13"
@@ -55,6 +55,10 @@ def init_command( # noqa: PLR0913
55
55
  bool,
56
56
  typer.Option(help="Run init in non-interactive mode (no prompts)."),
57
57
  ] = False,
58
+ hf_token: Annotated[
59
+ str | None,
60
+ typer.Option(help="Set the Hugging Face token for downloading gated models."),
61
+ ] = None,
58
62
  config: Annotated[
59
63
  list[str] | None,
60
64
  typer.Option(
@@ -83,6 +87,7 @@ def init_command( # noqa: PLR0913
83
87
  secret_values=secret_values,
84
88
  libraries_sync=libraries_sync,
85
89
  bucket_name=bucket_name,
90
+ hf_token=hf_token,
86
91
  )
87
92
  )
88
93
 
@@ -128,6 +133,7 @@ def _run_init_configuration(config: InitConfig) -> None:
128
133
  _handle_workspace_config(config)
129
134
  _handle_storage_backend_config(config)
130
135
  _handle_bucket_config(config)
136
+ _handle_hf_token_config(config)
131
137
  _handle_advanced_library_config(config)
132
138
  _handle_arbitrary_configs(config)
133
139
 
@@ -193,6 +199,25 @@ def _handle_bucket_config(config: InitConfig) -> str | None:
193
199
  return bucket_id
194
200
 
195
201
 
202
+ def _handle_hf_token_config(config: InitConfig) -> str | None:
203
+ """Handle Hugging Face token configuration step."""
204
+ hf_token = None
205
+
206
+ if config.interactive:
207
+ # First ask if they want to configure an HF token
208
+ configure_hf_token = _prompt_for_hf_token_configuration()
209
+ if configure_hf_token:
210
+ hf_token = _prompt_for_hf_token(default_hf_token=config.hf_token)
211
+ elif config.hf_token is not None:
212
+ hf_token = config.hf_token
213
+
214
+ if hf_token is not None:
215
+ secrets_manager.set_secret("HF_TOKEN", hf_token)
216
+ console.print("[bold green]Hugging Face token set[/bold green]")
217
+
218
+ return hf_token
219
+
220
+
196
221
  def _handle_advanced_library_config(config: InitConfig) -> bool | None:
197
222
  """Handle advanced library configuration step."""
198
223
  register_advanced_library = config.register_advanced_library
@@ -513,6 +538,69 @@ def _create_new_bucket(bucket_name: str) -> str:
513
538
  return bucket_id
514
539
 
515
540
 
541
+ def _prompt_for_hf_token_configuration() -> bool:
542
+ """Prompts the user whether to configure a Hugging Face token."""
543
+ # Check if there's already an HF token configured
544
+ current_hf_token = secrets_manager.get_secret("HF_TOKEN", should_error_on_not_found=False)
545
+
546
+ if current_hf_token:
547
+ explainer = """[bold cyan]Hugging Face Token Configuration[/bold cyan]
548
+ You currently have a Hugging Face token configured.
549
+
550
+ Hugging Face tokens are used to access gated models from the Hugging Face Hub, such as:
551
+ - Meta's Llama models
552
+ - black-forest-labs/FLUX.1-dev
553
+ - Other restricted or premium models
554
+
555
+ Would you like to update your Hugging Face token or keep the current one?"""
556
+ prompt_text = "Update Hugging Face token?"
557
+ default_value = False
558
+ else:
559
+ explainer = """[bold cyan]Hugging Face Token Configuration[/bold cyan]
560
+ Would you like to configure a Hugging Face token?
561
+
562
+ Hugging Face tokens are used by the model manager to download gated models from the Hugging Face Hub, such as:
563
+ - Meta's Llama models
564
+ - black-forest-labs/FLUX.1-dev
565
+ - Other restricted or premium models
566
+
567
+ If you don't plan to use gated models, you can skip this step.
568
+ You can get a token from https://huggingface.co/settings/tokens
569
+
570
+ You can always configure a token later by running the initialization process again."""
571
+ prompt_text = "Configure Hugging Face token?"
572
+ default_value = False
573
+
574
+ console.print(Panel(explainer, expand=False))
575
+ return Confirm.ask(prompt_text, default=default_value)
576
+
577
+
578
+ def _prompt_for_hf_token(default_hf_token: str | None = None) -> str | None:
579
+ """Prompts the user for their Hugging Face token."""
580
+ if default_hf_token is None:
581
+ default_hf_token = secrets_manager.get_secret("HF_TOKEN", should_error_on_not_found=False)
582
+
583
+ explainer = """[bold cyan]Hugging Face Token[/bold cyan]
584
+ Please enter your Hugging Face token to enable downloading of gated models.
585
+
586
+ To get a token:
587
+ 1. Go to https://huggingface.co/settings/tokens
588
+ 2. Create a new token with 'Read' permissions
589
+ 3. Copy and paste the token here
590
+
591
+ You can leave this blank to skip token configuration."""
592
+ console.print(Panel(explainer, expand=False))
593
+
594
+ hf_token = Prompt.ask(
595
+ "Hugging Face Token (optional)",
596
+ default=default_hf_token or "",
597
+ show_default=False,
598
+ )
599
+
600
+ # Return None if empty string
601
+ return hf_token if hf_token.strip() else None
602
+
603
+
516
604
  def _parse_key_value_pairs(pairs: list[str] | None) -> dict[str, Any] | None:
517
605
  """Parse key=value pairs from a list of strings.
518
606
 
@@ -1,6 +1,7 @@
1
1
  """Models command for managing AI models."""
2
2
 
3
3
  import asyncio
4
+ import sys
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  import typer
@@ -130,6 +131,7 @@ async def _download_model(
130
131
  except Exception as e:
131
132
  console.print("[bold red]Model download failed:[/bold red]")
132
133
  console.print(f"[red]{e}[/red]")
134
+ sys.exit(1)
133
135
 
134
136
 
135
137
  async def _list_models() -> None:
@@ -22,6 +22,7 @@ class InitConfig:
22
22
  secret_values: dict[str, str] | None = None
23
23
  libraries_sync: bool | None = None
24
24
  bucket_name: str | None = None
25
+ hf_token: str | None = None
25
26
 
26
27
 
27
28
  # Initialize console
@@ -1483,12 +1483,23 @@ class ParameterList(ParameterContainer):
1483
1483
  user_defined: bool = False,
1484
1484
  element_id: str | None = None,
1485
1485
  element_type: str | None = None,
1486
+ # UI convenience parameters
1487
+ collapsed: bool | None = None,
1488
+ child_prefix: str | None = None,
1489
+ grid: bool | None = None,
1490
+ grid_columns: int | None = None,
1486
1491
  ):
1487
1492
  if traits:
1488
1493
  self._original_traits = traits
1489
1494
  else:
1490
1495
  self._original_traits = set()
1491
1496
 
1497
+ # Store the UI convenience parameters
1498
+ self._collapsed = collapsed
1499
+ self._child_prefix = child_prefix
1500
+ self._grid = grid
1501
+ self._grid_columns = grid_columns
1502
+
1492
1503
  # Remember: we're a Parameter, too, just like everybody else.
1493
1504
  super().__init__(
1494
1505
  name=name,
@@ -1511,6 +1522,99 @@ class ParameterList(ParameterContainer):
1511
1522
  element_type=element_type,
1512
1523
  )
1513
1524
 
1525
+ @property
1526
+ def collapsed(self) -> bool | None:
1527
+ return self._collapsed
1528
+
1529
+ @collapsed.setter
1530
+ @BaseNodeElement.emits_update_on_write
1531
+ def collapsed(self, value: bool | None) -> None:
1532
+ self._collapsed = value
1533
+
1534
+ @property
1535
+ def child_prefix(self) -> str | None:
1536
+ return self._child_prefix
1537
+
1538
+ @child_prefix.setter
1539
+ @BaseNodeElement.emits_update_on_write
1540
+ def child_prefix(self, value: str | None) -> None:
1541
+ self._child_prefix = value
1542
+
1543
+ @property
1544
+ def grid(self) -> bool | None:
1545
+ return self._grid
1546
+
1547
+ @grid.setter
1548
+ @BaseNodeElement.emits_update_on_write
1549
+ def grid(self, value: bool | None) -> None:
1550
+ self._grid = value
1551
+
1552
+ @property
1553
+ def grid_columns(self) -> int | None:
1554
+ return self._grid_columns
1555
+
1556
+ @grid_columns.setter
1557
+ @BaseNodeElement.emits_update_on_write
1558
+ def grid_columns(self, value: int | None) -> None:
1559
+ self._grid_columns = value
1560
+
1561
+ @property
1562
+ def ui_options(self) -> dict:
1563
+ """Override ui_options to merge convenience parameters in real-time."""
1564
+ # Get base ui_options from parent
1565
+ base_ui_options = super().ui_options
1566
+
1567
+ # Build convenience options from instance parameters
1568
+ convenience_options = {}
1569
+
1570
+ if self._collapsed is not None:
1571
+ convenience_options["collapsed"] = self._collapsed
1572
+
1573
+ if self._child_prefix is not None:
1574
+ convenience_options["child_prefix"] = self._child_prefix
1575
+
1576
+ if self._grid is not None and self._grid:
1577
+ convenience_options["display"] = "grid"
1578
+
1579
+ if self._grid_columns is not None and self._grid:
1580
+ convenience_options["columns"] = self._grid_columns
1581
+
1582
+ # Merge convenience options with base ui_options
1583
+ return {
1584
+ **base_ui_options,
1585
+ **convenience_options,
1586
+ }
1587
+
1588
+ @ui_options.setter
1589
+ @BaseNodeElement.emits_update_on_write
1590
+ def ui_options(self, value: dict) -> None:
1591
+ """Set ui_options, preserving convenience parameters."""
1592
+ # Extract convenience parameters from the incoming value
1593
+ if "display" in value and value["display"] == "grid":
1594
+ self._grid = True
1595
+ if "columns" in value:
1596
+ self._grid_columns = value["columns"]
1597
+ else:
1598
+ self._grid = False
1599
+
1600
+ if "collapsed" in value:
1601
+ self._collapsed = value["collapsed"]
1602
+
1603
+ if "child_prefix" in value:
1604
+ self._child_prefix = value["child_prefix"]
1605
+
1606
+ # Set the base ui_options (excluding convenience parameters)
1607
+ base_ui_options = {
1608
+ k: v for k, v in value.items() if k not in ["display", "columns", "collapsed", "child_prefix"]
1609
+ }
1610
+ self._ui_options = base_ui_options
1611
+
1612
+ def to_dict(self) -> dict[str, Any]:
1613
+ """Override to_dict to use the merged ui_options."""
1614
+ data = super().to_dict()
1615
+ data["ui_options"] = self.ui_options
1616
+ return data
1617
+
1514
1618
  def _custom_getter_for_property_type(self) -> str:
1515
1619
  base_type = super()._custom_getter_for_property_type()
1516
1620
  result = f"list[{base_type}]"
@@ -384,9 +384,7 @@ class BaseNode(ABC):
384
384
  for name in names:
385
385
  parameter = self.get_parameter_by_name(name)
386
386
  if parameter is not None:
387
- ui_options = parameter.ui_options
388
- ui_options["hide"] = not visible
389
- parameter.ui_options = ui_options
387
+ parameter.ui_options = {**parameter.ui_options, "hide": not visible}
390
388
 
391
389
  def get_message_by_name_or_element_id(self, element: str) -> ParameterMessage | None:
392
390
  element_items = self.root_ui_element.find_elements_by_type(ParameterMessage)
@@ -408,9 +406,7 @@ class BaseNode(ABC):
408
406
  for name in names:
409
407
  message = self.get_message_by_name_or_element_id(name)
410
408
  if message is not None:
411
- ui_options = message.ui_options
412
- ui_options["hide"] = not visible
413
- message.ui_options = ui_options
409
+ message.ui_options = {**message.ui_options, "hide": not visible}
414
410
 
415
411
  def hide_message_by_name(self, names: str | list[str]) -> None:
416
412
  self._set_message_visibility(names, visible=False)
@@ -727,11 +723,9 @@ class BaseNode(ABC):
727
723
  return param
728
724
  return None
729
725
 
730
- # Abstract method to process the node. Must be defined by the type
731
726
  # Must save the values of the output parameters in NodeContext.
732
- @abstractmethod
733
- def process[T](self) -> AsyncResult | None:
734
- pass
727
+ def process(self) -> AsyncResult | None:
728
+ raise NotImplementedError
735
729
 
736
730
  async def aprocess(self) -> None:
737
731
  """Async version of process().
@@ -867,7 +861,7 @@ class BaseNode(ABC):
867
861
  msg = f"Parameter '{parameter_name} doesn't exist on {self.name}'"
868
862
  raise RuntimeError(msg)
869
863
 
870
- def reorder_elements(self, element_order: list[str | int]) -> None:
864
+ def reorder_elements(self, element_order: list[str] | list[int] | list[str | int]) -> None:
871
865
  """Reorder the elements of this node.
872
866
 
873
867
  Args:
@@ -1109,7 +1103,10 @@ class TrackedParameterOutputValues(dict[str, Any]):
1109
1103
  keys_to_clear = list(self.keys())
1110
1104
  super().clear()
1111
1105
  for key in keys_to_clear:
1112
- self._emit_parameter_change_event(key, None, deleted=True)
1106
+ # Some nodes still have values set, even if their output values are cleared
1107
+ # Here, we are emitting an event with those set values, to not misrepresent the values of the parameters in the UI.
1108
+ value = self._node.get_parameter_value(key)
1109
+ self._emit_parameter_change_event(key, value, deleted=True)
1113
1110
 
1114
1111
  def silent_clear(self) -> None:
1115
1112
  """Clear all values without emitting parameter change events."""
@@ -15,6 +15,7 @@ from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, Exec
15
15
  from griptape_nodes.retained_mode.events.execution_events import (
16
16
  ControlFlowResolvedEvent,
17
17
  CurrentControlNodeEvent,
18
+ InvolvedNodesEvent,
18
19
  SelectedControlOutputEvent,
19
20
  )
20
21
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
@@ -252,12 +253,21 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
252
253
  current_nodes = await self._process_nodes_for_dag(start_node)
253
254
  else:
254
255
  current_nodes = [start_node]
256
+ # For control flow/sequential: emit all nodes in flow as involved
255
257
  self._context.current_nodes = current_nodes
256
258
  # Set entry control parameter for initial node (None for workflow start)
257
259
  for node in current_nodes:
258
260
  node.set_entry_control_parameter(None)
259
261
  # Set up to debug
260
262
  self._context.paused = debug_mode
263
+ flow_manager = GriptapeNodes.FlowManager()
264
+ flow = flow_manager.get_flow_by_name(self._context.flow_name)
265
+ involved_nodes = list(flow.nodes.keys())
266
+ GriptapeNodes.EventManager().put_event(
267
+ ExecutionGriptapeNodeEvent(
268
+ wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=involved_nodes))
269
+ )
270
+ )
261
271
  await self.start(ResolveNodeState) # Begins the flow
262
272
 
263
273
  async def update(self) -> None:
@@ -43,10 +43,12 @@ class DagBuilder:
43
43
 
44
44
  graphs: dict[str, DirectedGraph] # Str is the name of the start node associated here.
45
45
  node_to_reference: dict[str, DagNode]
46
+ graph_to_nodes: dict[str, set[str]] # Track which nodes belong to which graph
46
47
 
47
48
  def __init__(self) -> None:
48
49
  self.graphs = {}
49
50
  self.node_to_reference: dict[str, DagNode] = {}
51
+ self.graph_to_nodes = {}
50
52
 
51
53
  # Complex with the inner recursive method, but it needs connections and added_nodes.
52
54
  def add_node_with_dependencies(self, node: BaseNode, graph_name: str = "default") -> list[BaseNode]: # noqa: C901
@@ -59,16 +61,15 @@ class DagBuilder:
59
61
  if graph is None:
60
62
  graph = DirectedGraph()
61
63
  self.graphs[graph_name] = graph
64
+ self.graph_to_nodes[graph_name] = set()
62
65
 
63
66
  def _add_node_recursive(current_node: BaseNode, visited: set[str], graph: DirectedGraph) -> None:
64
67
  if current_node.name in visited:
65
68
  return
66
69
  visited.add(current_node.name)
67
-
68
70
  # Skip if already in DAG (use DAG membership, not resolved state)
69
71
  if current_node.name in self.node_to_reference:
70
72
  return
71
-
72
73
  # Process dependencies first (depth-first)
73
74
  ignore_data_dependencies = False
74
75
  # This is specifically for output_selector. Overriding 'initialize_spotlight' doesn't work anymore.
@@ -98,6 +99,10 @@ class DagBuilder:
98
99
  dag_node = DagNode(node_reference=current_node, node_state=NodeState.WAITING)
99
100
  self.node_to_reference[current_node.name] = dag_node
100
101
  graph.add_node(node_for_adding=current_node.name)
102
+
103
+ # Track which nodes belong to this graph
104
+ self.graph_to_nodes[graph_name].add(current_node.name)
105
+
101
106
  # DON'T mark as resolved - that happens during actual execution
102
107
  added_nodes.append(current_node)
103
108
 
@@ -117,12 +122,19 @@ class DagBuilder:
117
122
  graph = DirectedGraph()
118
123
  self.graphs[graph_name] = graph
119
124
  graph.add_node(node_for_adding=node.name)
125
+
126
+ # Track which nodes belong to this graph
127
+ if graph_name not in self.graph_to_nodes:
128
+ self.graph_to_nodes[graph_name] = set()
129
+ self.graph_to_nodes[graph_name].add(node.name)
130
+
120
131
  return dag_node
121
132
 
122
133
  def clear(self) -> None:
123
134
  """Clear all nodes and references from the DAG builder."""
124
135
  self.graphs.clear()
125
136
  self.node_to_reference.clear()
137
+ self.graph_to_nodes.clear()
126
138
 
127
139
  def can_queue_control_node(self, node: DagNode) -> bool:
128
140
  if len(self.graphs) == 1:
@@ -205,3 +217,10 @@ class DagBuilder:
205
217
  return True
206
218
 
207
219
  return False
220
+
221
+ def cleanup_empty_graph_nodes(self, graph_name: str) -> None:
222
+ """Remove nodes from node_to_reference when their graph becomes empty (only in single node resolution)."""
223
+ if graph_name in self.graph_to_nodes:
224
+ for node_name in self.graph_to_nodes[graph_name]:
225
+ self.node_to_reference.pop(node_name, None)
226
+ self.graph_to_nodes.pop(graph_name, None)
@@ -18,6 +18,7 @@ from griptape_nodes.retained_mode.events.base_events import (
18
18
  from griptape_nodes.retained_mode.events.execution_events import (
19
19
  CurrentControlNodeEvent,
20
20
  CurrentDataNodeEvent,
21
+ InvolvedNodesEvent,
21
22
  NodeResolvedEvent,
22
23
  ParameterValueUpdateEvent,
23
24
  )
@@ -184,19 +185,22 @@ class ExecuteDagState(State):
184
185
  context: ParallelResolutionContext, node: BaseNode, network_name: str, flow_manager: FlowManager
185
186
  ) -> bool:
186
187
  """Check if control flow processing should be skipped."""
188
+ # Get network once to avoid duplicate lookups
189
+ if context.dag_builder is None:
190
+ msg = "DAG builder is not initialized"
191
+ raise ValueError(msg)
192
+ network = context.dag_builder.graphs.get(network_name, None)
193
+ if network is None:
194
+ msg = f"Network {network_name} not found in DAG builder"
195
+ raise ValueError(msg)
187
196
  if flow_manager.global_single_node_resolution:
197
+ # Clean up nodes from emptied graphs in single node resolution mode
198
+ if len(network) == 0 and context.dag_builder is not None:
199
+ context.dag_builder.cleanup_empty_graph_nodes(network_name)
200
+ ExecuteDagState._emit_involved_nodes_update(context)
188
201
  return True
189
202
 
190
- if context.dag_builder is not None:
191
- network = context.dag_builder.graphs.get(network_name, None)
192
- if network is not None and len(network) > 0:
193
- return True
194
-
195
- if node.stop_flow:
196
- node.stop_flow = False
197
- return True
198
-
199
- return False
203
+ return bool(len(network) > 0 or node.stop_flow)
200
204
 
201
205
  @staticmethod
202
206
  def _process_next_control_node(
@@ -229,6 +233,17 @@ class ExecuteDagState(State):
229
233
  )
230
234
  ExecuteDagState._add_and_queue_nodes(context, next_node, network_name)
231
235
 
236
+ @staticmethod
237
+ def _emit_involved_nodes_update(context: ParallelResolutionContext) -> None:
238
+ """Emit update of involved nodes based on current DAG state."""
239
+ if context.dag_builder is not None:
240
+ involved_nodes = list(context.node_to_reference.keys())
241
+ GriptapeNodes.EventManager().put_event(
242
+ ExecutionGriptapeNodeEvent(
243
+ wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=involved_nodes))
244
+ )
245
+ )
246
+
232
247
  @staticmethod
233
248
  def _add_and_queue_nodes(context: ParallelResolutionContext, next_node: BaseNode, network_name: str) -> None:
234
249
  """Add nodes to DAG and queue them if ready."""
@@ -1,19 +1,45 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
4
+ import logging
3
5
  from datetime import datetime # noqa: TC003 (can't put into type checking block as Pydantic model relies on it)
4
6
  from pathlib import Path
5
- from typing import ClassVar
7
+ from typing import Any, ClassVar
6
8
 
7
- from pydantic import BaseModel, Field
9
+ from pydantic import BaseModel, Field, field_serializer, field_validator
8
10
 
9
11
  from griptape_nodes.node_library.library_registry import (
10
12
  LibraryNameAndVersion, # noqa: TC001 (putting this into type checking causes it to not be defined)
11
13
  )
12
14
  from griptape_nodes.utils.metaclasses import SingletonMeta
13
15
 
16
+ logger = logging.getLogger("griptape_nodes")
17
+
18
+ # Type aliases for clarity
19
+ type NodeName = str
20
+ type ParameterName = str
21
+ type ParameterAttribute = str
22
+ type ParameterMinimalDict = dict[ParameterAttribute, Any]
23
+ type NodeParametersMapping = dict[NodeName, dict[ParameterName, ParameterMinimalDict]]
24
+
25
+
26
+ class WorkflowShape(BaseModel):
27
+ """This structure reflects the input and output shapes extracted from StartNodes and EndNodes inside of the workflow.
28
+
29
+ A workflow may have multiple StartNodes and multiple EndNodes, each contributing their parameters
30
+ to the overall workflow shape.
31
+
32
+ Structure is:
33
+ - inputs: {start_node_name: {param_name: param_minimal_dict}}
34
+ - outputs: {end_node_name: {param_name: param_minimal_dict}}
35
+ """
36
+
37
+ inputs: NodeParametersMapping = Field(default_factory=dict)
38
+ outputs: NodeParametersMapping = Field(default_factory=dict)
39
+
14
40
 
15
41
  class WorkflowMetadata(BaseModel):
16
- LATEST_SCHEMA_VERSION: ClassVar[str] = "0.7.0"
42
+ LATEST_SCHEMA_VERSION: ClassVar[str] = "0.8.0"
17
43
 
18
44
  name: str
19
45
  schema_version: str
@@ -27,6 +53,50 @@ class WorkflowMetadata(BaseModel):
27
53
  creation_date: datetime | None = Field(default=None)
28
54
  last_modified_date: datetime | None = Field(default=None)
29
55
  branched_from: str | None = Field(default=None)
56
+ workflow_shape: WorkflowShape | None = Field(default=None)
57
+
58
+ @field_serializer("workflow_shape")
59
+ def serialize_workflow_shape(self, workflow_shape: WorkflowShape | None) -> str | None:
60
+ """Serialize WorkflowShape as JSON string to avoid TOML serialization issues.
61
+
62
+ The WorkflowShape contains deeply nested dictionaries with None values that are
63
+ meaningful data (e.g., default_value: None). TOML's nested table format creates
64
+ unreadable output and tomlkit fails on None values in nested structures.
65
+ JSON preserves None as null and keeps the data compact and readable.
66
+ """
67
+ if workflow_shape is None:
68
+ return None
69
+ # Use json.dumps to preserve None values as null, which TOML can handle
70
+ return json.dumps(workflow_shape.model_dump(), separators=(",", ":"))
71
+
72
+ @field_validator("workflow_shape", mode="before")
73
+ @classmethod
74
+ def validate_workflow_shape(cls, value: Any) -> WorkflowShape | None:
75
+ """Deserialize WorkflowShape from JSON string during TOML loading.
76
+
77
+ When loading workflow metadata from TOML files, the workflow_shape field
78
+ is stored as a JSON string that needs to be converted back to a WorkflowShape
79
+ object. This validator handles the expected input formats:
80
+ - JSON strings (from TOML deserialization)
81
+ - WorkflowShape objects (from direct Python construction)
82
+ - None values (workflows without Start/End nodes)
83
+
84
+ If JSON deserialization fails, logs a warning and returns None for graceful
85
+ degradation, consistent with other metadata parsing failures in this codebase.
86
+ """
87
+ if value is None:
88
+ return None
89
+ if isinstance(value, WorkflowShape):
90
+ return value
91
+ if isinstance(value, str):
92
+ try:
93
+ data = json.loads(value)
94
+ return WorkflowShape(**data)
95
+ except (json.JSONDecodeError, TypeError, ValueError) as e:
96
+ logger.error("Failed to deserialize workflow_shape from JSON: %s", e)
97
+ return None
98
+ # Unexpected type - let Pydantic's normal validation handle it
99
+ return value
30
100
 
31
101
 
32
102
  class WorkflowRegistry(metaclass=SingletonMeta):
@@ -225,12 +225,10 @@ class GetFlowStateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
225
225
  Args:
226
226
  control_node: Name of the current control node (if any)
227
227
  resolving_node: Name of the node currently being resolved (if any)
228
- involved_nodes: Names of nodes that are queued to be executed or have been executed in the current run.
229
228
  """
230
229
 
231
230
  control_nodes: list[str] | None
232
231
  resolving_node: list[str] | None
233
- involved_nodes: list[str] | None
234
232
 
235
233
 
236
234
  @dataclass
@@ -348,6 +346,18 @@ class NodeFinishProcessEvent(ExecutionPayload):
348
346
  node_name: str
349
347
 
350
348
 
349
+ @dataclass
350
+ @PayloadRegistry.register
351
+ class InvolvedNodesEvent(ExecutionPayload):
352
+ """Event indicating which nodes are involved in the current execution.
353
+
354
+ For parallel resolution: Dynamic list based on DAG builder state
355
+ For control flow/sequential: All nodes when started, empty when complete
356
+ """
357
+
358
+ involved_nodes: list[str]
359
+
360
+
351
361
  @dataclass
352
362
  @PayloadRegistry.register
353
363
  class GriptapeEvent(ExecutionPayload):