griptape-nodes 0.47.0__tar.gz → 0.49.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 (127) hide show
  1. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/PKG-INFO +1 -1
  2. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/pyproject.toml +1 -2
  3. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/__init__.py +12 -33
  4. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/app/app.py +7 -6
  5. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +10 -2
  6. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/core_types.py +64 -0
  7. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/node_types.py +8 -12
  8. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/mcp_server/ws_request_manager.py +6 -6
  9. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +6 -6
  10. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +1 -4
  11. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +19 -14
  12. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/retained_mode.py +3 -3
  13. griptape_nodes-0.49.0/src/griptape_nodes/utils/version_utils.py +126 -0
  14. griptape_nodes-0.47.0/src/griptape_nodes/utils/version_utils.py +0 -51
  15. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/README.md +0 -0
  16. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/app/.python-version +0 -0
  17. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/app/__init__.py +0 -0
  18. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/app/api.py +0 -0
  19. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/app/watch.py +0 -0
  20. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
  21. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  22. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -0
  23. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  24. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/__init__.py +0 -0
  25. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
  26. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  27. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  28. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +0 -0
  29. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
  30. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
  31. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/connections.py +0 -0
  32. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/flow.py +0 -0
  33. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
  34. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/machines/__init__.py +0 -0
  35. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/machines/control_flow.py +0 -0
  36. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/machines/fsm.py +0 -0
  37. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/machines/node_resolution.py +0 -0
  38. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
  39. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/mcp_server/server.py +0 -0
  40. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/node_library/__init__.py +0 -0
  41. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
  42. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
  43. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/node_library/workflow_registry.py +0 -0
  44. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/py.typed +0 -0
  45. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
  46. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
  47. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
  48. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
  49. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  50. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/base_events.py +0 -0
  51. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
  52. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
  53. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
  54. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/execution_events.py +0 -0
  55. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/flow_events.py +0 -0
  56. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  57. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
  58. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
  59. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/node_events.py +0 -0
  60. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
  61. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
  62. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  63. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  64. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  65. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  66. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
  67. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
  68. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  69. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +0 -0
  70. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
  71. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  72. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  73. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  74. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  75. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  76. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  77. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +0 -0
  78. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  79. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  80. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  81. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  82. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  83. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  84. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  85. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  86. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  87. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  88. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  89. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  90. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +0 -0
  91. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  92. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  93. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +0 -0
  94. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  95. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  96. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/settings.py +0 -0
  97. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +0 -0
  98. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  99. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  101. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/__init__.py +0 -0
  103. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
  104. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/button.py +0 -0
  105. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/clamp.py +0 -0
  106. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/compare.py +0 -0
  107. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/compare_images.py +0 -0
  108. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
  109. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/minmax.py +0 -0
  110. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/options.py +0 -0
  111. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/slider.py +0 -0
  112. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
  113. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/traits/traits.json +0 -0
  114. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/updater/__init__.py +0 -0
  115. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/updater/__main__.py +0 -0
  116. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/utils/__init__.py +0 -0
  117. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
  118. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/utils/image_preview.py +0 -0
  119. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
  120. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
  121. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
  122. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  123. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  124. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  125. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
  126. {griptape_nodes-0.47.0 → griptape_nodes-0.49.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
  127. {griptape_nodes-0.47.0 → griptape_nodes-0.49.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.47.0
3
+ Version: 0.49.0
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.0
6
6
  Requires-Dist: pydantic>=2.10.6
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "griptape-nodes"
3
- version = "0.47.0"
3
+ version = "0.49.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0, <3.13"
@@ -77,7 +77,6 @@ ignore = [
77
77
  "E501", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/834
78
78
  "D100", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/835
79
79
  "BLE001", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/839
80
- "B026", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/836
81
80
  "SLF001", # TODO :https://github.com/griptape-ai/griptape-nodes/issues/838
82
81
  "SIM108", # Intentional
83
82
  "SIM110", # Intentional
@@ -32,7 +32,13 @@ with console.status("Loading Griptape Nodes...") as status:
32
32
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
33
33
  from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
34
34
  from griptape_nodes.utils.uv_utils import find_uv_bin
35
- from griptape_nodes.utils.version_utils import get_complete_version_string, get_current_version, get_install_source
35
+ from griptape_nodes.utils.version_utils import (
36
+ get_complete_version_string,
37
+ get_current_version,
38
+ get_install_source,
39
+ get_latest_version_git,
40
+ get_latest_version_pypi,
41
+ )
36
42
 
37
43
  CONFIG_DIR = xdg_config_home() / "griptape_nodes"
38
44
  DATA_DIR = xdg_data_home() / "griptape_nodes"
@@ -642,38 +648,11 @@ def _get_latest_version(package: str, install_source: str) -> str:
642
648
  str: Latest release tag (e.g., "v0.31.4")
643
649
  """
644
650
  if install_source == "pypi":
645
- update_url = PYPI_UPDATE_URL.format(package=package)
646
-
647
- with httpx.Client() as client:
648
- response = client.get(update_url)
649
- try:
650
- response.raise_for_status()
651
- data = response.json()
652
- return f"v{data['info']['version']}"
653
- except httpx.HTTPStatusError as e:
654
- console.print(f"[red]Error fetching latest version: {e}[/red]")
655
- return get_current_version()
656
- elif install_source == "git":
657
- # We only install auto updating from the 'latest' tag
658
- revision = LATEST_TAG
659
- update_url = GITHUB_UPDATE_URL.format(package=package, revision=revision)
660
-
661
- with httpx.Client() as client:
662
- response = client.get(update_url)
663
- try:
664
- response.raise_for_status()
665
- # Get the latest commit SHA for the tag, this effectively the latest version of the package
666
- data = response.json()
667
- if "object" in data and "sha" in data["object"]:
668
- return data["object"]["sha"][:7]
669
- # Should not happen, but if it does, return the current version
670
- return get_current_version()
671
- except httpx.HTTPStatusError as e:
672
- console.print(f"[red]Error fetching latest version: {e}[/red]")
673
- return get_current_version()
674
- else:
675
- # If the package is installed from a file, just return the current version since the user is likely managing it manually
676
- return get_current_version()
651
+ return get_latest_version_pypi(package, PYPI_UPDATE_URL)
652
+ if install_source == "git":
653
+ return get_latest_version_git(package, GITHUB_UPDATE_URL, LATEST_TAG)
654
+ # If the package is installed from a file, just return the current version since the user is likely managing it manually
655
+ return get_current_version()
677
656
 
678
657
 
679
658
  def _auto_update_self() -> None:
@@ -92,7 +92,6 @@ def start_app() -> None:
92
92
  Starts the event loop and listens for events from the Nodes API.
93
93
  """
94
94
  _init_event_listeners()
95
-
96
95
  # Listen for any signals to exit the app
97
96
  for sig in (signal.SIGINT, signal.SIGTERM):
98
97
  signal.signal(sig, lambda *_: sys.exit(0))
@@ -100,11 +99,9 @@ def start_app() -> None:
100
99
  api_key = _ensure_api_key()
101
100
  threading.Thread(target=mcp_server, args=(api_key,), daemon=True).start()
102
101
  threading.Thread(target=_listen_for_api_events, args=(api_key,), daemon=True).start()
103
-
104
102
  if STATIC_SERVER_ENABLED:
105
103
  static_dir = _build_static_dir()
106
104
  threading.Thread(target=start_api, args=(static_dir, event_queue), daemon=True).start()
107
-
108
105
  _process_event_queue()
109
106
 
110
107
 
@@ -118,7 +115,7 @@ def _ensure_api_key() -> str:
118
115
  "[code]gtn init --api-key <your key>[/code]\n"
119
116
  "[bold red]You can generate a new key from [/bold red][bold blue][link=https://nodes.griptape.ai]https://nodes.griptape.ai[/link][/bold blue]",
120
117
  ),
121
- title="🔑 Missing Nodes API Key",
118
+ title="[red]X[/red] Missing Nodes API Key",
122
119
  border_style="red",
123
120
  padding=(1, 4),
124
121
  )
@@ -263,8 +260,12 @@ def _process_event_queue() -> None:
263
260
  Event queue will be populated by background threads listening for events from the Nodes API.
264
261
  """
265
262
  # Wait for WebSocket connection to be established before processing events
266
- ws_ready_event.wait()
267
-
263
+ timed_out = ws_ready_event.wait(timeout=15)
264
+ if not timed_out:
265
+ console.print(
266
+ "[red] The connection to the websocket timed out. Please check your internet connection or the status of Griptape Nodes API.[/red]"
267
+ )
268
+ sys.exit(1)
268
269
  while True:
269
270
  event = event_queue.get(block=True)
270
271
  if isinstance(event, EventRequest):
@@ -92,7 +92,7 @@ class LocalWorkflowExecutor(WorkflowExecutor):
92
92
  node_name = result_event.payload.node_name
93
93
  flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(node_name)
94
94
  event_request = EventRequest(request=SingleExecutionStepRequest(flow_name=flow_name))
95
- GriptapeNodes.handle_request(event_request.request)
95
+ self.queue.put(event_request)
96
96
 
97
97
  elif type(result_event.payload).__name__ == "NodeFinishProcessEvent":
98
98
  event_log = f"NodeFinishProcessEvent: {result_event.payload}"
@@ -196,7 +196,13 @@ class LocalWorkflowExecutor(WorkflowExecutor):
196
196
  try:
197
197
  event = self.queue.get(block=True)
198
198
 
199
- if isinstance(event, ExecutionGriptapeNodeEvent):
199
+ if isinstance(event, EventRequest):
200
+ # Handle EventRequest objects by processing them through GriptapeNodes
201
+ request_payload = event.request
202
+ GriptapeNodes.handle_request(
203
+ request_payload, response_topic=event.response_topic, request_id=event.request_id
204
+ )
205
+ elif isinstance(event, ExecutionGriptapeNodeEvent):
200
206
  result_event = event.wrapped_event
201
207
 
202
208
  if type(result_event.payload).__name__ == "ControlFlowResolvedEvent":
@@ -208,6 +214,8 @@ class LocalWorkflowExecutor(WorkflowExecutor):
208
214
  is_flow_finished = True
209
215
  logger.error(msg)
210
216
  error = LocalExecutorError(msg)
217
+ else:
218
+ logger.info("Unknown event type encountered: %s", type(event))
211
219
 
212
220
  self.queue.task_done()
213
221
 
@@ -1450,6 +1450,70 @@ class ParameterList(ParameterContainer):
1450
1450
  self.remove_child(child)
1451
1451
  del child
1452
1452
 
1453
+ # --- Convenience methods for stable list management ---
1454
+ def get_child_parameters(self) -> list[Parameter]:
1455
+ """Return direct child parameters only, in order of appearance."""
1456
+ return self.find_elements_by_type(element_type=Parameter, find_recursively=False)
1457
+
1458
+ def append_child_parameter(self, display_name: str | None = None) -> Parameter:
1459
+ """Append one child parameter and optionally set a display name.
1460
+
1461
+ This preserves existing children and adds a new one at the end.
1462
+ """
1463
+ child = self.add_child_parameter()
1464
+ if display_name is not None:
1465
+ ui_opts = child.ui_options or {}
1466
+ ui_opts["display_name"] = display_name
1467
+ child.ui_options = ui_opts
1468
+ return child
1469
+
1470
+ def remove_last_child_parameter(self) -> None:
1471
+ """Remove the last child parameter if one exists.
1472
+
1473
+ This removes from the end to preserve earlier children and their connections.
1474
+ """
1475
+ children = self.get_child_parameters()
1476
+ if children:
1477
+ last = children[-1]
1478
+ self.remove_child(last)
1479
+ del last
1480
+
1481
+ def ensure_length(self, desired_count: int, display_name_prefix: str | None = None) -> None:
1482
+ """Grow or shrink the list to the desired length while preserving existing items.
1483
+
1484
+ - If increasing, appends new children to the end.
1485
+ - If decreasing, removes children from the end.
1486
+ - Optionally sets display names like "{prefix} 1", "{prefix} 2", ...
1487
+ """
1488
+ if desired_count is None:
1489
+ return
1490
+ try:
1491
+ desired_count = int(desired_count)
1492
+ except Exception:
1493
+ desired_count = 0
1494
+ desired_count = max(desired_count, 0)
1495
+
1496
+ current_children = self.get_child_parameters()
1497
+ current_len = len(current_children)
1498
+
1499
+ # Grow
1500
+ if current_len < desired_count:
1501
+ for index in range(current_len, desired_count):
1502
+ name = f"{display_name_prefix} {index + 1}" if display_name_prefix else None
1503
+ self.append_child_parameter(display_name=name)
1504
+
1505
+ # Shrink
1506
+ elif current_len > desired_count:
1507
+ for _ in range(current_len - desired_count):
1508
+ self.remove_last_child_parameter()
1509
+
1510
+ # Optionally re-apply display names to existing children to keep indices tidy
1511
+ if display_name_prefix:
1512
+ for index, child in enumerate(self.get_child_parameters()):
1513
+ ui_opts = child.ui_options or {}
1514
+ ui_opts["display_name"] = f"{display_name_prefix} {index + 1}"
1515
+ child.ui_options = ui_opts
1516
+
1453
1517
  def add_child(self, child: BaseNodeElement) -> None:
1454
1518
  """Override to mark parent node as unresolved when children are added.
1455
1519
 
@@ -849,21 +849,11 @@ class BaseNode(ABC):
849
849
  # Create event data using the parameter's to_event method
850
850
  if remove:
851
851
  # Import logger here to avoid circular dependency
852
- from griptape_nodes.retained_mode.griptape_nodes import logger
853
-
854
- logger.info(
855
- f"RemoveElementEvent: Emitting parameter lifecycle event for element {parameter.name} on node {self.name}"
856
- )
857
852
  event = ExecutionGriptapeNodeEvent(
858
853
  wrapped_event=ExecutionEvent(payload=RemoveElementEvent(element_id=parameter.element_id))
859
854
  )
860
855
  else:
861
856
  event_data = parameter.to_event(self)
862
- from griptape_nodes.retained_mode.griptape_nodes import logger
863
-
864
- logger.info(
865
- f"AlterElementEvent: Emitting parameter lifecycle eventfor element {parameter.name} on node {self.name}"
866
- )
867
857
  # Publish the event
868
858
  event = ExecutionGriptapeNodeEvent(
869
859
  wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
@@ -1063,11 +1053,17 @@ class EndNode(BaseNode):
1063
1053
 
1064
1054
 
1065
1055
  class StartLoopNode(BaseNode):
1066
- finished: bool
1067
- current_index: int
1068
1056
  end_node: EndLoopNode | None = None
1069
1057
  """Creating class for Start Loop Node in order to implement loop functionality in execution."""
1070
1058
 
1059
+ @abstractmethod
1060
+ def is_loop_finished(self) -> bool:
1061
+ """Return True if the loop has finished executing.
1062
+
1063
+ This method must be implemented by subclasses to define when
1064
+ the loop should terminate.
1065
+ """
1066
+
1071
1067
 
1072
1068
  class EndLoopNode(BaseNode):
1073
1069
  start_node: StartLoopNode | None = None
@@ -41,7 +41,7 @@ class WebSocketConnectionManager:
41
41
  try:
42
42
  message = json.dumps(data)
43
43
  await self.websocket.send(message)
44
- logger.debug("📤 Sent message: %s", message)
44
+ logger.debug("Sent message: %s", message)
45
45
  except Exception as e:
46
46
  logger.error("Failed to send message: %s", e)
47
47
  raise
@@ -157,7 +157,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
157
157
 
158
158
  except Exception as e:
159
159
  self.connection_manager.connected = False
160
- logger.error("🔴 WebSocket connection failed: %s", str(e))
160
+ logger.error("[red]X[/red] WebSocket connection failed: %s", str(e))
161
161
  msg = f"Failed to connect to WebSocket: {e!s}"
162
162
  raise ConnectionError(msg) from e
163
163
 
@@ -192,7 +192,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
192
192
  """Send an event to the API without waiting for a response."""
193
193
  from griptape_nodes.app.app import _determine_request_topic
194
194
 
195
- logger.debug("📝 Creating Event: %s - %s", request_type, json.dumps(payload))
195
+ logger.debug("Creating Event: %s - %s", request_type, json.dumps(payload))
196
196
 
197
197
  data = {"event_type": "EventRequest", "request_type": request_type, "request": payload}
198
198
  topic = _determine_request_topic()
@@ -231,7 +231,7 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
231
231
  def success_handler(response: Any, _: Any) -> None:
232
232
  if not response_future.done():
233
233
  result = response.get("payload", {}).get("result", "Success")
234
- logger.debug(" Request succeeded: %s", result)
234
+ logger.debug("[green]OK[/green] Request succeeded: %s", result)
235
235
  response_future.set_result(result)
236
236
 
237
237
  def failure_handler(response: Any, _: Any) -> None:
@@ -239,14 +239,14 @@ class AsyncRequestManager(Generic[T]): # noqa: UP046
239
239
  error = (
240
240
  response.get("payload", {}).get("result", {}).get("exception", "Unknown error") or "Unknown error"
241
241
  )
242
- logger.error(" Request failed: %s", error)
242
+ logger.error("[red]X[/red] Request failed: %s", error)
243
243
  response_future.set_exception(Exception(error))
244
244
 
245
245
  # Generate request ID and subscribe
246
246
  request_id = self.connection_manager.subscribe_to_request_event(success_handler, failure_handler)
247
247
  payload["request_id"] = request_id
248
248
 
249
- logger.debug("🚀 Request (%s): %s %s", request_id, request_type, json.dumps(payload))
249
+ logger.debug("Request (%s): %s %s", request_id, request_type, json.dumps(payload))
250
250
 
251
251
  try:
252
252
  # Send the event
@@ -240,10 +240,10 @@ class LibraryManager:
240
240
 
241
241
  # Status emojis mapping
242
242
  status_emoji = {
243
- LibraryStatus.GOOD: "",
244
- LibraryStatus.FLAWED: "🟡",
245
- LibraryStatus.UNUSABLE: "",
246
- LibraryStatus.MISSING: "",
243
+ LibraryStatus.GOOD: "[green]OK[/green]",
244
+ LibraryStatus.FLAWED: "[yellow]![/yellow]",
245
+ LibraryStatus.UNUSABLE: "[red]X[/red]",
246
+ LibraryStatus.MISSING: "[red]?[/red]",
247
247
  }
248
248
 
249
249
  # Add rows for each library info
@@ -256,7 +256,7 @@ class LibraryManager:
256
256
  # Library name column with emoji based on status
257
257
  emoji = status_emoji.get(lib_info.status, "ERROR: Unknown/Unexpected Library Status")
258
258
  name = lib_info.library_name if lib_info.library_name else "*UNKNOWN*"
259
- library_name = f"{emoji} {name}"
259
+ library_name = f"{emoji} - {name}"
260
260
 
261
261
  library_version = lib_info.library_version
262
262
  if library_version:
@@ -1596,7 +1596,7 @@ class LibraryManager:
1596
1596
  f"[bold blue]Return to: [link={nodes_app_url}]{nodes_app_url}[/link] to access the Workflow Editor[/bold blue]",
1597
1597
  vertical="middle",
1598
1598
  ),
1599
- title="🚀 Griptape Nodes Engine Started",
1599
+ title="Griptape Nodes Engine Started",
1600
1600
  subtitle=f"[green]{engine_version}{session_info}[/green]",
1601
1601
  border_style="green",
1602
1602
  padding=(1, 4),
@@ -167,10 +167,7 @@ class StaticFilesManager:
167
167
 
168
168
  try:
169
169
  response = httpx.request(
170
- response["method"],
171
- response["url"],
172
- content=data,
173
- headers=response["headers"],
170
+ response["method"], response["url"], content=data, headers=response["headers"], timeout=60
174
171
  )
175
172
  response.raise_for_status()
176
173
  except httpx.HTTPStatusError as e:
@@ -389,19 +389,19 @@ class WorkflowManager:
389
389
 
390
390
  # Status emojis mapping
391
391
  status_emoji = {
392
- self.WorkflowStatus.GOOD: "",
393
- self.WorkflowStatus.FLAWED: "🟡",
394
- self.WorkflowStatus.UNUSABLE: "",
395
- self.WorkflowStatus.MISSING: "",
392
+ self.WorkflowStatus.GOOD: "[green]OK[/green]",
393
+ self.WorkflowStatus.FLAWED: "[yellow]![/yellow]",
394
+ self.WorkflowStatus.UNUSABLE: "[red]X[/red]",
395
+ self.WorkflowStatus.MISSING: "[red]?[/red]",
396
396
  }
397
397
 
398
398
  dependency_status_emoji = {
399
- self.WorkflowDependencyStatus.PERFECT: "",
400
- self.WorkflowDependencyStatus.GOOD: "👌",
401
- self.WorkflowDependencyStatus.CAUTION: "🟡",
402
- self.WorkflowDependencyStatus.BAD: "",
403
- self.WorkflowDependencyStatus.MISSING: "",
404
- self.WorkflowDependencyStatus.UNKNOWN: "",
399
+ self.WorkflowDependencyStatus.PERFECT: "[green]OK[/green]",
400
+ self.WorkflowDependencyStatus.GOOD: "[green]GOOD[/green]",
401
+ self.WorkflowDependencyStatus.CAUTION: "[yellow]CAUTION[/yellow]",
402
+ self.WorkflowDependencyStatus.BAD: "[red]BAD[/red]",
403
+ self.WorkflowDependencyStatus.MISSING: "[red]MISSING[/red]",
404
+ self.WorkflowDependencyStatus.UNKNOWN: "[red]UNKNOWN[/red]",
405
405
  }
406
406
 
407
407
  # Add rows for each workflow info
@@ -414,7 +414,7 @@ class WorkflowManager:
414
414
  # Workflow name column with emoji based on status
415
415
  emoji = status_emoji.get(wf_info.status, "ERR: Unknown/Unexpected Workflow Status")
416
416
  name = wf_info.workflow_name if wf_info.workflow_name else "*UNKNOWN*"
417
- workflow_name = f"{emoji} {name}"
417
+ workflow_name = f"{emoji} - {name}"
418
418
 
419
419
  # Problems column - format with numbers if there's more than one
420
420
  problems = "\n".join(wf_info.problems) if wf_info.problems else "No problems detected."
@@ -423,11 +423,11 @@ class WorkflowManager:
423
423
  if wf_info.status == self.WorkflowStatus.MISSING or (
424
424
  wf_info.status == self.WorkflowStatus.UNUSABLE and not wf_info.workflow_dependencies
425
425
  ):
426
- dependencies = " UNKNOWN"
426
+ dependencies = "[red]?[/red] UNKNOWN"
427
427
  else:
428
428
  dependencies = (
429
429
  "\n".join(
430
- f"{dependency_status_emoji.get(dep.status, '?')} {dep.library_name} ({dep.version_requested}): {dep.status.value}"
430
+ f"{dependency_status_emoji.get(dep.status, '?')} - {dep.library_name} ({dep.version_requested}): {dep.status.value}"
431
431
  for dep in wf_info.workflow_dependencies
432
432
  )
433
433
  if wf_info.workflow_dependencies
@@ -886,7 +886,12 @@ class WorkflowManager:
886
886
  # See how our desired version compares against the actual library we (may) have.
887
887
  # See if the library exists.
888
888
  library_metadata_request = GetLibraryMetadataRequest(library=library_name)
889
- library_metadata_result = GriptapeNodes.handle_request(library_metadata_request)
889
+ # NOTE: Per https://github.com/griptape-ai/griptape-vsl-gui/issues/1123, we
890
+ # generate a FLOOD of error messages here that can swamp the GUI. We'll call
891
+ # directly instead of the usual handle_request() path so we don't generate those.
892
+ library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
893
+ library_metadata_request
894
+ )
890
895
  if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
891
896
  # Metadata failed to be found.
892
897
  had_critical_error = True
@@ -125,10 +125,10 @@ def command_arg_handler(node_param_split_func: Callable) -> Callable:
125
125
  instance_or_cls,
126
126
  node=node,
127
127
  param=param,
128
- *args_to_process,
128
+ *args_to_process, # noqa: B026
129
129
  **cleaned_kwargs,
130
130
  )
131
- return func(node=node, param=param, *args_to_process, **cleaned_kwargs)
131
+ return func(node=node, param=param, *args_to_process, **cleaned_kwargs) # noqa: B026
132
132
 
133
133
  return wrapper
134
134
 
@@ -1460,7 +1460,7 @@ class RetainedMode:
1460
1460
  if command_name:
1461
1461
  func = getattr(cls, command_name, None)
1462
1462
  if not func or not callable(func):
1463
- return f" No such command: {command_name}"
1463
+ return f"[red]X[/red] No such command: {command_name}"
1464
1464
 
1465
1465
  doc = inspect.getdoc(func) or "No documentation available."
1466
1466
  sig_lines = _fancy_signature(func)
@@ -0,0 +1,126 @@
1
+ """Version utilities for Griptape Nodes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.metadata
6
+ import json
7
+ from typing import Literal
8
+
9
+ import httpx
10
+ from rich.console import Console
11
+
12
+ console = Console()
13
+
14
+ engine_version = importlib.metadata.version("griptape_nodes")
15
+
16
+
17
+ def get_current_version() -> str:
18
+ """Returns the current version of the Griptape Nodes package."""
19
+ return f"v{engine_version}"
20
+
21
+
22
+ def get_install_source() -> tuple[Literal["git", "file", "pypi"], str | None]:
23
+ """Determines the install source of the Griptape Nodes package.
24
+
25
+ Returns:
26
+ tuple: A tuple containing the install source and commit ID (if applicable).
27
+ """
28
+ dist = importlib.metadata.distribution("griptape_nodes")
29
+ direct_url_text = dist.read_text("direct_url.json")
30
+ # installing from pypi doesn't have a direct_url.json file
31
+ if direct_url_text is None:
32
+ return "pypi", None
33
+
34
+ direct_url_info = json.loads(direct_url_text)
35
+ url = direct_url_info.get("url")
36
+ if url and url.startswith("file://"):
37
+ return "file", None
38
+ if "vcs_info" in direct_url_info:
39
+ return "git", direct_url_info["vcs_info"].get("commit_id")[:7]
40
+ # Fall back to pypi if no other source is found
41
+ return "pypi", None
42
+
43
+
44
+ def get_complete_version_string() -> str:
45
+ """Returns the complete version string including install source and commit ID.
46
+
47
+ Format: v1.2.3 (source) or v1.2.3 (source - commit_id)
48
+
49
+ Returns:
50
+ Complete version string with source and commit info.
51
+ """
52
+ version = get_current_version()
53
+ source, commit_id = get_install_source()
54
+ if commit_id is None:
55
+ return f"{version} ({source})"
56
+ return f"{version} ({source} - {commit_id})"
57
+
58
+
59
+ def get_latest_version_pypi(package: str, pypi_url: str) -> str:
60
+ """Gets the latest version from PyPI.
61
+
62
+ Args:
63
+ package: The name of the package to fetch the latest version for.
64
+ pypi_url: The PyPI URL template to use.
65
+
66
+ Returns:
67
+ str: Latest release tag (e.g., "v0.31.4") or current version if fetch fails.
68
+ """
69
+ version = get_current_version()
70
+ update_url = pypi_url.format(package=package)
71
+
72
+ with httpx.Client(timeout=30.0) as client:
73
+ try:
74
+ response = client.get(update_url)
75
+ except httpx.RequestError as e:
76
+ console.print(f"[red]Error fetching latest version due to error: [/red][cyan]{e}[/cyan]")
77
+ console.print(
78
+ f"[red]Please check your internet connection or if you can access the following update url: [/red] [cyan]{update_url}[/cyan]"
79
+ )
80
+ return version
81
+
82
+ try:
83
+ response.raise_for_status()
84
+ data = response.json()
85
+ if "info" in data and "version" in data["info"]:
86
+ version = f"v{data['info']['version']}"
87
+ except httpx.HTTPStatusError as e:
88
+ console.print(f"[red]Error fetching latest version: {e}[/red]")
89
+
90
+ return version
91
+
92
+
93
+ def get_latest_version_git(package: str, github_url: str, latest_tag: str) -> str:
94
+ """Gets the latest version from Git.
95
+
96
+ Args:
97
+ package: The name of the package to fetch the latest version for.
98
+ github_url: The GitHub URL template to use.
99
+ latest_tag: The tag to fetch (usually 'latest').
100
+
101
+ Returns:
102
+ str: Latest commit SHA (first 7 characters) or current version if fetch fails.
103
+ """
104
+ version = get_current_version()
105
+ revision = latest_tag
106
+ update_url = github_url.format(package=package, revision=revision)
107
+
108
+ with httpx.Client(timeout=30.0) as client:
109
+ try:
110
+ response = client.get(update_url)
111
+ except httpx.RequestError as e:
112
+ console.print(f"[red]Error fetching latest version due to error: [/red][cyan]{e}[/cyan]")
113
+ console.print(
114
+ f"[red]Please check your internet connection or if you can access the following update url: [/red] [cyan]{update_url}[/cyan]"
115
+ )
116
+ return version
117
+
118
+ try:
119
+ response.raise_for_status()
120
+ data = response.json()
121
+ if "object" in data and "sha" in data["object"]:
122
+ version = data["object"]["sha"][:7]
123
+ except httpx.HTTPStatusError as e:
124
+ console.print(f"[red]Error fetching latest version: {e}[/red]")
125
+
126
+ return version
@@ -1,51 +0,0 @@
1
- """Version utilities for Griptape Nodes."""
2
-
3
- from __future__ import annotations
4
-
5
- import importlib.metadata
6
- import json
7
- from typing import Literal
8
-
9
- engine_version = importlib.metadata.version("griptape_nodes")
10
-
11
-
12
- def get_current_version() -> str:
13
- """Returns the current version of the Griptape Nodes package."""
14
- return f"v{engine_version}"
15
-
16
-
17
- def get_install_source() -> tuple[Literal["git", "file", "pypi"], str | None]:
18
- """Determines the install source of the Griptape Nodes package.
19
-
20
- Returns:
21
- tuple: A tuple containing the install source and commit ID (if applicable).
22
- """
23
- dist = importlib.metadata.distribution("griptape_nodes")
24
- direct_url_text = dist.read_text("direct_url.json")
25
- # installing from pypi doesn't have a direct_url.json file
26
- if direct_url_text is None:
27
- return "pypi", None
28
-
29
- direct_url_info = json.loads(direct_url_text)
30
- url = direct_url_info.get("url")
31
- if url and url.startswith("file://"):
32
- return "file", None
33
- if "vcs_info" in direct_url_info:
34
- return "git", direct_url_info["vcs_info"].get("commit_id")[:7]
35
- # Fall back to pypi if no other source is found
36
- return "pypi", None
37
-
38
-
39
- def get_complete_version_string() -> str:
40
- """Returns the complete version string including install source and commit ID.
41
-
42
- Format: v1.2.3 (source) or v1.2.3 (source - commit_id)
43
-
44
- Returns:
45
- Complete version string with source and commit info.
46
- """
47
- version = get_current_version()
48
- source, commit_id = get_install_source()
49
- if commit_id is None:
50
- return f"{version} ({source})"
51
- return f"{version} ({source} - {commit_id})"