crewai-cli 1.14.8a3__tar.gz → 1.14.8a3.dev20260624__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 (151) hide show
  1. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/PKG-INFO +2 -2
  2. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/pyproject.toml +1 -1
  3. crewai_cli-1.14.8a3.dev20260624/src/crewai_cli/__init__.py +1 -0
  4. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/crew_run_tui.py +238 -2
  5. crewai_cli-1.14.8a3.dev20260624/src/crewai_cli/kickoff_flow.py +105 -0
  6. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/run_crew.py +10 -0
  7. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/run_declarative_flow.py +68 -8
  8. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_crew_run_tui.py +46 -0
  9. crewai_cli-1.14.8a3.dev20260624/tests/test_flow_commands.py +248 -0
  10. crewai_cli-1.14.8a3.dev20260624/tests/test_kickoff_flow.py +63 -0
  11. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_run_crew.py +46 -2
  12. crewai_cli-1.14.8a3/src/crewai_cli/__init__.py +0 -1
  13. crewai_cli-1.14.8a3/tests/test_flow_commands.py +0 -117
  14. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/.gitignore +0 -0
  15. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/README.md +0 -0
  16. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/add_crew_to_flow.py +0 -0
  17. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/__init__.py +0 -0
  18. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/constants.py +0 -0
  19. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/main.py +0 -0
  20. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/__init__.py +0 -0
  21. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/auth0.py +0 -0
  22. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/base_provider.py +0 -0
  23. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/entra_id.py +0 -0
  24. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/keycloak.py +0 -0
  25. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/okta.py +0 -0
  26. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/providers/workos.py +0 -0
  27. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/token.py +0 -0
  28. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/authentication/utils.py +0 -0
  29. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/checkpoint_cli.py +0 -0
  30. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/checkpoint_tui.py +0 -0
  31. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/cli.py +0 -0
  32. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/command.py +0 -0
  33. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/config.py +0 -0
  34. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/constants.py +0 -0
  35. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/create_crew.py +0 -0
  36. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/create_flow.py +0 -0
  37. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/create_json_crew.py +0 -0
  38. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/crew_chat.py +0 -0
  39. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/deploy/__init__.py +0 -0
  40. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/deploy/archive.py +0 -0
  41. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/deploy/main.py +0 -0
  42. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/deploy/validate.py +0 -0
  43. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/enterprise/__init__.py +0 -0
  44. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/enterprise/main.py +0 -0
  45. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/evaluate_crew.py +0 -0
  46. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/experimental/__init__.py +0 -0
  47. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/experimental/skills/__init__.py +0 -0
  48. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/experimental/skills/main.py +0 -0
  49. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/git.py +0 -0
  50. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/install_crew.py +0 -0
  51. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/memory_tui.py +0 -0
  52. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/organization/__init__.py +0 -0
  53. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/organization/main.py +0 -0
  54. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/plot_flow.py +0 -0
  55. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/plus_api.py +0 -0
  56. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/provider.py +0 -0
  57. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/py.typed +0 -0
  58. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/remote_template/__init__.py +0 -0
  59. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/remote_template/main.py +0 -0
  60. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/replay_from_task.py +0 -0
  61. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/reset_memories_command.py +0 -0
  62. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/settings/__init__.py +0 -0
  63. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/settings/main.py +0 -0
  64. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/shared/__init__.py +0 -0
  65. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/shared/token_manager.py +0 -0
  66. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/task_outputs.py +0 -0
  67. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/AGENTS.md +0 -0
  68. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/__init__.py +0 -0
  69. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/.gitignore +0 -0
  70. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/README.md +0 -0
  71. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/__init__.py +0 -0
  72. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/config/agents.yaml +0 -0
  73. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/config/tasks.yaml +0 -0
  74. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/crew.py +0 -0
  75. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/knowledge/user_preference.txt +0 -0
  76. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/main.py +0 -0
  77. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/pyproject.toml +0 -0
  78. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/skills/.gitkeep +0 -0
  79. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/tools/__init__.py +0 -0
  80. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/crew/tools/custom_tool.py +0 -0
  81. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/declarative_flow/.gitignore +0 -0
  82. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/declarative_flow/README.md +0 -0
  83. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/declarative_flow/flow.yaml +0 -0
  84. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/declarative_flow/pyproject.toml +0 -0
  85. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/.gitignore +0 -0
  86. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/README.md +0 -0
  87. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/__init__.py +0 -0
  88. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/crews/content_crew/config/agents.yaml +0 -0
  89. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/crews/content_crew/config/tasks.yaml +0 -0
  90. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/crews/content_crew/content_crew.py +0 -0
  91. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/main.py +0 -0
  92. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/pyproject.toml +0 -0
  93. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/skills/.gitkeep +0 -0
  94. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/tools/__init__.py +0 -0
  95. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/flow/tools/custom_tool.py +0 -0
  96. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/tool/.gitignore +0 -0
  97. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/tool/README.md +0 -0
  98. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/tool/pyproject.toml +0 -0
  99. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/tool/src/{{folder_name}}/__init__.py +0 -0
  100. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/templates/tool/src/{{folder_name}}/tool.py +0 -0
  101. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/tools/__init__.py +0 -0
  102. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/tools/main.py +0 -0
  103. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/train_crew.py +0 -0
  104. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/triggers/__init__.py +0 -0
  105. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/triggers/main.py +0 -0
  106. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/tui_picker.py +0 -0
  107. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/update_crew.py +0 -0
  108. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/user_data.py +0 -0
  109. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/utils.py +0 -0
  110. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/src/crewai_cli/version.py +0 -0
  111. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/__init__.py +0 -0
  112. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/__init__.py +0 -0
  113. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/__init__.py +0 -0
  114. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/test_auth0.py +0 -0
  115. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/test_entra_id.py +0 -0
  116. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/test_keycloak.py +0 -0
  117. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/test_okta.py +0 -0
  118. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/providers/test_workos.py +0 -0
  119. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/test_auth_main.py +0 -0
  120. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/authentication/test_utils.py +0 -0
  121. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/deploy/__init__.py +0 -0
  122. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/deploy/test_archive.py +0 -0
  123. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/deploy/test_deploy_main.py +0 -0
  124. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/deploy/test_validate.py +0 -0
  125. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/enterprise/__init__.py +0 -0
  126. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/enterprise/test_main.py +0 -0
  127. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/experimental/__init__.py +0 -0
  128. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/experimental/skills/__init__.py +0 -0
  129. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/experimental/skills/test_main.py +0 -0
  130. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/organization/__init__.py +0 -0
  131. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/organization/test_main.py +0 -0
  132. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/skills/__init__.py +0 -0
  133. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_cli.py +0 -0
  134. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_click_compatibility.py +0 -0
  135. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_config.py +0 -0
  136. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_constants.py +0 -0
  137. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_create_crew.py +0 -0
  138. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_create_flow.py +0 -0
  139. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_crew_test.py +0 -0
  140. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_git.py +0 -0
  141. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_install_crew.py +0 -0
  142. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_plus_api.py +0 -0
  143. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_run_declarative_flow.py +0 -0
  144. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_settings_command.py +0 -0
  145. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_token_manager.py +0 -0
  146. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_train_crew.py +0 -0
  147. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_utils.py +0 -0
  148. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/test_version.py +0 -0
  149. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/tools/__init__.py +0 -0
  150. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/tools/test_main.py +0 -0
  151. {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a3.dev20260624}/tests/triggers/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crewai-cli
3
- Version: 1.14.8a3
3
+ Version: 1.14.8a3.dev20260624
4
4
  Summary: CLI for CrewAI — scaffold, run, deploy and manage AI agent crews.
5
5
  Project-URL: Homepage, https://crewai.com
6
6
  Project-URL: Documentation, https://docs.crewai.com
@@ -10,7 +10,7 @@ Requires-Python: <3.14,>=3.10
10
10
  Requires-Dist: appdirs~=1.4.4
11
11
  Requires-Dist: certifi
12
12
  Requires-Dist: click<9,>=8.1.7
13
- Requires-Dist: crewai-core==1.14.8a3
13
+ Requires-Dist: crewai-core==1.14.8a3.dev20260624
14
14
  Requires-Dist: cryptography>=42.0
15
15
  Requires-Dist: httpx~=0.28.1
16
16
  Requires-Dist: packaging>=23.0
@@ -8,7 +8,7 @@ authors = [
8
8
  ]
9
9
  requires-python = ">=3.10, <3.14"
10
10
  dependencies = [
11
- "crewai-core==1.14.8a3",
11
+ "crewai-core==1.14.8a3.dev20260624",
12
12
  "click>=8.1.7,<9",
13
13
  "pydantic>=2.11.9,<2.13",
14
14
  "pydantic-settings~=2.10.1",
@@ -0,0 +1 @@
1
+ __version__ = "1.14.8a3.dev20260624"
@@ -17,7 +17,7 @@ from textual.binding import Binding, BindingType
17
17
  from textual.containers import Horizontal, Vertical, VerticalScroll
18
18
  from textual.css.query import NoMatches
19
19
  from textual.screen import ModalScreen
20
- from textual.widgets import Button, Footer, Header, Static
20
+ from textual.widgets import Button, Footer, Header, Input, Static
21
21
 
22
22
 
23
23
  _SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
@@ -382,6 +382,18 @@ Screen {
382
382
  height: auto;
383
383
  }
384
384
 
385
+ #conversation-input {
386
+ display: none;
387
+ height: 3;
388
+ border-top: hkey #333333;
389
+ background: #1c1c1c;
390
+ color: #e0e0e0;
391
+ }
392
+
393
+ #conversation-input:focus {
394
+ border-top: hkey #1F7982;
395
+ }
396
+
385
397
  Header {
386
398
  background: #1c1c1c;
387
399
  color: #FF5A50;
@@ -483,6 +495,7 @@ FooterKey .footer-key--key {
483
495
  total_tasks: int = 0,
484
496
  agent_names: list[str] | None = None,
485
497
  task_names: list[str] | None = None,
498
+ conversational: bool = False,
486
499
  ):
487
500
  super().__init__()
488
501
  self.title = f"CrewAI — {crew_name}"
@@ -544,6 +557,13 @@ FooterKey .footer-key--key {
544
557
  self._event_handlers: list[tuple[type, Any]] = []
545
558
 
546
559
  self._crew: Any = None
560
+ self._flow: Any = None
561
+ self._is_conversational = conversational
562
+ self._conversation_messages: list[tuple[str, str]] = []
563
+ self._conversation_turns = 0
564
+ self._conversation_turn_in_progress = False
565
+ self._conversation_previous_defer_trace_finalization: bool | None = None
566
+ self._conversation_exit_commands = {"exit", "quit"}
547
567
  self._default_inputs: dict[str, Any] | None = None
548
568
  self._crew_result: Any = None
549
569
  self._crew_json_path: Any = None
@@ -566,6 +586,10 @@ FooterKey .footer-key--key {
566
586
  yield Static(id="task-header")
567
587
  with VerticalScroll(id="scroll-area"):
568
588
  yield Static(id="main-content")
589
+ yield Input(
590
+ placeholder="Message the flow...",
591
+ id="conversation-input",
592
+ )
569
593
  with VerticalScroll(id="log-panel"):
570
594
  yield Static(id="log-content")
571
595
  yield Footer()
@@ -574,7 +598,9 @@ FooterKey .footer-key--key {
574
598
  self._start_time = time.time()
575
599
  self._subscribe()
576
600
  self._tick_timer = self.set_interval(1 / 8, self._tick)
577
- if self._crew:
601
+ if self._is_conversational and self._flow:
602
+ self._start_conversational_session()
603
+ elif self._crew:
578
604
  self._run_crew_worker()
579
605
  elif self._crew_json_path:
580
606
  self._load_and_run_worker()
@@ -725,6 +751,140 @@ FooterKey .footer-key--key {
725
751
  self._tick_timer = self.set_interval(1 / 2, self._tick)
726
752
  self._unsubscribe_if_no_running_memory_save(wait_for_queued=True)
727
753
 
754
+ # ── Conversational flow execution ───────────────────────
755
+
756
+ def _start_conversational_session(self) -> None:
757
+ from crewai.events.listeners.tracing.utils import (
758
+ set_suppress_tracing_messages,
759
+ set_tui_mode,
760
+ )
761
+
762
+ set_tui_mode(True)
763
+ set_suppress_tracing_messages(True)
764
+ with self._lock:
765
+ self._status = "chatting"
766
+ self._current_step = None
767
+ self._elapsed_frozen = None
768
+ self._conversation_previous_defer_trace_finalization = getattr(
769
+ self._flow, "defer_trace_finalization", False
770
+ )
771
+ self._flow.defer_trace_finalization = True
772
+
773
+ try:
774
+ input_widget = self.query_one("#conversation-input", Input)
775
+ input_widget.display = True
776
+ input_widget.focus()
777
+ except Exception: # noqa: S110
778
+ pass
779
+
780
+ def _finalize_conversational_session(self) -> None:
781
+ if not (self._is_conversational and self._flow):
782
+ return
783
+ try:
784
+ self._flow.finalize_session_traces()
785
+ except Exception: # noqa: S110
786
+ pass
787
+ previous = self._conversation_previous_defer_trace_finalization
788
+ if previous is not None:
789
+ try:
790
+ self._flow.defer_trace_finalization = previous
791
+ except Exception: # noqa: S110
792
+ pass
793
+
794
+ def on_input_submitted(self, event: Input.Submitted) -> None:
795
+ if event.input.id != "conversation-input":
796
+ return
797
+ if not self._is_conversational:
798
+ return
799
+
800
+ message = event.value.strip()
801
+ event.input.value = ""
802
+ if not message:
803
+ return
804
+ if message.lower() in self._conversation_exit_commands:
805
+ self._finalize_conversational_session()
806
+ self._unsubscribe()
807
+ self.exit(self._crew_result)
808
+ return
809
+ if self._conversation_turn_in_progress:
810
+ return
811
+
812
+ with self._lock:
813
+ self._conversation_messages.append(("user", message))
814
+ self._conversation_turn_in_progress = True
815
+ self._conversation_turns += 1
816
+ self._status = "working"
817
+ self._current_step = ("yellow", "Thinking…", "")
818
+ self._is_streaming = False
819
+ self._streaming_text = ""
820
+ self._task_full_output = ""
821
+ self._current_llm_text = ""
822
+
823
+ event.input.disabled = True
824
+ self._run_conversation_turn_worker(message)
825
+
826
+ @work(thread=True, exclusive=True, group="conversation")
827
+ def _run_conversation_turn_worker(self, message: str) -> None:
828
+ from crewai.events.listeners.tracing.utils import (
829
+ set_suppress_tracing_messages,
830
+ set_tui_mode,
831
+ )
832
+
833
+ set_tui_mode(True)
834
+ set_suppress_tracing_messages(True)
835
+ try:
836
+ result = self._flow.handle_turn(message)
837
+ if hasattr(result, "get_full_text") and hasattr(result, "result"):
838
+ for _chunk in result:
839
+ pass
840
+ result = result.result
841
+ self.call_from_thread(self._on_conversation_turn_done, result)
842
+ except Exception as e:
843
+ self.call_from_thread(self._on_conversation_turn_failed, str(e))
844
+
845
+ def _on_conversation_turn_done(self, result: Any) -> None:
846
+ with self._lock:
847
+ output = self._stringify_output(result)
848
+ self._conversation_messages.append(("assistant", output))
849
+ self._crew_result = result
850
+ self._conversation_turn_in_progress = False
851
+ self._status = "chatting"
852
+ self._is_streaming = False
853
+ self._streaming_text = ""
854
+ self._current_step = None
855
+ self._enable_conversation_input()
856
+ self._tick()
857
+ self._scroll_to_result()
858
+
859
+ def _on_conversation_turn_failed(self, error: str) -> None:
860
+ with self._lock:
861
+ self._status = "failed"
862
+ self._error = error
863
+ self._conversation_turn_in_progress = False
864
+ self._is_streaming = False
865
+ self._current_step = None
866
+ self._enable_conversation_input()
867
+ self._tick()
868
+
869
+ def _enable_conversation_input(self) -> None:
870
+ try:
871
+ input_widget = self.query_one("#conversation-input", Input)
872
+ input_widget.disabled = False
873
+ input_widget.focus()
874
+ except Exception: # noqa: S110
875
+ pass
876
+
877
+ def _stringify_output(self, result: Any) -> str:
878
+ raw_result = getattr(result, "raw", result)
879
+ if raw_result is None:
880
+ return ""
881
+ if isinstance(raw_result, str):
882
+ return raw_result
883
+ try:
884
+ return _json.dumps(raw_result, default=str, ensure_ascii=False)
885
+ except TypeError:
886
+ return str(raw_result)
887
+
728
888
  # ── Actions ─────────────────────────────────────────────
729
889
 
730
890
  def action_toggle_sidebar(self) -> None:
@@ -783,6 +943,7 @@ FooterKey .footer-key--key {
783
943
  self._refresh_log_panel()
784
944
 
785
945
  async def action_quit(self) -> None:
946
+ self._finalize_conversational_session()
786
947
  self._unsubscribe()
787
948
  self.exit(self._crew_result)
788
949
 
@@ -958,6 +1119,30 @@ FooterKey .footer-key--key {
958
1119
  t = Text()
959
1120
  sidebar_width = 30
960
1121
 
1122
+ if self._is_conversational:
1123
+ t.append(" CONVERSATION\n", style=f"bold {_C_PRIMARY}")
1124
+ t.append("\n")
1125
+ if self._conversation_turn_in_progress:
1126
+ t.append(f" {self._spinner()} ", style=_C_PRIMARY)
1127
+ t.append("Working\n", style=f"bold {_C_TEXT}")
1128
+ elif self._status == "failed":
1129
+ t.append(" ✘ Failed\n", style=_C_RED)
1130
+ else:
1131
+ t.append(" ● Ready\n", style=_C_GREEN)
1132
+ t.append(f" Turns {self._conversation_turns}\n", style=_C_DIM)
1133
+ t.append("\n")
1134
+ t.append(" TOKENS\n", style=f"bold {_C_PRIMARY}")
1135
+ t.append("\n")
1136
+ out = self._output_tokens + self._live_out_tokens
1137
+ t.append(f" ↑ {self._input_tokens:,}\n", style=_C_DIM)
1138
+ t.append(f" ↓ {out:,}\n", style=_C_DIM)
1139
+ t.append("\n")
1140
+ t.append(" COMMANDS\n", style=f"bold {_C_PRIMARY}")
1141
+ t.append("\n")
1142
+ t.append(" quit / exit\n", style=_C_DIM)
1143
+ widget.update(t)
1144
+ return
1145
+
961
1146
  t.append(" TASKS\n", style=f"bold {_C_PRIMARY}")
962
1147
  t.append("\n")
963
1148
 
@@ -1011,6 +1196,22 @@ FooterKey .footer-key--key {
1011
1196
  widget = self.query_one("#task-header", Static)
1012
1197
  t = Text()
1013
1198
 
1199
+ if self._is_conversational:
1200
+ if self._status == "failed":
1201
+ t.append("✘ ", style=f"bold {_C_RED}")
1202
+ t.append("Failed", style=f"bold {_C_RED}")
1203
+ if self._error:
1204
+ t.append(f"\n{self._error[:120]}", style=_C_RED)
1205
+ elif self._conversation_turn_in_progress:
1206
+ t.append(f"{self._spinner()} ", style=_C_PRIMARY)
1207
+ t.append("Flow is responding", style=f"bold {_C_PRIMARY}")
1208
+ else:
1209
+ t.append("● ", style=f"bold {_C_GREEN}")
1210
+ t.append("Conversational flow ready", style=f"bold {_C_GREEN}")
1211
+ t.append(" Type a message below", style=_C_DIM)
1212
+ widget.update(t)
1213
+ return
1214
+
1014
1215
  if self._status == "completed":
1015
1216
  elapsed = self._elapsed_frozen or (time.time() - self._start_time)
1016
1217
  t.append("✔ ", style=f"bold {_C_GREEN}")
@@ -1062,6 +1263,41 @@ FooterKey .footer-key--key {
1062
1263
  t = Text()
1063
1264
  should_scroll = False
1064
1265
 
1266
+ if self._is_conversational:
1267
+ if not self._conversation_messages and not self._is_streaming:
1268
+ t.append(" Start the conversation below.\n", style=_C_MUTED)
1269
+ for role, content in self._conversation_messages:
1270
+ if role == "user":
1271
+ t.append("\n You\n", style=f"bold {_C_TEAL}")
1272
+ else:
1273
+ t.append("\n Assistant\n", style=f"bold {_C_PRIMARY}")
1274
+ rendered = _format_json_in_text(_unescape_text(content))
1275
+ for line in rendered.split("\n"):
1276
+ style = _C_TEXT if role == "assistant" else _C_DIM
1277
+ t.append(f" {line}\n", style=style)
1278
+
1279
+ if self._is_streaming and self._streaming_text:
1280
+ text = _unescape_text(self._filtered_streaming_text())
1281
+ if text.strip():
1282
+ t.append("\n Assistant\n", style=f"bold {_C_PRIMARY}")
1283
+ for line in text.rstrip().split("\n")[-40:]:
1284
+ t.append(f" {line}\n", style=_C_TEXT)
1285
+ should_scroll = True
1286
+
1287
+ if self._status == "failed" and self._error:
1288
+ t.append("\n Error\n", style=f"bold {_C_RED}")
1289
+ t.append(f" {self._error}\n", style=_C_RED)
1290
+
1291
+ widget.update(t)
1292
+ if should_scroll:
1293
+ try:
1294
+ self.query_one("#scroll-area", VerticalScroll).scroll_end(
1295
+ animate=False
1296
+ )
1297
+ except Exception: # noqa: S110
1298
+ pass
1299
+ return
1300
+
1065
1301
  # Plan section
1066
1302
  if self._plan and self._plan.get("steps"):
1067
1303
  plan_title = self._plan.get("plan", "Plan")
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import inspect
5
+ from pathlib import Path
6
+ import subprocess
7
+ import sys
8
+ from typing import Any
9
+
10
+ import click
11
+
12
+
13
+ def _project_script_target(script_name: str) -> str | None:
14
+ try:
15
+ from crewai_cli.utils import read_toml
16
+
17
+ pyproject = read_toml()
18
+ except Exception:
19
+ return None
20
+
21
+ target = pyproject.get("project", {}).get("scripts", {}).get(script_name)
22
+ return target if isinstance(target, str) else None
23
+
24
+
25
+ def _prepare_project_import_path() -> None:
26
+ cwd = Path.cwd()
27
+ for path in (cwd / "src", cwd):
28
+ path_str = str(path)
29
+ if path.exists() and path_str not in sys.path:
30
+ sys.path.insert(0, path_str)
31
+
32
+
33
+ def _load_conversational_flow_from_kickoff_script() -> Any | None:
34
+ target = _project_script_target("kickoff")
35
+ if not target or ":" not in target:
36
+ return None
37
+
38
+ module_name, _callable_name = target.split(":", 1)
39
+ _prepare_project_import_path()
40
+
41
+ try:
42
+ module = importlib.import_module(module_name)
43
+ from crewai.flow.flow import Flow
44
+ except Exception:
45
+ return None
46
+
47
+ for value in vars(module).values():
48
+ if (
49
+ inspect.isclass(value)
50
+ and value is not Flow
51
+ and issubclass(value, Flow)
52
+ and getattr(value, "conversational", False)
53
+ ):
54
+ return value()
55
+
56
+ for value in vars(module).values():
57
+ if (
58
+ isinstance(value, Flow)
59
+ and getattr(value, "conversational", False)
60
+ and callable(getattr(value, "handle_turn", None))
61
+ ):
62
+ return value
63
+
64
+ return None
65
+
66
+
67
+ def _run_conversational_flow_tui(flow: Any) -> Any:
68
+ from crewai_cli.crew_run_tui import CrewRunApp
69
+
70
+ app = CrewRunApp(
71
+ crew_name=getattr(flow, "name", None) or type(flow).__name__,
72
+ conversational=True,
73
+ )
74
+ app._flow = flow
75
+ app.run()
76
+
77
+ if app._status == "failed":
78
+ raise SystemExit(1)
79
+
80
+ return app._crew_result
81
+
82
+
83
+ def kickoff_flow() -> None:
84
+ """
85
+ Kickoff the flow by running a command in the UV environment.
86
+ """
87
+ flow = _load_conversational_flow_from_kickoff_script()
88
+ if flow is not None:
89
+ _run_conversational_flow_tui(flow)
90
+ return
91
+
92
+ command = ["uv", "run", "kickoff"]
93
+
94
+ try:
95
+ result = subprocess.run(command, capture_output=False, text=True, check=True) # noqa: S603
96
+
97
+ if result.stderr:
98
+ click.echo(result.stderr, err=True)
99
+
100
+ except subprocess.CalledProcessError as e:
101
+ click.echo(f"An error occurred while running the flow: {e}", err=True)
102
+ click.echo(e.output, err=True)
103
+
104
+ except Exception as e:
105
+ click.echo(f"An unexpected error occurred: {e}", err=True)
@@ -604,6 +604,16 @@ def _run_flow_project(
604
604
  run_declarative_flow_in_project_env(definition=definition)
605
605
  return
606
606
 
607
+ from crewai_cli.kickoff_flow import (
608
+ _load_conversational_flow_from_kickoff_script,
609
+ _run_conversational_flow_tui,
610
+ )
611
+
612
+ flow = _load_conversational_flow_from_kickoff_script()
613
+ if flow is not None:
614
+ _run_conversational_flow_tui(flow)
615
+ return
616
+
607
617
  _execute_uv_script("kickoff", entity_type="flow")
608
618
 
609
619
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from pathlib import Path
4
+ from pathlib import Path, PureWindowsPath
5
5
  import subprocess
6
6
  from typing import Any
7
7
 
@@ -12,7 +12,7 @@ from crewai_cli.utils import build_env_with_all_tool_credentials
12
12
 
13
13
 
14
14
  def run_declarative_flow_in_project_env(
15
- definition: str, inputs: str | None = None
15
+ definition: str | Path, inputs: str | None = None
16
16
  ) -> None:
17
17
  """Run a declarative flow inside the project's Python environment."""
18
18
  if is_declarative_flow_project_env() or not _has_project_file():
@@ -25,7 +25,7 @@ def run_declarative_flow_in_project_env(
25
25
  _execute_declarative_flow_command(["uv", "run", "crewai", "run"])
26
26
 
27
27
 
28
- def plot_declarative_flow_in_project_env(definition: str) -> None:
28
+ def plot_declarative_flow_in_project_env(definition: str | Path) -> None:
29
29
  """Plot a declarative flow inside the project's Python environment."""
30
30
  if is_declarative_flow_project_env() or not _has_project_file():
31
31
  plot_declarative_flow(definition=definition)
@@ -34,7 +34,7 @@ def plot_declarative_flow_in_project_env(definition: str) -> None:
34
34
  _execute_declarative_flow_command(["uv", "run", "crewai", "flow", "plot"])
35
35
 
36
36
 
37
- def run_declarative_flow(definition: str, inputs: str | None = None) -> None:
37
+ def run_declarative_flow(definition: str | Path, inputs: str | None = None) -> None:
38
38
  """Run a declarative flow from a definition path."""
39
39
  parsed_inputs = _parse_inputs(inputs)
40
40
 
@@ -50,7 +50,7 @@ def run_declarative_flow(definition: str, inputs: str | None = None) -> None:
50
50
  click.echo(_format_result(result))
51
51
 
52
52
 
53
- def plot_declarative_flow(definition: str) -> None:
53
+ def plot_declarative_flow(definition: str | Path) -> None:
54
54
  """Plot a declarative flow from a definition path."""
55
55
  try:
56
56
  flow = load_declarative_flow(definition)
@@ -62,7 +62,7 @@ def plot_declarative_flow(definition: str) -> None:
62
62
  raise SystemExit(1) from exc
63
63
 
64
64
 
65
- def load_declarative_flow(definition: str) -> Any:
65
+ def load_declarative_flow(definition: str | Path) -> Any:
66
66
  """Load a declarative Flow instance from a definition path."""
67
67
  try:
68
68
  from crewai.flow.flow import Flow
@@ -102,7 +102,8 @@ def load_declarative_flow(definition: str) -> Any:
102
102
 
103
103
  def configured_project_declarative_flow(
104
104
  pyproject_data: dict[str, Any] | None = None,
105
- ) -> str | None:
105
+ project_root: Path | None = None,
106
+ ) -> Path | None:
106
107
  """Return the configured declarative flow source for flow projects."""
107
108
  if pyproject_data is None:
108
109
  try:
@@ -118,7 +119,66 @@ def configured_project_declarative_flow(
118
119
  definition = crewai_config.get("definition")
119
120
  if not isinstance(definition, str):
120
121
  return None
121
- return definition.strip() or None
122
+ definition = definition.strip()
123
+ if not definition:
124
+ return None
125
+
126
+ return _resolve_project_definition_path(
127
+ definition=definition,
128
+ project_root=project_root or Path.cwd(),
129
+ )
130
+
131
+
132
+ def _resolve_project_definition_path(definition: str, project_root: Path) -> Path:
133
+ definition_path = Path(definition)
134
+ windows_definition_path = PureWindowsPath(definition)
135
+
136
+ if definition.startswith("~"):
137
+ raise click.UsageError(
138
+ "[tool.crewai] definition must be a project-local path; "
139
+ f"got {definition!r}."
140
+ )
141
+
142
+ if definition_path.is_absolute() or windows_definition_path.is_absolute():
143
+ raise click.UsageError(
144
+ "[tool.crewai] definition must be relative to the project root; "
145
+ f"got {definition!r}."
146
+ )
147
+
148
+ try:
149
+ root = project_root.resolve(strict=True)
150
+ except OSError as exc:
151
+ raise click.UsageError(
152
+ f"Invalid project root for [tool.crewai] definition: {exc}"
153
+ ) from exc
154
+
155
+ candidate = root / definition_path
156
+ try:
157
+ resolved_candidate = candidate.resolve(strict=False)
158
+ except OSError as exc:
159
+ raise click.UsageError(
160
+ f"Invalid [tool.crewai] definition path {definition!r}: {exc}"
161
+ ) from exc
162
+
163
+ if not resolved_candidate.is_relative_to(root):
164
+ raise click.UsageError(
165
+ "[tool.crewai] definition must resolve inside the project root; "
166
+ f"got {definition!r}."
167
+ )
168
+
169
+ if not resolved_candidate.exists():
170
+ raise click.UsageError(
171
+ "[tool.crewai] definition must point to an existing file; "
172
+ f"got {definition!r}."
173
+ )
174
+
175
+ if not resolved_candidate.is_file():
176
+ raise click.UsageError(
177
+ "[tool.crewai] definition must point to a regular file; "
178
+ f"got {definition!r}."
179
+ )
180
+
181
+ return resolved_candidate
122
182
 
123
183
 
124
184
  def _execute_declarative_flow_command(command: list[str]) -> None:
@@ -126,6 +126,52 @@ def test_chain_deploy_does_not_login_for_deploy_exit(monkeypatch, capsys) -> Non
126
126
  assert "Deploy failed with exit code 42" in capsys.readouterr().out
127
127
 
128
128
 
129
+ def test_conversation_turn_done_records_assistant_message() -> None:
130
+ class RawResult:
131
+ raw = "hello from the flow"
132
+
133
+ app = CrewRunApp(conversational=True)
134
+ app._conversation_turn_in_progress = True
135
+ app._enable_conversation_input = lambda: None # type: ignore[method-assign]
136
+ app._tick = lambda: None # type: ignore[method-assign]
137
+ app._scroll_to_result = lambda: None # type: ignore[method-assign]
138
+
139
+ app._on_conversation_turn_done(RawResult())
140
+
141
+ assert app._conversation_messages == [("assistant", "hello from the flow")]
142
+ assert app._conversation_turn_in_progress is False
143
+ assert app._status == "chatting"
144
+ assert isinstance(app._crew_result, RawResult)
145
+
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_conversation_input_submits_turn() -> None:
149
+ class FakeFlow:
150
+ defer_trace_finalization = False
151
+
152
+ def handle_turn(self, message: str) -> str:
153
+ return f"reply: {message}"
154
+
155
+ def finalize_session_traces(self) -> None:
156
+ pass
157
+
158
+ app = CrewRunApp(crew_name="Demo", conversational=True)
159
+ app._flow = FakeFlow()
160
+
161
+ async with app.run_test() as pilot:
162
+ await pilot.click("#conversation-input")
163
+ await pilot.press("h", "i", "enter")
164
+ for _ in range(50):
165
+ await pilot.pause(0.05)
166
+ if app._conversation_messages[-1:] == [("assistant", "reply: hi")]:
167
+ break
168
+
169
+ assert app._conversation_messages == [
170
+ ("user", "hi"),
171
+ ("assistant", "reply: hi"),
172
+ ]
173
+
174
+
129
175
  def test_plan_step_status_updates_only_the_explicit_step() -> None:
130
176
  app = _app_with_plan()
131
177