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,180 @@
1
+ """
2
+ Defines a centralized state store for the TUI application, following state management best practices.
3
+ """
4
+ import logging
5
+ from typing import Dict, List, Optional, Any
6
+ import copy
7
+
8
+ from autobyteus.agent.context import AgentConfig
9
+ from autobyteus.agent_team.agent_team import AgentTeam
10
+ from autobyteus.agent.phases import AgentOperationalPhase
11
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
12
+ from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent, StreamEventType as AgentStreamEventType
13
+ from autobyteus.agent.streaming.stream_event_payloads import (
14
+ AgentOperationalPhaseTransitionData, ToolInvocationApprovalRequestedData,
15
+ AssistantChunkData, AssistantCompleteResponseData
16
+ )
17
+ from autobyteus.agent_team.streaming.agent_team_stream_events import AgentTeamStreamEvent
18
+ from autobyteus.agent_team.streaming.agent_team_stream_event_payloads import AgentEventRebroadcastPayload, SubTeamEventRebroadcastPayload, AgentTeamPhaseTransitionData
19
+ from autobyteus.task_management.task_plan import Task
20
+ from autobyteus.task_management.events import TaskPlanPublishedEvent, TaskStatusUpdatedEvent
21
+ from autobyteus.task_management.base_task_board import TaskStatus
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ class TUIStateStore:
26
+ """
27
+ A centralized store for all TUI-related state.
28
+
29
+ This class acts as the single source of truth for the UI. It processes events
30
+ from the backend and updates its state. The main App class can then react to
31
+ these state changes to update the UI components declaratively.
32
+ """
33
+
34
+ def __init__(self, team: AgentTeam):
35
+ self.team_name = team.name
36
+ self.team_role = team.role
37
+
38
+ self.focused_node_data: Optional[Dict[str, Any]] = None
39
+
40
+ self._node_roles: Dict[str, str] = self._extract_node_roles(team)
41
+ self._nodes: Dict[str, Any] = self._initialize_root_node()
42
+ self._agent_phases: Dict[str, AgentOperationalPhase] = {}
43
+ self._team_phases: Dict[str, AgentTeamOperationalPhase] = {self.team_name: AgentTeamOperationalPhase.UNINITIALIZED}
44
+ self._agent_event_history: Dict[str, List[AgentStreamEvent]] = {}
45
+ self._team_event_history: Dict[str, List[AgentTeamStreamEvent]] = {self.team_name: []}
46
+ self._pending_approvals: Dict[str, ToolInvocationApprovalRequestedData] = {}
47
+ self._speaking_agents: Dict[str, bool] = {}
48
+
49
+ # State for task boards
50
+ self._task_plans: Dict[str, List[Task]] = {} # team_name -> List[Task]
51
+ self._task_statuses: Dict[str, Dict[str, TaskStatus]] = {} # team_name -> {task_id: status}
52
+
53
+ # Version counter to signal state changes to the UI
54
+ self.version = 0
55
+
56
+ def _extract_node_roles(self, team: AgentTeam) -> Dict[str, str]:
57
+ roles = {}
58
+ if team._runtime and team._runtime.context and team._runtime.context.config:
59
+ for node_config in team._runtime.context.config.nodes:
60
+ role = getattr(node_config.node_definition, 'role', None)
61
+ if role:
62
+ roles[node_config.name] = role
63
+ return roles
64
+
65
+ def _initialize_root_node(self) -> Dict[str, Any]:
66
+ return {
67
+ self.team_name: {
68
+ "type": "team",
69
+ "name": self.team_name,
70
+ "role": self.team_role,
71
+ "children": {}
72
+ }
73
+ }
74
+
75
+ def process_event(self, event: AgentTeamStreamEvent):
76
+ self.version += 1 # Increment on any event to signal a change
77
+
78
+ if event.event_source_type == "TEAM" and isinstance(event.data, AgentTeamPhaseTransitionData):
79
+ self._team_phases[self.team_name] = event.data.new_phase
80
+
81
+ self._process_event_recursively(event, self.team_name)
82
+
83
+ def _process_event_recursively(self, event: AgentTeamStreamEvent, parent_name: str):
84
+ if parent_name not in self._team_event_history:
85
+ self._team_event_history[parent_name] = []
86
+ self._team_event_history[parent_name].append(event)
87
+
88
+ if event.event_source_type == "TASK_BOARD":
89
+ # The 'parent_name' argument holds the friendly name of the team (or sub-team)
90
+ # that is the context for this event. This is the key we use for UI state.
91
+ team_name_key = parent_name
92
+ if isinstance(event.data, TaskPlanPublishedEvent):
93
+ self._task_plans[team_name_key] = event.data.plan.tasks
94
+ # Reset statuses when a new plan is published
95
+ self._task_statuses[team_name_key] = {task.task_id: TaskStatus.NOT_STARTED for task in event.data.plan.tasks}
96
+ logger.debug(f"TUI State: Updated task plan for '{team_name_key}' with {len(event.data.plan.tasks)} tasks.")
97
+ elif isinstance(event.data, TaskStatusUpdatedEvent):
98
+ # Update status
99
+ if team_name_key not in self._task_statuses:
100
+ self._task_statuses[team_name_key] = {}
101
+ self._task_statuses[team_name_key][event.data.task_id] = event.data.new_status
102
+ logger.debug(f"TUI State: Updated status for task '{event.data.task_id}' in team '{team_name_key}' to {event.data.new_status}.")
103
+
104
+ # Update deliverables if they are provided in the event.
105
+ if event.data.deliverables is not None:
106
+ if team_name_key in self._task_plans:
107
+ for task in self._task_plans[team_name_key]:
108
+ if task.task_id == event.data.task_id:
109
+ task.file_deliverables = event.data.deliverables
110
+ logger.debug(f"TUI State: Synced deliverables for task '{event.data.task_id}' in team '{team_name_key}'.")
111
+ break
112
+ return
113
+
114
+ if isinstance(event.data, AgentEventRebroadcastPayload):
115
+ payload = event.data
116
+ agent_name = payload.agent_name
117
+ agent_event = payload.agent_event
118
+
119
+ if agent_name not in self._agent_event_history:
120
+ self._agent_event_history[agent_name] = []
121
+ if self._find_node(parent_name):
122
+ agent_role = self._node_roles.get(agent_name, "Agent")
123
+ self._add_node(agent_name, {"type": "agent", "name": agent_name, "role": agent_role, "children": {}}, parent_name)
124
+ else: logger.error(f"Cannot add agent node '{agent_name}': parent '{parent_name}' not found.")
125
+ self._agent_event_history[agent_name].append(agent_event)
126
+
127
+ if agent_event.event_type == AgentStreamEventType.AGENT_OPERATIONAL_PHASE_TRANSITION:
128
+ self._agent_phases[agent_name] = agent_event.data.new_phase
129
+ if agent_name in self._pending_approvals: del self._pending_approvals[agent_name]
130
+ elif agent_event.event_type == AgentStreamEventType.AGENT_IDLE:
131
+ self._agent_phases[agent_name] = AgentOperationalPhase.IDLE
132
+ elif agent_event.event_type == AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED:
133
+ self._pending_approvals[agent_name] = agent_event.data
134
+
135
+ elif isinstance(event.data, SubTeamEventRebroadcastPayload):
136
+ payload = event.data
137
+ sub_team_name = payload.sub_team_node_name
138
+ sub_team_event = payload.sub_team_event
139
+ if not self._find_node(sub_team_name):
140
+ role = self._node_roles.get(sub_team_name, "Sub-Team")
141
+ self._add_node(sub_team_name, {"type": "subteam", "name": sub_team_name, "role": role, "children": {}}, parent_name)
142
+ if sub_team_event.event_source_type == "TEAM" and isinstance(sub_team_event.data, AgentTeamPhaseTransitionData):
143
+ self._team_phases[sub_team_name] = sub_team_event.data.new_phase
144
+ self._process_event_recursively(sub_team_event, parent_name=sub_team_name)
145
+
146
+ def _add_node(self, node_name: str, node_data: Dict, parent_name: str):
147
+ parent = self._find_node(parent_name)
148
+ if parent: parent["children"][node_name] = node_data
149
+ else: logger.error(f"Could not find parent node '{parent_name}' to add child '{node_name}'.")
150
+
151
+ def _find_node(self, node_name: str, tree: Optional[Dict] = None) -> Optional[Dict]:
152
+ tree = tree or self._nodes
153
+ for name, node_data in tree.items():
154
+ if name == node_name: return node_data
155
+ if node_data.get("children"):
156
+ found = self._find_node(node_name, node_data.get("children"))
157
+ if found: return found
158
+ return None
159
+
160
+ def get_tree_data(self) -> Dict:
161
+ return copy.deepcopy(self._nodes)
162
+
163
+ def get_history_for_node(self, node_name: str, node_type: str) -> List:
164
+ if node_type == 'agent': return self._agent_event_history.get(node_name, [])
165
+ return []
166
+
167
+ def get_pending_approval_for_agent(self, agent_name: str) -> Optional[ToolInvocationApprovalRequestedData]:
168
+ return self._pending_approvals.get(agent_name)
169
+
170
+ def get_task_board_plan(self, team_name: str) -> Optional[List[Task]]:
171
+ return self._task_plans.get(team_name)
172
+
173
+ def get_task_board_statuses(self, team_name: str) -> Optional[Dict[str, TaskStatus]]:
174
+ return self._task_statuses.get(team_name)
175
+
176
+ def clear_pending_approval(self, agent_name: str):
177
+ if agent_name in self._pending_approvals: del self._pending_approvals[agent_name]
178
+
179
+ def set_focused_node(self, node_data: Optional[Dict[str, Any]]):
180
+ self.focused_node_data = node_data
@@ -0,0 +1,6 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/__init__.py
2
+ """
3
+ Custom Textual widgets for the agent team TUI.
4
+ """
5
+ from . import renderables
6
+ from .logo import Logo
@@ -0,0 +1,149 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py
2
+ """
3
+ Defines the sidebar widget that lists all nodes in the team hierarchy.
4
+ """
5
+ import logging
6
+ from typing import Dict, Any, Optional
7
+
8
+ from textual.message import Message
9
+ from textual.widgets import Static, Tree
10
+ from textual.widgets.tree import TreeNode
11
+ from textual.containers import Vertical
12
+
13
+ from autobyteus.agent.phases import AgentOperationalPhase
14
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
15
+ from .shared import (
16
+ AGENT_PHASE_ICONS, TEAM_PHASE_ICONS, SUB_TEAM_ICON,
17
+ TEAM_ICON, SPEAKING_ICON, DEFAULT_ICON
18
+ )
19
+ from .logo import Logo
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class AgentListSidebar(Static):
24
+ """A widget to display the hierarchical list of team nodes. This is a dumb
25
+ rendering component driven by the TUIStateStore."""
26
+
27
+ class NodeSelected(Message):
28
+ """Posted when any node is selected in the tree."""
29
+ def __init__(self, node_data: Dict[str, Any]) -> None:
30
+ self.node_data = node_data
31
+ super().__init__()
32
+
33
+ def __init__(self, *args, **kwargs) -> None:
34
+ super().__init__(*args, **kwargs)
35
+ self._node_map: Dict[str, TreeNode] = {} # Maps node names to TreeNode objects
36
+
37
+ def compose(self):
38
+ with Vertical():
39
+ yield Tree("Agent Team", id="agent-tree")
40
+ yield Logo()
41
+
42
+ def on_tree_node_selected(self, event: Tree.NodeSelected) -> None:
43
+ """Handle node selection from the tree."""
44
+ if event.node.data:
45
+ self.post_message(self.NodeSelected(event.node.data))
46
+ event.stop()
47
+
48
+ def _build_label(self, name: str, node_data: Dict, agent_phases: Dict, team_phases: Dict, speaking_agents: Dict) -> str:
49
+ """Constructs the display label for a tree node."""
50
+ node_type = node_data["type"]
51
+ icon = DEFAULT_ICON
52
+
53
+ if node_type == "agent":
54
+ phase = agent_phases.get(name, AgentOperationalPhase.UNINITIALIZED)
55
+ icon = SPEAKING_ICON if speaking_agents.get(name) else AGENT_PHASE_ICONS.get(phase, DEFAULT_ICON)
56
+ label = f"{icon} {name}"
57
+ elif node_type in ["team", "subteam"]:
58
+ phase = team_phases.get(name, AgentTeamOperationalPhase.UNINITIALIZED)
59
+ default_icon = TEAM_ICON if node_type == "team" else SUB_TEAM_ICON
60
+ icon = TEAM_PHASE_ICONS.get(phase, default_icon)
61
+ role = node_data.get("role")
62
+ label = f"{icon} {role or name}"
63
+ if role and role != name:
64
+ label += f" ({name})"
65
+ else:
66
+ label = f"{icon} {name}"
67
+
68
+ return label
69
+
70
+ def update_tree(self, tree_data: Dict, agent_phases: Dict[str, AgentOperationalPhase], team_phases: Dict[str, AgentTeamOperationalPhase], speaking_agents: Dict[str, bool]):
71
+ """
72
+ Performs an in-place update of the tree to reflect the new state,
73
+ avoiding a full rebuild for better performance and preserving UI state like expansion.
74
+ """
75
+ tree = self.query_one(Tree)
76
+
77
+ if not tree_data:
78
+ tree.root.set_label("Initializing agent team...")
79
+ return
80
+
81
+ root_name = list(tree_data.keys())[0]
82
+ root_node_data = tree_data[root_name]
83
+
84
+ # Kick off the recursive update from the root.
85
+ self._update_node_recursively(tree.root, root_node_data, agent_phases, team_phases, speaking_agents)
86
+
87
+ # Ensure the root is expanded on the first run.
88
+ if not tree.root.is_expanded:
89
+ tree.root.expand()
90
+
91
+ def _update_node_recursively(self, ui_node: TreeNode, node_data: Dict, agent_phases: Dict, team_phases: Dict, speaking_agents: Dict):
92
+ """Recursively updates a node and reconciles its children."""
93
+ # 1. Update the current node's label and data
94
+ name = node_data['name']
95
+ label = self._build_label(name, node_data, agent_phases, team_phases, speaking_agents)
96
+ ui_node.set_label(label)
97
+ ui_node.data = node_data
98
+ self._node_map[name] = ui_node # Ensure map is always up-to-date
99
+
100
+ # 2. Reconcile children
101
+ new_children_data = node_data.get("children", {})
102
+ existing_ui_children_by_name = {child.data['name']: child for child in ui_node.children if child.data}
103
+
104
+ # Add new nodes and update existing ones
105
+ for child_name, child_data in new_children_data.items():
106
+ if child_name in existing_ui_children_by_name:
107
+ # Node exists, so we recursively update it
108
+ child_ui_node = existing_ui_children_by_name[child_name]
109
+ self._update_node_recursively(child_ui_node, child_data, agent_phases, team_phases, speaking_agents)
110
+ else:
111
+ # Node is new, so we add it
112
+ new_child_label = self._build_label(child_name, child_data, agent_phases, team_phases, speaking_agents)
113
+ is_leaf = child_data.get("children", {}) == {} and child_data['type'] == 'agent'
114
+
115
+ if is_leaf:
116
+ new_ui_node = ui_node.add_leaf(new_child_label, data=child_data)
117
+ else:
118
+ new_ui_node = ui_node.add(new_child_label, data=child_data)
119
+ # Since this is a new branch, we must build its children too
120
+ self._update_node_recursively(new_ui_node, child_data, agent_phases, team_phases, speaking_agents)
121
+
122
+ self._node_map[child_name] = new_ui_node
123
+
124
+ # Remove old nodes that no longer exist in the new data
125
+ nodes_to_remove = []
126
+ for existing_child_name, existing_child_node in existing_ui_children_by_name.items():
127
+ if existing_child_name not in new_children_data:
128
+ nodes_to_remove.append(existing_child_node)
129
+ if existing_child_name in self._node_map:
130
+ del self._node_map[existing_child_name]
131
+
132
+ for node in nodes_to_remove:
133
+ node.remove()
134
+
135
+ def update_selection(self, node_name: Optional[str]):
136
+ """Updates the tree's selection and expands parents to make it visible."""
137
+ if not node_name or node_name not in self._node_map:
138
+ return
139
+
140
+ tree = self.query_one(Tree)
141
+ node_to_select = self._node_map[node_name]
142
+
143
+ parent = node_to_select.parent
144
+ while parent:
145
+ parent.expand()
146
+ parent = parent.parent
147
+
148
+ tree.select_node(node_to_select)
149
+ tree.scroll_to_node(node_to_select)
@@ -0,0 +1,320 @@
1
+ """
2
+ Defines the main focus pane widget for displaying detailed logs or summaries.
3
+ """
4
+ import logging
5
+ import json
6
+ from typing import Optional, List, Any, Dict
7
+
8
+ from rich.text import Text
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+ from textual.message import Message
12
+ from textual.widgets import Input, Static, Button
13
+ from textual.containers import VerticalScroll, Horizontal
14
+
15
+ from autobyteus.agent.phases import AgentOperationalPhase
16
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
17
+ from autobyteus.task_management.base_task_board import TaskStatus
18
+ from autobyteus.task_management.task_plan import Task
19
+ from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent, StreamEventType as AgentStreamEventType
20
+ from autobyteus.agent.streaming.stream_event_payloads import (
21
+ AgentOperationalPhaseTransitionData, AssistantChunkData, AssistantCompleteResponseData,
22
+ ErrorEventData, ToolInteractionLogEntryData, ToolInvocationApprovalRequestedData, ToolInvocationAutoExecutingData,
23
+ SystemTaskNotificationData
24
+ )
25
+ from .shared import (
26
+ AGENT_PHASE_ICONS, TEAM_PHASE_ICONS, SUB_TEAM_ICON, DEFAULT_ICON,
27
+ USER_ICON, ASSISTANT_ICON, TEAM_ICON, AGENT_ICON, SYSTEM_TASK_ICON
28
+ )
29
+ from . import renderables
30
+ from .task_board_panel import TaskBoardPanel
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ class FocusPane(Static):
35
+ """
36
+ A widget to display detailed logs for agents or high-level dashboards for teams.
37
+ This is a dumb rendering component driven by the TUIStateStore.
38
+ """
39
+
40
+ class MessageSubmitted(Message):
41
+ def __init__(self, text: str, agent_name: str) -> None:
42
+ self.text = text
43
+ self.agent_name = agent_name
44
+ super().__init__()
45
+
46
+ class ApprovalSubmitted(Message):
47
+ def __init__(self, agent_name: str, invocation_id: str, is_approved: bool, reason: Optional[str]) -> None:
48
+ self.agent_name = agent_name
49
+ self.invocation_id = invocation_id
50
+ self.is_approved = is_approved
51
+ self.reason = reason
52
+ super().__init__()
53
+
54
+ def __init__(self, *args, **kwargs) -> None:
55
+ super().__init__(*args, **kwargs)
56
+ self._focused_node_data: Optional[Dict[str, Any]] = None
57
+ self._pending_approval_data: Optional[ToolInvocationApprovalRequestedData] = None
58
+
59
+ # State variables for streaming
60
+ self._thinking_widget: Optional[Static] = None
61
+ self._thinking_text: Optional[Text] = None
62
+ self._assistant_content_widget: Optional[Static] = None
63
+ self._assistant_content_text: Optional[Text] = None
64
+
65
+ # Buffers for batched UI updates to improve performance
66
+ self._reasoning_buffer: str = ""
67
+ self._content_buffer: str = ""
68
+
69
+ def compose(self):
70
+ yield Static("Select a node from the sidebar", id="focus-pane-title")
71
+ yield VerticalScroll(id="focus-pane-log-container")
72
+ yield Horizontal(id="approval-buttons")
73
+ yield Input(placeholder="Select an agent to send messages...", id="focus-pane-input", disabled=True)
74
+
75
+ async def on_input_submitted(self, event: Input.Submitted) -> None:
76
+ if event.value and self._focused_node_data and self._focused_node_data.get("type") == 'agent':
77
+ log_container = self.query_one("#focus-pane-log-container")
78
+ user_message_text = Text(f"{USER_ICON} You: {event.value}", style="bright_blue")
79
+ await log_container.mount(Static(""))
80
+ await log_container.mount(Static(user_message_text))
81
+ log_container.scroll_end(animate=False)
82
+
83
+ self.post_message(self.MessageSubmitted(event.value, self._focused_node_data['name']))
84
+ self.query_one(Input).clear()
85
+ event.stop()
86
+
87
+ async def on_button_pressed(self, event: Button.Pressed) -> None:
88
+ if not self._pending_approval_data or not self._focused_node_data:
89
+ return
90
+
91
+ is_approved = event.button.id == "approve-btn"
92
+ reason = "User approved via TUI." if is_approved else "User denied via TUI."
93
+
94
+ log_container = self.query_one("#focus-pane-log-container")
95
+ approval_text = "APPROVED" if is_approved else "DENIED"
96
+ display_text = Text(f"{USER_ICON} You: {approval_text} (Reason: {reason})", style="bright_cyan")
97
+ await log_container.mount(Static(""))
98
+ await log_container.mount(Static(display_text))
99
+ log_container.scroll_end(animate=False)
100
+
101
+ self.post_message(self.ApprovalSubmitted(
102
+ agent_name=self._focused_node_data['name'],
103
+ invocation_id=self._pending_approval_data.invocation_id,
104
+ is_approved=is_approved, reason=reason
105
+ ))
106
+ await self._clear_approval_ui()
107
+ event.stop()
108
+
109
+ async def _clear_approval_ui(self):
110
+ self._pending_approval_data = None
111
+ await self.query_one("#approval-buttons").remove_children()
112
+ input_widget = self.query_one(Input)
113
+ if self._focused_node_data and self._focused_node_data.get("type") == "agent":
114
+ input_widget.disabled = False
115
+ input_widget.placeholder = f"Send a message to {self._focused_node_data['name']}..."
116
+ input_widget.focus()
117
+ else:
118
+ input_widget.disabled = True
119
+ input_widget.placeholder = "Select an agent to send messages..."
120
+
121
+ async def _show_approval_prompt(self):
122
+ if not self._pending_approval_data: return
123
+ input_widget = self.query_one(Input)
124
+ input_widget.placeholder = "Please approve or deny the tool call..."
125
+ input_widget.disabled = True
126
+ button_container = self.query_one("#approval-buttons")
127
+ await button_container.remove_children()
128
+ await button_container.mount(
129
+ Button("Approve", variant="success", id="approve-btn"),
130
+ Button("Deny", variant="error", id="deny-btn")
131
+ )
132
+
133
+ def _update_title(self, agent_phases: Dict[str, AgentOperationalPhase], team_phases: Dict[str, AgentTeamOperationalPhase]):
134
+ """Renders the title of the focus pane with the node's current status."""
135
+ if not self._focused_node_data:
136
+ self.query_one("#focus-pane-title").update("Select a node from the sidebar")
137
+ return
138
+
139
+ node_name = self._focused_node_data.get("name", "Unknown")
140
+ node_type = self._focused_node_data.get("type", "node")
141
+ node_type_str = node_type.replace("_", " ").capitalize()
142
+
143
+ title_icon = DEFAULT_ICON
144
+ phase_str = ""
145
+
146
+ if node_type == 'agent':
147
+ title_icon = AGENT_ICON
148
+ phase = agent_phases.get(node_name, AgentOperationalPhase.UNINITIALIZED)
149
+ phase_str = f" (Status: {phase.value})"
150
+ elif node_type == 'subteam':
151
+ title_icon = SUB_TEAM_ICON
152
+ phase = team_phases.get(node_name, AgentTeamOperationalPhase.UNINITIALIZED)
153
+ phase_str = f" (Status: {phase.value})"
154
+ elif node_type == 'team':
155
+ title_icon = TEAM_ICON
156
+ phase = team_phases.get(node_name, AgentTeamOperationalPhase.UNINITIALIZED)
157
+ phase_str = f" (Status: {phase.value})"
158
+
159
+ self.query_one("#focus-pane-title").update(f"{title_icon} {node_type_str}: [bold]{node_name}[/bold]{phase_str}")
160
+
161
+ def update_current_node_status(self, all_agent_phases: Dict, all_team_phases: Dict):
162
+ """A lightweight method to only update the title with the latest status."""
163
+ self._update_title(all_agent_phases, all_team_phases)
164
+
165
+ async def update_content(self, node_data: Dict[str, Any], history: List[Any],
166
+ pending_approval: Optional[ToolInvocationApprovalRequestedData],
167
+ all_agent_phases: Dict[str, AgentOperationalPhase],
168
+ all_team_phases: Dict[str, AgentTeamOperationalPhase],
169
+ task_plan: Optional[List[Task]],
170
+ task_statuses: Optional[Dict[str, TaskStatus]]):
171
+ """The main method to update the entire pane based on new state."""
172
+ self.flush_stream_buffers()
173
+
174
+ self._focused_node_data = node_data
175
+ self._pending_approval_data = pending_approval
176
+
177
+ self._update_title(all_agent_phases, all_team_phases)
178
+
179
+ log_container = self.query_one("#focus-pane-log-container")
180
+ await log_container.remove_children()
181
+
182
+ self._thinking_widget = None
183
+ self._thinking_text = None
184
+ self._assistant_content_widget = None
185
+ self._assistant_content_text = None
186
+
187
+ await self._clear_approval_ui()
188
+
189
+ if self._focused_node_data.get("type") == 'agent':
190
+ for event in history:
191
+ await self.add_agent_event(event)
192
+ if self._pending_approval_data:
193
+ await self._show_approval_prompt()
194
+ elif self._focused_node_data.get("type") in ['team', 'subteam']:
195
+ await self._render_team_dashboard(node_data, all_agent_phases, all_team_phases, task_plan, task_statuses)
196
+
197
+ async def _render_team_dashboard(self, node_data: Dict[str, Any],
198
+ all_agent_phases: Dict[str, AgentOperationalPhase],
199
+ all_team_phases: Dict[str, AgentTeamOperationalPhase],
200
+ task_plan: Optional[List[Task]],
201
+ task_statuses: Optional[Dict[str, TaskStatus]]):
202
+ """Renders a static summary dashboard for a team or sub-team."""
203
+ log_container = self.query_one("#focus-pane-log-container")
204
+
205
+ phase = all_team_phases.get(node_data['name'], AgentTeamOperationalPhase.UNINITIALIZED)
206
+ phase_icon = TEAM_PHASE_ICONS.get(phase, DEFAULT_ICON)
207
+ info_text = Text()
208
+ info_text.append(f"Name: {node_data['name']}\n", style="bold")
209
+ if node_data.get('role'):
210
+ info_text.append(f"Role: {node_data['role']}\n")
211
+ info_text.append(f"Status: {phase_icon} {phase.value}")
212
+ await log_container.mount(Static(Panel(info_text, title="Team Info", border_style="green", title_align="left")))
213
+
214
+ await log_container.mount(TaskBoardPanel(tasks=task_plan, statuses=task_statuses, team_name=node_data['name']))
215
+
216
+ children_data = node_data.get("children", {})
217
+ if children_data:
218
+ team_text = Text()
219
+ for name, child_node in children_data.items():
220
+ if child_node['type'] == 'agent':
221
+ agent_phase = all_agent_phases.get(name, AgentOperationalPhase.UNINITIALIZED)
222
+ agent_icon = AGENT_PHASE_ICONS.get(agent_phase, DEFAULT_ICON)
223
+ team_text.append(f" ▪ {agent_icon} {name} (Agent): {agent_phase.value}\n")
224
+ elif child_node['type'] == 'subteam':
225
+ wf_phase = all_team_phases.get(name, AgentTeamOperationalPhase.UNINITIALIZED)
226
+ wf_icon = TEAM_PHASE_ICONS.get(wf_phase, SUB_TEAM_ICON)
227
+ team_text.append(f" ▪ {wf_icon} {name} (Sub-Team): {wf_phase.value}\n")
228
+ await log_container.mount(Static(Panel(team_text, title="Team Status", border_style="blue", title_align="left")))
229
+
230
+ async def _close_thinking_block(self, scroll: bool = True):
231
+ if self._thinking_widget and self._thinking_text:
232
+ self.flush_stream_buffers()
233
+ self._thinking_text.append("\n</Thinking>", style="dim italic cyan")
234
+ self._thinking_widget.update(self._thinking_text)
235
+ if scroll:
236
+ self.query_one("#focus-pane-log-container").scroll_end(animate=False)
237
+ self._thinking_widget = None
238
+ self._thinking_text = None
239
+
240
+ def flush_stream_buffers(self):
241
+ scrolled = False
242
+ if self._reasoning_buffer and self._thinking_widget and self._thinking_text:
243
+ self._thinking_text.append(self._reasoning_buffer)
244
+ self._thinking_widget.update(self._thinking_text)
245
+ self._reasoning_buffer = ""
246
+ scrolled = True
247
+ if self._content_buffer and self._assistant_content_widget and self._assistant_content_text:
248
+ self._assistant_content_text.append(self._content_buffer)
249
+ self._assistant_content_widget.update(self._assistant_content_text)
250
+ self._content_buffer = ""
251
+ scrolled = True
252
+ if scrolled:
253
+ self.query_one("#focus-pane-log-container").scroll_end(animate=False)
254
+
255
+ async def add_agent_event(self, event: AgentStreamEvent):
256
+ log_container = self.query_one("#focus-pane-log-container")
257
+ event_type = event.event_type
258
+
259
+ if event_type == AgentStreamEventType.ASSISTANT_CHUNK:
260
+ data: AssistantChunkData = event.data
261
+ if data.reasoning:
262
+ if self._thinking_widget is None:
263
+ self.flush_stream_buffers()
264
+ await log_container.mount(Static(""))
265
+ self._thinking_text = Text("<Thinking>\n", style="dim italic cyan")
266
+ self._thinking_widget = Static(self._thinking_text)
267
+ await log_container.mount(self._thinking_widget)
268
+ self._reasoning_buffer += data.reasoning
269
+ if data.content:
270
+ if self._thinking_widget: await self._close_thinking_block()
271
+ if self._assistant_content_widget is None:
272
+ await log_container.mount(Static(""))
273
+ self._assistant_content_text = Text(f"{ASSISTANT_ICON} assistant: ", style="bold green")
274
+ self._assistant_content_widget = Static(self._assistant_content_text)
275
+ await log_container.mount(self._assistant_content_widget)
276
+ self._content_buffer += data.content
277
+ return
278
+
279
+ if event_type == AgentStreamEventType.ASSISTANT_COMPLETE_RESPONSE:
280
+ was_streaming_content = self._assistant_content_widget is not None
281
+ self.flush_stream_buffers()
282
+ await self._close_thinking_block()
283
+ self._assistant_content_widget = None
284
+ self._assistant_content_text = None
285
+ if not was_streaming_content:
286
+ renderables_list = renderables.render_assistant_complete_response(event.data)
287
+ if renderables_list:
288
+ await log_container.mount(Static(""))
289
+ for item in renderables_list: await log_container.mount(Static(item))
290
+ log_container.scroll_end(animate=False)
291
+ return
292
+
293
+ is_stream_breaking_event = event_type in [
294
+ AgentStreamEventType.TOOL_INTERACTION_LOG_ENTRY,
295
+ AgentStreamEventType.TOOL_INVOCATION_AUTO_EXECUTING,
296
+ AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED,
297
+ AgentStreamEventType.ERROR_EVENT,
298
+ AgentStreamEventType.SYSTEM_TASK_NOTIFICATION, # NEW
299
+ ]
300
+ if is_stream_breaking_event:
301
+ self.flush_stream_buffers()
302
+ await self._close_thinking_block()
303
+ self._assistant_content_widget = None
304
+ self._assistant_content_text = None
305
+
306
+ renderable = None
307
+ if event_type == AgentStreamEventType.TOOL_INTERACTION_LOG_ENTRY: renderable = renderables.render_tool_interaction_log(event.data)
308
+ elif event_type == AgentStreamEventType.TOOL_INVOCATION_AUTO_EXECUTING: renderable = renderables.render_tool_auto_executing(event.data)
309
+ elif event_type == AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED:
310
+ renderable = renderables.render_tool_approval_request(event.data)
311
+ self._pending_approval_data = event.data
312
+ await self._show_approval_prompt()
313
+ elif event_type == AgentStreamEventType.ERROR_EVENT: renderable = renderables.render_error(event.data)
314
+ elif event_type == AgentStreamEventType.SYSTEM_TASK_NOTIFICATION: renderable = renderables.render_system_task_notification(event.data) # NEW
315
+
316
+ if renderable:
317
+ await log_container.mount(Static(""))
318
+ await log_container.mount(Static(renderable))
319
+
320
+ log_container.scroll_end(animate=False)