autobyteus 1.1.3__py3-none-any.whl → 1.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. autobyteus/agent/agent.py +1 -1
  2. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
  3. autobyteus/agent/context/__init__.py +4 -2
  4. autobyteus/agent/context/agent_config.py +35 -8
  5. autobyteus/agent/context/agent_context_registry.py +73 -0
  6. autobyteus/agent/events/notifiers.py +4 -0
  7. autobyteus/agent/events/worker_event_dispatcher.py +1 -2
  8. autobyteus/agent/handlers/inter_agent_message_event_handler.py +8 -3
  9. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +19 -19
  10. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
  11. autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
  12. autobyteus/agent/handlers/user_input_message_event_handler.py +16 -1
  13. autobyteus/agent/input_processor/__init__.py +1 -7
  14. autobyteus/agent/message/context_file_type.py +6 -0
  15. autobyteus/agent/message/send_message_to.py +74 -99
  16. autobyteus/agent/phases/discover.py +2 -1
  17. autobyteus/agent/runtime/agent_runtime.py +10 -2
  18. autobyteus/agent/runtime/agent_worker.py +1 -0
  19. autobyteus/agent/sender_type.py +15 -0
  20. autobyteus/agent/streaming/agent_event_stream.py +6 -0
  21. autobyteus/agent/streaming/stream_event_payloads.py +12 -0
  22. autobyteus/agent/streaming/stream_events.py +3 -0
  23. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +7 -4
  24. autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
  25. autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
  26. autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
  27. autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
  28. autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
  29. autobyteus/agent/workspace/base_workspace.py +17 -2
  30. autobyteus/agent_team/__init__.py +1 -0
  31. autobyteus/agent_team/agent_team.py +93 -0
  32. autobyteus/agent_team/agent_team_builder.py +184 -0
  33. autobyteus/agent_team/base_agent_team.py +86 -0
  34. autobyteus/agent_team/bootstrap_steps/__init__.py +24 -0
  35. autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +73 -0
  36. autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +54 -0
  37. autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +25 -0
  38. autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +23 -0
  39. autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +41 -0
  40. autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +85 -0
  41. autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +51 -0
  42. autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +45 -0
  43. autobyteus/agent_team/context/__init__.py +17 -0
  44. autobyteus/agent_team/context/agent_team_config.py +33 -0
  45. autobyteus/agent_team/context/agent_team_context.py +61 -0
  46. autobyteus/agent_team/context/agent_team_runtime_state.py +56 -0
  47. autobyteus/agent_team/context/team_manager.py +147 -0
  48. autobyteus/agent_team/context/team_node_config.py +76 -0
  49. autobyteus/agent_team/events/__init__.py +29 -0
  50. autobyteus/agent_team/events/agent_team_event_dispatcher.py +39 -0
  51. autobyteus/agent_team/events/agent_team_events.py +53 -0
  52. autobyteus/agent_team/events/agent_team_input_event_queue_manager.py +21 -0
  53. autobyteus/agent_team/exceptions.py +8 -0
  54. autobyteus/agent_team/factory/__init__.py +9 -0
  55. autobyteus/agent_team/factory/agent_team_factory.py +99 -0
  56. autobyteus/agent_team/handlers/__init__.py +19 -0
  57. autobyteus/agent_team/handlers/agent_team_event_handler_registry.py +23 -0
  58. autobyteus/agent_team/handlers/base_agent_team_event_handler.py +16 -0
  59. autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +61 -0
  60. autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +27 -0
  61. autobyteus/agent_team/handlers/process_user_message_event_handler.py +46 -0
  62. autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +48 -0
  63. autobyteus/agent_team/phases/__init__.py +11 -0
  64. autobyteus/agent_team/phases/agent_team_operational_phase.py +19 -0
  65. autobyteus/agent_team/phases/agent_team_phase_manager.py +48 -0
  66. autobyteus/agent_team/runtime/__init__.py +13 -0
  67. autobyteus/agent_team/runtime/agent_team_runtime.py +82 -0
  68. autobyteus/agent_team/runtime/agent_team_worker.py +117 -0
  69. autobyteus/agent_team/shutdown_steps/__init__.py +17 -0
  70. autobyteus/agent_team/shutdown_steps/agent_team_shutdown_orchestrator.py +35 -0
  71. autobyteus/agent_team/shutdown_steps/agent_team_shutdown_step.py +42 -0
  72. autobyteus/agent_team/shutdown_steps/base_agent_team_shutdown_step.py +16 -0
  73. autobyteus/agent_team/shutdown_steps/bridge_cleanup_step.py +28 -0
  74. autobyteus/agent_team/shutdown_steps/sub_team_shutdown_step.py +41 -0
  75. autobyteus/agent_team/streaming/__init__.py +26 -0
  76. autobyteus/agent_team/streaming/agent_event_bridge.py +48 -0
  77. autobyteus/agent_team/streaming/agent_event_multiplexer.py +70 -0
  78. autobyteus/agent_team/streaming/agent_team_event_notifier.py +64 -0
  79. autobyteus/agent_team/streaming/agent_team_event_stream.py +33 -0
  80. autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +32 -0
  81. autobyteus/agent_team/streaming/agent_team_stream_events.py +56 -0
  82. autobyteus/agent_team/streaming/team_event_bridge.py +50 -0
  83. autobyteus/agent_team/task_notification/__init__.py +11 -0
  84. autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +164 -0
  85. autobyteus/agent_team/task_notification/task_notification_mode.py +24 -0
  86. autobyteus/agent_team/utils/__init__.py +9 -0
  87. autobyteus/agent_team/utils/wait_for_idle.py +46 -0
  88. autobyteus/cli/__init__.py +1 -1
  89. autobyteus/cli/agent_team_tui/__init__.py +4 -0
  90. autobyteus/cli/agent_team_tui/app.py +210 -0
  91. autobyteus/cli/agent_team_tui/state.py +180 -0
  92. autobyteus/cli/agent_team_tui/widgets/__init__.py +6 -0
  93. autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +149 -0
  94. autobyteus/cli/agent_team_tui/widgets/focus_pane.py +320 -0
  95. autobyteus/cli/agent_team_tui/widgets/logo.py +20 -0
  96. autobyteus/cli/agent_team_tui/widgets/renderables.py +77 -0
  97. autobyteus/cli/agent_team_tui/widgets/shared.py +60 -0
  98. autobyteus/cli/agent_team_tui/widgets/status_bar.py +14 -0
  99. autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +82 -0
  100. autobyteus/cli/cli_display.py +1 -1
  101. autobyteus/cli/workflow_tui/__init__.py +4 -0
  102. autobyteus/cli/workflow_tui/app.py +210 -0
  103. autobyteus/cli/workflow_tui/state.py +189 -0
  104. autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
  105. autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
  106. autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
  107. autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
  108. autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
  109. autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
  110. autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
  111. autobyteus/events/event_types.py +8 -0
  112. autobyteus/llm/api/autobyteus_llm.py +11 -12
  113. autobyteus/llm/api/lmstudio_llm.py +34 -0
  114. autobyteus/llm/api/ollama_llm.py +8 -13
  115. autobyteus/llm/api/openai_compatible_llm.py +20 -3
  116. autobyteus/llm/autobyteus_provider.py +73 -46
  117. autobyteus/llm/llm_factory.py +103 -139
  118. autobyteus/llm/lmstudio_provider.py +104 -0
  119. autobyteus/llm/models.py +83 -53
  120. autobyteus/llm/ollama_provider.py +69 -61
  121. autobyteus/llm/ollama_provider_resolver.py +1 -0
  122. autobyteus/llm/providers.py +13 -12
  123. autobyteus/llm/runtimes.py +11 -0
  124. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  125. autobyteus/task_management/__init__.py +43 -0
  126. autobyteus/task_management/base_task_board.py +68 -0
  127. autobyteus/task_management/converters/__init__.py +11 -0
  128. autobyteus/task_management/converters/task_board_converter.py +64 -0
  129. autobyteus/task_management/converters/task_plan_converter.py +48 -0
  130. autobyteus/task_management/deliverable.py +16 -0
  131. autobyteus/task_management/deliverables/__init__.py +8 -0
  132. autobyteus/task_management/deliverables/file_deliverable.py +15 -0
  133. autobyteus/task_management/events.py +27 -0
  134. autobyteus/task_management/in_memory_task_board.py +126 -0
  135. autobyteus/task_management/schemas/__init__.py +15 -0
  136. autobyteus/task_management/schemas/deliverable_schema.py +13 -0
  137. autobyteus/task_management/schemas/plan_definition.py +35 -0
  138. autobyteus/task_management/schemas/task_status_report.py +27 -0
  139. autobyteus/task_management/task_plan.py +110 -0
  140. autobyteus/task_management/tools/__init__.py +14 -0
  141. autobyteus/task_management/tools/get_task_board_status.py +68 -0
  142. autobyteus/task_management/tools/publish_task_plan.py +113 -0
  143. autobyteus/task_management/tools/update_task_status.py +135 -0
  144. autobyteus/tools/__init__.py +2 -0
  145. autobyteus/tools/ask_user_input.py +2 -1
  146. autobyteus/tools/bash/bash_executor.py +61 -15
  147. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
  148. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
  149. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
  150. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
  151. autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
  152. autobyteus/tools/browser/standalone/navigate_to.py +2 -0
  153. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
  154. autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
  155. autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
  156. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
  157. autobyteus/tools/file/file_reader.py +36 -9
  158. autobyteus/tools/file/file_writer.py +37 -9
  159. autobyteus/tools/functional_tool.py +5 -4
  160. autobyteus/tools/image_downloader.py +2 -0
  161. autobyteus/tools/mcp/config_service.py +63 -58
  162. autobyteus/tools/mcp/server/http_managed_mcp_server.py +14 -2
  163. autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +14 -2
  164. autobyteus/tools/mcp/server_instance_manager.py +30 -4
  165. autobyteus/tools/mcp/tool_registrar.py +106 -51
  166. autobyteus/tools/parameter_schema.py +17 -11
  167. autobyteus/tools/pdf_downloader.py +2 -1
  168. autobyteus/tools/registry/tool_definition.py +36 -37
  169. autobyteus/tools/registry/tool_registry.py +50 -2
  170. autobyteus/tools/timer.py +2 -0
  171. autobyteus/tools/tool_category.py +15 -4
  172. autobyteus/tools/tool_meta.py +6 -1
  173. autobyteus/tools/tool_origin.py +10 -0
  174. autobyteus/tools/usage/formatters/default_json_example_formatter.py +78 -3
  175. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +23 -3
  176. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +6 -0
  177. autobyteus/tools/usage/formatters/google_json_example_formatter.py +7 -0
  178. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +6 -4
  179. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +23 -7
  180. autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +14 -25
  181. autobyteus/tools/usage/providers/__init__.py +2 -12
  182. autobyteus/tools/usage/providers/tool_manifest_provider.py +36 -29
  183. autobyteus/tools/usage/registries/__init__.py +7 -12
  184. autobyteus/tools/usage/registries/tool_formatter_pair.py +15 -0
  185. autobyteus/tools/usage/registries/tool_formatting_registry.py +58 -0
  186. autobyteus/tools/usage/registries/tool_usage_parser_registry.py +55 -0
  187. autobyteus/workflow/agentic_workflow.py +93 -0
  188. autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
  189. autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
  190. autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
  191. autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
  192. autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
  193. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
  194. autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
  195. autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
  196. autobyteus/workflow/context/__init__.py +17 -0
  197. autobyteus/workflow/context/team_manager.py +147 -0
  198. autobyteus/workflow/context/workflow_config.py +30 -0
  199. autobyteus/workflow/context/workflow_context.py +61 -0
  200. autobyteus/workflow/context/workflow_node_config.py +76 -0
  201. autobyteus/workflow/context/workflow_runtime_state.py +53 -0
  202. autobyteus/workflow/events/__init__.py +29 -0
  203. autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
  204. autobyteus/workflow/events/workflow_events.py +53 -0
  205. autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
  206. autobyteus/workflow/exceptions.py +8 -0
  207. autobyteus/workflow/factory/__init__.py +9 -0
  208. autobyteus/workflow/factory/workflow_factory.py +99 -0
  209. autobyteus/workflow/handlers/__init__.py +19 -0
  210. autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
  211. autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
  212. autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
  213. autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
  214. autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
  215. autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
  216. autobyteus/workflow/phases/__init__.py +11 -0
  217. autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
  218. autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
  219. autobyteus/workflow/runtime/__init__.py +13 -0
  220. autobyteus/workflow/runtime/workflow_runtime.py +82 -0
  221. autobyteus/workflow/runtime/workflow_worker.py +117 -0
  222. autobyteus/workflow/shutdown_steps/__init__.py +17 -0
  223. autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
  224. autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
  225. autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
  226. autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
  227. autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
  228. autobyteus/workflow/streaming/__init__.py +26 -0
  229. autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
  230. autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
  231. autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
  232. autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
  233. autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
  234. autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
  235. autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
  236. autobyteus/workflow/utils/__init__.py +9 -0
  237. autobyteus/workflow/utils/wait_for_idle.py +46 -0
  238. autobyteus/workflow/workflow_builder.py +151 -0
  239. {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/METADATA +16 -14
  240. autobyteus-1.1.5.dist-info/RECORD +455 -0
  241. {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/top_level.txt +1 -0
  242. examples/__init__.py +1 -0
  243. examples/agent_team/__init__.py +1 -0
  244. examples/discover_phase_transitions.py +104 -0
  245. examples/run_browser_agent.py +262 -0
  246. examples/run_google_slides_agent.py +287 -0
  247. examples/run_mcp_browser_client.py +174 -0
  248. examples/run_mcp_google_slides_client.py +270 -0
  249. examples/run_mcp_list_tools.py +189 -0
  250. examples/run_poem_writer.py +284 -0
  251. examples/run_sqlite_agent.py +295 -0
  252. autobyteus/agent/context/agent_phase_manager.py +0 -264
  253. autobyteus/agent/context/phases.py +0 -49
  254. autobyteus/agent/group/__init__.py +0 -0
  255. autobyteus/agent/group/agent_group.py +0 -164
  256. autobyteus/agent/group/agent_group_context.py +0 -81
  257. autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
  258. autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
  259. autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
  260. autobyteus/agent/workflow/__init__.py +0 -11
  261. autobyteus/agent/workflow/agentic_workflow.py +0 -89
  262. autobyteus/tools/mcp/call_handlers/__init__.py +0 -16
  263. autobyteus/tools/mcp/call_handlers/base_handler.py +0 -40
  264. autobyteus/tools/mcp/call_handlers/stdio_handler.py +0 -76
  265. autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +0 -55
  266. autobyteus/tools/mcp/registrar.py +0 -202
  267. autobyteus/tools/usage/providers/json_example_provider.py +0 -32
  268. autobyteus/tools/usage/providers/json_schema_provider.py +0 -35
  269. autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +0 -28
  270. autobyteus/tools/usage/providers/xml_example_provider.py +0 -28
  271. autobyteus/tools/usage/providers/xml_schema_provider.py +0 -29
  272. autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +0 -26
  273. autobyteus/tools/usage/registries/json_example_formatter_registry.py +0 -51
  274. autobyteus/tools/usage/registries/json_schema_formatter_registry.py +0 -51
  275. autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +0 -42
  276. autobyteus/tools/usage/registries/xml_example_formatter_registry.py +0 -30
  277. autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +0 -33
  278. autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +0 -30
  279. autobyteus/workflow/simple_task.py +0 -98
  280. autobyteus/workflow/task.py +0 -147
  281. autobyteus/workflow/workflow.py +0 -49
  282. autobyteus-1.1.3.dist-info/RECORD +0 -312
  283. {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/WHEEL +0 -0
  284. {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,20 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/logo.py
2
+ """
3
+ Defines a widget to display the AutoByteus ASCII art logo and tagline.
4
+ """
5
+ from rich.text import Text
6
+ from textual.widgets import Static
7
+ from textual.containers import Vertical
8
+
9
+ class Logo(Vertical):
10
+ """A widget to display the AutoByteus logo and tagline."""
11
+
12
+ def compose(self) -> None:
13
+ # A simple, clean, single-line text logo that is more readable
14
+ # and respects that "AutoByteus" is one word.
15
+ logo_text = Text(justify="center")
16
+ logo_text.append("Auto", style="bold cyan")
17
+ logo_text.append("Byteus", style="bold magenta")
18
+
19
+ yield Static(logo_text, classes="logo-art")
20
+ yield Static("Orchestrating AI Agent Teams.", classes="logo-tagline")
@@ -0,0 +1,77 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/renderables.py
2
+ """
3
+ Contains pure functions that convert agent event data into Rich renderables for the FocusPane.
4
+ This separates presentation logic from the view logic of the widget itself.
5
+ """
6
+ import json
7
+ from typing import Optional
8
+
9
+ from rich.text import Text
10
+ from rich.panel import Panel
11
+
12
+ from autobyteus.agent.streaming.stream_event_payloads import (
13
+ AgentOperationalPhaseTransitionData, AssistantCompleteResponseData,
14
+ ErrorEventData, ToolInteractionLogEntryData, ToolInvocationApprovalRequestedData,
15
+ ToolInvocationAutoExecutingData, SystemTaskNotificationData
16
+ )
17
+ from .shared import ASSISTANT_ICON, TOOL_ICON, PROMPT_ICON, ERROR_ICON, LOG_ICON, SYSTEM_TASK_ICON
18
+
19
+ def render_assistant_complete_response(data: AssistantCompleteResponseData) -> list[Text | Panel]:
20
+ """Renders a complete, pre-aggregated assistant response."""
21
+ renderables = []
22
+ if data.reasoning:
23
+ reasoning_text = Text("<Thinking>\n", style="dim italic cyan")
24
+ reasoning_text.append(data.reasoning)
25
+ reasoning_text.append("\n</Thinking>", style="dim italic cyan")
26
+ renderables.append(reasoning_text)
27
+
28
+ if data.content:
29
+ content_text = Text()
30
+ content_text.append(f"{ASSISTANT_ICON} assistant: ", style="bold green")
31
+ content_text.append(data.content)
32
+ renderables.append(content_text)
33
+
34
+ return renderables
35
+
36
+ def render_tool_interaction_log(data: ToolInteractionLogEntryData) -> Text:
37
+ """Renders a tool interaction log entry."""
38
+ return Text(f"{LOG_ICON} [tool-log] {data.log_entry}", style="dim")
39
+
40
+ def render_tool_auto_executing(data: ToolInvocationAutoExecutingData) -> Text:
41
+ """Renders a notification that a tool is being executed automatically."""
42
+ try:
43
+ args_str = json.dumps(data.arguments, indent=2)
44
+ except (TypeError, OverflowError):
45
+ args_str = str(data.arguments)
46
+
47
+ text_content = Text(f"{TOOL_ICON} Executing tool '", style="default")
48
+ text_content.append(f"{data.tool_name}", style="bold yellow")
49
+ text_content.append("' with arguments:\n", style="default")
50
+ text_content.append(args_str, style="yellow")
51
+ return text_content
52
+
53
+ def render_tool_approval_request(data: ToolInvocationApprovalRequestedData) -> Text:
54
+ """Renders a prompt for the user to approve a tool call."""
55
+ try:
56
+ args_str = json.dumps(data.arguments, indent=2)
57
+ except (TypeError, OverflowError):
58
+ args_str = str(data.arguments)
59
+
60
+ text_content = Text(f"{PROMPT_ICON} Requesting approval for tool '", style="default")
61
+ text_content.append(f"{data.tool_name}", style="bold yellow")
62
+ text_content.append("' with arguments:\n", style="default")
63
+ text_content.append(args_str, style="yellow")
64
+ return text_content
65
+
66
+ def render_error(data: ErrorEventData) -> Text:
67
+ """Renders an error event."""
68
+ error_text = f"Error from {data.source}: {data.message}"
69
+ if data.details:
70
+ error_text += f"\nDetails: {data.details}"
71
+ return Text(f"{ERROR_ICON} {error_text}", style="bold red")
72
+
73
+ def render_system_task_notification(data: SystemTaskNotificationData) -> Text:
74
+ """Renders a system-generated task notification."""
75
+ text_content = Text(f"{SYSTEM_TASK_ICON} System Task Notification: ", style="bold magenta")
76
+ text_content.append(data.content, style="magenta")
77
+ return text_content
@@ -0,0 +1,60 @@
1
+ """
2
+ Shared constants and data for TUI widgets.
3
+ """
4
+ from typing import Dict
5
+ from autobyteus.agent.phases import AgentOperationalPhase
6
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
7
+ from autobyteus.task_management.base_task_board import TaskStatus
8
+
9
+ AGENT_PHASE_ICONS: Dict[AgentOperationalPhase, str] = {
10
+ AgentOperationalPhase.UNINITIALIZED: "⚪",
11
+ AgentOperationalPhase.BOOTSTRAPPING: "⏳",
12
+ AgentOperationalPhase.IDLE: "🟢",
13
+ AgentOperationalPhase.PROCESSING_USER_INPUT: "💭",
14
+ AgentOperationalPhase.AWAITING_LLM_RESPONSE: "💭",
15
+ AgentOperationalPhase.ANALYZING_LLM_RESPONSE: "🤔",
16
+ AgentOperationalPhase.AWAITING_TOOL_APPROVAL: "❓",
17
+ AgentOperationalPhase.TOOL_DENIED: "❌",
18
+ AgentOperationalPhase.EXECUTING_TOOL: "🛠️",
19
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT: "⚙️",
20
+ AgentOperationalPhase.SHUTTING_DOWN: "🌙",
21
+ AgentOperationalPhase.SHUTDOWN_COMPLETE: "⚫",
22
+ AgentOperationalPhase.ERROR: "❗",
23
+ }
24
+
25
+ TEAM_PHASE_ICONS: Dict[AgentTeamOperationalPhase, str] = {
26
+ AgentTeamOperationalPhase.UNINITIALIZED: "⚪",
27
+ AgentTeamOperationalPhase.BOOTSTRAPPING: "⏳",
28
+ AgentTeamOperationalPhase.IDLE: "🟢",
29
+ AgentTeamOperationalPhase.PROCESSING: "⚙️",
30
+ AgentTeamOperationalPhase.SHUTTING_DOWN: "🌙",
31
+ AgentTeamOperationalPhase.SHUTDOWN_COMPLETE: "⚫",
32
+ AgentTeamOperationalPhase.ERROR: "❗",
33
+ }
34
+
35
+ TASK_STATUS_ICONS: Dict[TaskStatus, str] = {
36
+ TaskStatus.NOT_STARTED: "⚪",
37
+ TaskStatus.IN_PROGRESS: "⏳",
38
+ TaskStatus.COMPLETED: "✅",
39
+ TaskStatus.FAILED: "❌",
40
+ TaskStatus.BLOCKED: "🔒",
41
+ }
42
+
43
+ # Main component icons
44
+ SUB_TEAM_ICON = "📂"
45
+ TEAM_ICON = "🏁"
46
+ AGENT_ICON = "🤖"
47
+
48
+ # General UI icons
49
+ SPEAKING_ICON = "🔊"
50
+ DEFAULT_ICON = "❓"
51
+
52
+ # Semantic icons for log entries
53
+ USER_ICON = "👤"
54
+ ASSISTANT_ICON = "🤖"
55
+ TOOL_ICON = "🛠️"
56
+ PROMPT_ICON = "❓"
57
+ ERROR_ICON = "💥"
58
+ PHASE_ICON = "🔄"
59
+ LOG_ICON = "📄"
60
+ SYSTEM_TASK_ICON = "📥" # NEW
@@ -0,0 +1,14 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/status_bar.py
2
+ """
3
+ Defines the status bar widget for the TUI.
4
+ """
5
+
6
+ from textual.widgets import Footer
7
+
8
+ class StatusBar(Footer):
9
+ """A simple footer widget that displays key bindings."""
10
+
11
+ def __init__(self) -> None:
12
+ super().__init__()
13
+ # This will be automatically populated by Textual's binding system.
14
+ # You can add more status information here if needed in the future.
@@ -0,0 +1,82 @@
1
+ import logging
2
+ from typing import List, Optional, Dict, Union
3
+
4
+ from rich.table import Table
5
+ from rich.panel import Panel
6
+ from rich.text import Text
7
+ from textual.widgets import Static
8
+
9
+ from autobyteus.task_management.task_plan import Task
10
+ from autobyteus.task_management.base_task_board import TaskStatus
11
+ from .shared import TASK_STATUS_ICONS, LOG_ICON
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class TaskBoardPanel(Static):
16
+ """A widget to display the team's task board."""
17
+
18
+ def __init__(self, tasks: Optional[List[Task]], statuses: Dict[str, TaskStatus], team_name: str, **kwargs) -> None:
19
+ super().__init__(**kwargs)
20
+ self.tasks = tasks or []
21
+ self.statuses = statuses or {}
22
+ self.team_name = team_name
23
+
24
+ def compose(self) -> None:
25
+ if not self.tasks:
26
+ yield Static(Panel("No task plan has been published yet.", title="Task Board", border_style="yellow", title_align="left"))
27
+ return
28
+
29
+ table = Table(
30
+ expand=True,
31
+ show_header=True,
32
+ header_style="bold magenta",
33
+ show_lines=True
34
+ )
35
+ table.add_column("ID", justify="left", style="cyan", no_wrap=True, min_width=10)
36
+ table.add_column("Name", style="white", min_width=15)
37
+ table.add_column("Status", justify="left", style="white")
38
+ table.add_column("Assigned To", justify="center", style="green")
39
+ table.add_column("Deliverables", justify="left", style="cyan", min_width=30)
40
+ table.add_column("Depends On", justify="center", style="dim")
41
+
42
+ # Create a name-to-ID map to resolve dependency names
43
+ id_to_name_map = {task.task_id: task.task_name for task in self.tasks}
44
+
45
+ # Sort tasks by name for consistent ordering
46
+ sorted_tasks = sorted(self.tasks, key=lambda t: t.task_name)
47
+
48
+ for task in sorted_tasks:
49
+ task_status = self.statuses.get(task.task_id, TaskStatus.NOT_STARTED)
50
+ status_icon = TASK_STATUS_ICONS.get(task_status, "❓")
51
+ status_text = f"{status_icon} {task_status.value.upper().replace('_', ' ')}"
52
+
53
+ status_style = "default"
54
+ if task_status == TaskStatus.COMPLETED:
55
+ status_style = "strike dim green"
56
+ elif task_status == TaskStatus.FAILED:
57
+ status_style = "bold red"
58
+
59
+ # Create a renderable for the deliverables column
60
+ deliverables_renderable: Union[str, Text] = "N/A"
61
+ if task.file_deliverables:
62
+ text = Text()
63
+ for i, d in enumerate(task.file_deliverables):
64
+ if i > 0:
65
+ text.append("\n") # Add a newline for spacing between deliverables
66
+ text.append(f"{LOG_ICON} {d.file_path}\n", style="bold")
67
+ text.append(f" └─ {d.summary}", style="dim")
68
+ deliverables_renderable = text
69
+
70
+ # Resolve dependency IDs to names for display
71
+ dep_names = [id_to_name_map.get(dep_id, dep_id) for dep_id in task.dependencies]
72
+
73
+ table.add_row(
74
+ task.task_id,
75
+ task.task_name,
76
+ Text(status_text, style=status_style),
77
+ task.assignee_name or "N/A",
78
+ deliverables_renderable,
79
+ ", ".join(dep_names)
80
+ )
81
+
82
+ yield Static(Panel(table, title="Task Board", border_style="blue", title_align="left"))
@@ -4,7 +4,7 @@ import sys
4
4
  from typing import Optional, List, Dict, Any
5
5
  import json
6
6
 
7
- from autobyteus.agent.context.phases import AgentOperationalPhase
7
+ from autobyteus.agent.phases.phase_enum import AgentOperationalPhase
8
8
  from autobyteus.agent.streaming.stream_events import StreamEvent, StreamEventType
9
9
  from autobyteus.agent.streaming.stream_event_payloads import (
10
10
  AssistantChunkData,
@@ -0,0 +1,4 @@
1
+ # file: autobyteus/autobyteus/cli/workflow_tui/__init__.py
2
+ """
3
+ A Textual-based TUI for interacting with Agentic Workflows.
4
+ """
@@ -0,0 +1,210 @@
1
+ # file: autobyteus/autobyteus/cli/workflow_tui/app.py
2
+ """
3
+ The main Textual application class for the workflow TUI. This class orchestrates
4
+ the UI by reacting to changes in a central state store.
5
+ """
6
+ import asyncio
7
+ import logging
8
+ from typing import Dict, Optional, Any
9
+
10
+ from textual.app import App, ComposeResult
11
+ from textual.containers import Horizontal
12
+ from textual.widgets import Header, Static
13
+ from textual.reactive import reactive
14
+
15
+ from autobyteus.workflow.agentic_workflow import AgenticWorkflow
16
+ from autobyteus.workflow.streaming.workflow_event_stream import WorkflowEventStream
17
+ from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
18
+ from autobyteus.agent.streaming.stream_events import StreamEventType as AgentStreamEventType
19
+ from autobyteus.agent.streaming.stream_event_payloads import AssistantChunkData
20
+ from autobyteus.workflow.streaming.workflow_stream_event_payloads import AgentEventRebroadcastPayload, WorkflowPhaseTransitionData
21
+
22
+ from .state import TUIStateStore
23
+ from .widgets.agent_list_sidebar import AgentListSidebar
24
+ from .widgets.focus_pane import FocusPane
25
+ from .widgets.status_bar import StatusBar
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ class WorkflowApp(App):
30
+ """A Textual TUI for interacting with an agentic workflow, built around a central state store."""
31
+
32
+ TITLE = "AutoByteus"
33
+ CSS_PATH = "app.css"
34
+ BINDINGS = [
35
+ ("d", "toggle_dark", "Toggle Dark Mode"),
36
+ ("q", "quit", "Quit"),
37
+ ]
38
+
39
+ focused_node_data: reactive[Optional[Dict[str, Any]]] = reactive(None)
40
+ # The store_version property will trigger UI updates for the sidebar.
41
+ store_version: reactive[int] = reactive(0)
42
+
43
+ def __init__(self, workflow: AgenticWorkflow, **kwargs):
44
+ super().__init__(**kwargs)
45
+ self.workflow = workflow
46
+ self.store = TUIStateStore(workflow=self.workflow)
47
+ self.workflow_stream: Optional[WorkflowEventStream] = None
48
+ # Flag to indicate that the UI needs an update, used for throttling.
49
+ self._ui_update_pending = False
50
+
51
+ def compose(self) -> ComposeResult:
52
+ yield Header(id="app-header", name="AutoByteus Mission Control")
53
+ with Horizontal(id="main-container"):
54
+ yield AgentListSidebar(id="sidebar")
55
+ yield FocusPane(id="focus-pane")
56
+ yield StatusBar()
57
+
58
+ async def on_mount(self) -> None:
59
+ """Start background tasks when the app is mounted."""
60
+ self.workflow.start()
61
+ self.workflow_stream = WorkflowEventStream(self.workflow)
62
+
63
+ # Initialize the UI with the starting state
64
+ initial_tree = self.store.get_tree_data()
65
+ initial_focus_node = initial_tree.get(self.workflow.name)
66
+
67
+ self.store.set_focused_node(initial_focus_node)
68
+ self.focused_node_data = initial_focus_node
69
+ self.store_version = self.store.version # Trigger initial render
70
+
71
+ self.run_worker(self._listen_for_workflow_events(), name="workflow_listener")
72
+
73
+ # Set up a timer to run the throttled UI updater at ~15 FPS.
74
+ self.set_interval(1 / 15, self._throttled_ui_updater, name="ui_updater")
75
+ logger.info("Workflow TUI mounted, workflow listener and throttled UI updater started.")
76
+
77
+ async def on_unmount(self) -> None:
78
+ if self.workflow and self.workflow.is_running:
79
+ await self.workflow.stop()
80
+
81
+ def _throttled_ui_updater(self) -> None:
82
+ """
83
+ Periodically checks if the UI state is dirty and, if so, triggers
84
+ reactive updates. It also flushes streaming buffers from the focus pane.
85
+ """
86
+ focus_pane = self.query_one(FocusPane)
87
+ if self._ui_update_pending:
88
+ self._ui_update_pending = False
89
+ # This is the throttled trigger for the async watcher.
90
+ self.store_version = self.store.version
91
+
92
+ # Always flush the focus pane's streaming buffer for smooth text rendering.
93
+ focus_pane.flush_stream_buffers()
94
+
95
+ async def _listen_for_workflow_events(self) -> None:
96
+ """A background worker that forwards workflow events to the state store and updates the UI."""
97
+ if not self.workflow_stream: return
98
+ try:
99
+ async for event in self.workflow_stream.all_events():
100
+ # 1. Always update the central state store immediately.
101
+ self.store.process_event(event)
102
+
103
+ # 2. Mark the UI as needing an update for the throttled components.
104
+ self._ui_update_pending = True
105
+
106
+ # 3. Handle real-time, incremental updates directly.
107
+ # This is for components like the FocusPane's text stream, which needs
108
+ # to be as low-latency as possible. The actual UI update is buffered.
109
+ if isinstance(event.data, AgentEventRebroadcastPayload):
110
+ payload = event.data
111
+ agent_name = payload.agent_name
112
+ agent_event = payload.agent_event
113
+ focus_pane = self.query_one(FocusPane)
114
+
115
+ is_currently_focused = (focus_pane._focused_node_data and focus_pane._focused_node_data.get('name') == agent_name)
116
+
117
+ # If the event is for the currently focused agent, send the event
118
+ # to be buffered and eventually rendered.
119
+ if is_currently_focused:
120
+ await focus_pane.add_agent_event(agent_event)
121
+
122
+ except asyncio.CancelledError:
123
+ logger.info("Workflow event listener task was cancelled.")
124
+ except Exception:
125
+ logger.error("Critical error in workflow TUI event listener", exc_info=True)
126
+ finally:
127
+ if self.workflow_stream: await self.workflow_stream.close()
128
+
129
+ # --- Reactive Watchers ---
130
+
131
+ async def watch_store_version(self, new_version: int):
132
+ """
133
+ Reacts to changes in the store version. This is now called by the throttled
134
+ updater, not on every event. Its main job is to update less-frequently
135
+ changing components like the sidebar tree and workflow dashboards.
136
+ """
137
+ sidebar = self.query_one(AgentListSidebar)
138
+ focus_pane = self.query_one(FocusPane)
139
+
140
+ # Fetch fresh data from the store for the update
141
+ tree_data = self.store.get_tree_data()
142
+ agent_phases = self.store._agent_phases
143
+ workflow_phases = self.store._workflow_phases
144
+ speaking_agents = self.store._speaking_agents
145
+
146
+ # Update sidebar
147
+ sidebar.update_tree(tree_data, agent_phases, workflow_phases, speaking_agents)
148
+
149
+ # Intelligently update the focus pane
150
+ focused_data = self.focused_node_data
151
+ if focused_data and focused_data.get("type") in ['workflow', 'subworkflow']:
152
+ # If a workflow/subworkflow is focused, its dashboard might be out of date.
153
+ # A full re-render is cheap and ensures consistency for its title and panels.
154
+ history = self.store.get_history_for_node(focused_data['name'], focused_data['type'])
155
+ await focus_pane.update_content(
156
+ node_data=focused_data,
157
+ history=history,
158
+ pending_approval=None,
159
+ all_agent_phases=agent_phases,
160
+ all_workflow_phases=workflow_phases
161
+ )
162
+ elif focused_data and focused_data.get("type") == 'agent':
163
+ # For agents, we only need to update the title status, not the whole log.
164
+ focus_pane.update_current_node_status(agent_phases, workflow_phases)
165
+
166
+
167
+ async def watch_focused_node_data(self, new_node_data: Optional[Dict[str, Any]]):
168
+ """Reacts to changes in which node is focused. Primarily used for full pane reloads on user click."""
169
+ if not new_node_data: return
170
+
171
+ node_name = new_node_data['name']
172
+ node_type = new_node_data['type']
173
+
174
+ history = self.store.get_history_for_node(node_name, node_type)
175
+ pending_approval = self.store.get_pending_approval_for_agent(node_name) if node_type == 'agent' else None
176
+
177
+ sidebar = self.query_one(AgentListSidebar)
178
+ focus_pane = self.query_one(FocusPane)
179
+
180
+ await focus_pane.update_content(
181
+ node_data=new_node_data,
182
+ history=history,
183
+ pending_approval=pending_approval,
184
+ all_agent_phases=self.store._agent_phases,
185
+ all_workflow_phases=self.store._workflow_phases
186
+ )
187
+
188
+ sidebar.update_selection(node_name)
189
+
190
+ # --- Event Handlers (Actions) ---
191
+
192
+ def on_agent_list_sidebar_node_selected(self, message: AgentListSidebar.NodeSelected):
193
+ """Handles a node being selected by updating the store and the app's reactive state."""
194
+ self.store.set_focused_node(message.node_data)
195
+ self.focused_node_data = message.node_data
196
+
197
+ async def on_focus_pane_message_submitted(self, message: FocusPane.MessageSubmitted):
198
+ """Dispatches a user message to the backend model."""
199
+ user_message = AgentInputUserMessage(content=message.text)
200
+ await self.workflow.post_message(message=user_message, target_agent_name=message.agent_name)
201
+
202
+ async def on_focus_pane_approval_submitted(self, message: FocusPane.ApprovalSubmitted):
203
+ """Dispatches a tool approval to the backend model."""
204
+ self.store.clear_pending_approval(message.agent_name)
205
+ await self.workflow.post_tool_execution_approval(
206
+ agent_name=message.agent_name,
207
+ tool_invocation_id=message.invocation_id,
208
+ is_approved=message.is_approved,
209
+ reason=message.reason,
210
+ )
@@ -0,0 +1,189 @@
1
+ # file: autobyteus/autobyteus/cli/workflow_tui/state.py
2
+ """
3
+ Defines a centralized state store for the TUI application, following state management best practices.
4
+ """
5
+ import logging
6
+ from typing import Dict, List, Optional, Any
7
+ import copy
8
+
9
+ from autobyteus.agent.context import AgentConfig
10
+ from autobyteus.workflow.agentic_workflow import AgenticWorkflow
11
+ from autobyteus.agent.phases import AgentOperationalPhase
12
+ from autobyteus.workflow.phases import WorkflowOperationalPhase
13
+ from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent, StreamEventType as AgentStreamEventType
14
+ from autobyteus.agent.streaming.stream_event_payloads import (
15
+ AgentOperationalPhaseTransitionData, ToolInvocationApprovalRequestedData,
16
+ AssistantChunkData, AssistantCompleteResponseData
17
+ )
18
+ from autobyteus.workflow.streaming.workflow_stream_events import WorkflowStreamEvent
19
+ from autobyteus.workflow.streaming.workflow_stream_event_payloads import AgentEventRebroadcastPayload, SubWorkflowEventRebroadcastPayload, WorkflowPhaseTransitionData
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class TUIStateStore:
24
+ """
25
+ A centralized store for all TUI-related state.
26
+
27
+ This class acts as the single source of truth for the UI. It processes events
28
+ from the backend and updates its state. The main App class can then react to
29
+ these state changes to update the UI components declaratively. This is a plain
30
+ Python class and does not use Textual reactive properties.
31
+ """
32
+
33
+ def __init__(self, workflow: AgenticWorkflow):
34
+ self.workflow_name = workflow.name
35
+ self.workflow_role = workflow.role
36
+
37
+ self.focused_node_data: Optional[Dict[str, Any]] = None
38
+
39
+ self._node_roles: Dict[str, str] = self._extract_node_roles(workflow)
40
+ self._nodes: Dict[str, Any] = self._initialize_root_node()
41
+ self._agent_phases: Dict[str, AgentOperationalPhase] = {}
42
+ self._workflow_phases: Dict[str, WorkflowOperationalPhase] = {self.workflow_name: WorkflowOperationalPhase.UNINITIALIZED}
43
+ self._agent_event_history: Dict[str, List[AgentStreamEvent]] = {}
44
+ self._workflow_event_history: Dict[str, List[WorkflowStreamEvent]] = {self.workflow_name: []}
45
+ self._pending_approvals: Dict[str, ToolInvocationApprovalRequestedData] = {}
46
+ self._speaking_agents: Dict[str, bool] = {}
47
+
48
+ # REMOVED: The complex stream aggregator is the source of the bug.
49
+ # self._agent_stream_aggregators: Dict[str, Dict[str, str]] = {}
50
+
51
+ # Version counter to signal state changes to the UI
52
+ self.version = 0
53
+
54
+ def _extract_node_roles(self, workflow: AgenticWorkflow) -> Dict[str, str]:
55
+ """Builds a map of node names to their defined roles from the config."""
56
+ roles = {}
57
+ if workflow._runtime and workflow._runtime.context and workflow._runtime.context.config:
58
+ for node_config in workflow._runtime.context.config.nodes:
59
+ role = getattr(node_config.node_definition, 'role', None)
60
+ if role:
61
+ roles[node_config.name] = role
62
+ return roles
63
+
64
+ def _initialize_root_node(self) -> Dict[str, Any]:
65
+ """Creates the initial root node for the state tree."""
66
+ return {
67
+ self.workflow_name: {
68
+ "type": "workflow",
69
+ "name": self.workflow_name,
70
+ "role": self.workflow_role,
71
+ "children": {}
72
+ }
73
+ }
74
+
75
+ def process_event(self, event: WorkflowStreamEvent):
76
+ """
77
+ The main entry point for processing events from the backend.
78
+ This method acts as a reducer, updating the state based on the event.
79
+ """
80
+ if event.event_source_type == "WORKFLOW" and isinstance(event.data, WorkflowPhaseTransitionData):
81
+ self._workflow_phases[self.workflow_name] = event.data.new_phase
82
+
83
+ self._process_event_recursively(event, self.workflow_name)
84
+
85
+ # Increment version to signal that the state has changed.
86
+ self.version += 1
87
+
88
+ # REMOVED: The flush aggregator logic is no longer needed.
89
+ # def _flush_aggregator_for_agent(self, agent_name: str): ...
90
+
91
+ def _process_event_recursively(self, event: WorkflowStreamEvent, parent_name: str):
92
+ """Recursively processes events to build up the state tree."""
93
+ if parent_name not in self._workflow_event_history:
94
+ self._workflow_event_history[parent_name] = []
95
+ self._workflow_event_history[parent_name].append(event)
96
+
97
+ # AGENT EVENT (LEAF NODE)
98
+ if isinstance(event.data, AgentEventRebroadcastPayload):
99
+ payload = event.data
100
+ agent_name = payload.agent_name
101
+ agent_event = payload.agent_event
102
+
103
+ if agent_name not in self._agent_event_history:
104
+ self._agent_event_history[agent_name] = []
105
+ if self._find_node(parent_name):
106
+ agent_role = self._node_roles.get(agent_name, "Agent")
107
+ self._add_node(agent_name, {"type": "agent", "name": agent_name, "role": agent_role, "children": {}}, parent_name)
108
+ else:
109
+ logger.error(f"Cannot add agent node '{agent_name}': parent '{parent_name}' not found in state tree.")
110
+
111
+ # SIMPLIFIED LOGIC: Always append the event to the history, regardless of focus.
112
+ # This ensures the history is always a complete and accurate log of what happened.
113
+ self._agent_event_history[agent_name].append(agent_event)
114
+
115
+ # --- State update logic for specific events (applies to both focused and non-focused) ---
116
+ if agent_event.event_type == AgentStreamEventType.AGENT_OPERATIONAL_PHASE_TRANSITION:
117
+ phase_data: AgentOperationalPhaseTransitionData = agent_event.data
118
+ self._agent_phases[agent_name] = phase_data.new_phase
119
+ if agent_name in self._pending_approvals:
120
+ del self._pending_approvals[agent_name]
121
+ elif agent_event.event_type == AgentStreamEventType.AGENT_IDLE:
122
+ self._agent_phases[agent_name] = AgentOperationalPhase.IDLE
123
+ elif agent_event.event_type == AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED:
124
+ self._pending_approvals[agent_name] = agent_event.data
125
+
126
+ # SUB-WORKFLOW EVENT (BRANCH NODE)
127
+ elif isinstance(event.data, SubWorkflowEventRebroadcastPayload):
128
+ payload = event.data
129
+ sub_workflow_name = payload.sub_workflow_node_name
130
+ sub_workflow_event = payload.sub_workflow_event
131
+
132
+ sub_workflow_node = self._find_node(sub_workflow_name)
133
+ if not sub_workflow_node:
134
+ role = self._node_roles.get(sub_workflow_name, "Sub-Workflow")
135
+ self._add_node(sub_workflow_name, {"type": "subworkflow", "name": sub_workflow_name, "role": role, "children": {}}, parent_name)
136
+
137
+ if sub_workflow_event.event_source_type == "WORKFLOW" and isinstance(sub_workflow_event.data, WorkflowPhaseTransitionData):
138
+ self._workflow_phases[sub_workflow_name] = sub_workflow_event.data.new_phase
139
+
140
+ self._process_event_recursively(sub_workflow_event, parent_name=sub_workflow_name)
141
+
142
+ def _add_node(self, node_name: str, node_data: Dict, parent_name: str):
143
+ """Adds a node to the state tree under a specific parent."""
144
+ parent = self._find_node(parent_name)
145
+ if parent:
146
+ parent["children"][node_name] = node_data
147
+ else:
148
+ logger.error(f"Could not find parent node '{parent_name}' to add child '{node_name}'.")
149
+
150
+ def _find_node(self, node_name: str, tree: Optional[Dict] = None) -> Optional[Dict]:
151
+ """Recursively finds a node by name in the state tree."""
152
+ if tree is None:
153
+ tree = self._nodes
154
+
155
+ for name, node_data in tree.items():
156
+ if name == node_name:
157
+ return node_data
158
+ if node_data.get("children"):
159
+ found = self._find_node(node_name, node_data.get("children"))
160
+ if found:
161
+ return found
162
+ return None
163
+
164
+ def get_tree_data(self) -> Dict:
165
+ """Constructs a serializable representation of the tree for the sidebar."""
166
+ return copy.deepcopy(self._nodes)
167
+
168
+ def get_history_for_node(self, node_name: str, node_type: str) -> List:
169
+ """Retrieves the event history for a given node."""
170
+ if node_type == 'agent':
171
+ # REMOVED: Flushing is no longer necessary as the history is always complete.
172
+ return self._agent_event_history.get(node_name, [])
173
+ elif node_type in ['workflow', 'subworkflow']:
174
+ return []
175
+ return []
176
+
177
+ def get_pending_approval_for_agent(self, agent_name: str) -> Optional[ToolInvocationApprovalRequestedData]:
178
+ """Gets pending approval data for a specific agent."""
179
+ return self._pending_approvals.get(agent_name)
180
+
181
+ def clear_pending_approval(self, agent_name: str):
182
+ """Clears a pending approval after it's been handled."""
183
+ if agent_name in self._pending_approvals:
184
+ del self._pending_approvals[agent_name]
185
+
186
+ def set_focused_node(self, node_data: Optional[Dict[str, Any]]):
187
+ """Sets the currently focused node in the state."""
188
+ # REMOVED: Flushing logic is no longer needed here.
189
+ self.focused_node_data = node_data