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.
- autobyteus/agent/agent.py +1 -1
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/__init__.py +4 -2
- autobyteus/agent/context/agent_config.py +35 -8
- autobyteus/agent/context/agent_context_registry.py +73 -0
- autobyteus/agent/events/notifiers.py +4 -0
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +8 -3
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +19 -19
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
- autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
- autobyteus/agent/handlers/user_input_message_event_handler.py +16 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +74 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_runtime.py +10 -2
- autobyteus/agent/runtime/agent_worker.py +1 -0
- autobyteus/agent/sender_type.py +15 -0
- autobyteus/agent/streaming/agent_event_stream.py +6 -0
- autobyteus/agent/streaming/stream_event_payloads.py +12 -0
- autobyteus/agent/streaming/stream_events.py +3 -0
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +7 -4
- autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
- autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
- autobyteus/agent/workspace/base_workspace.py +17 -2
- autobyteus/agent_team/__init__.py +1 -0
- autobyteus/agent_team/agent_team.py +93 -0
- autobyteus/agent_team/agent_team_builder.py +184 -0
- autobyteus/agent_team/base_agent_team.py +86 -0
- autobyteus/agent_team/bootstrap_steps/__init__.py +24 -0
- autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +73 -0
- autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +54 -0
- autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +25 -0
- autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +23 -0
- autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +85 -0
- autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +51 -0
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +45 -0
- autobyteus/agent_team/context/__init__.py +17 -0
- autobyteus/agent_team/context/agent_team_config.py +33 -0
- autobyteus/agent_team/context/agent_team_context.py +61 -0
- autobyteus/agent_team/context/agent_team_runtime_state.py +56 -0
- autobyteus/agent_team/context/team_manager.py +147 -0
- autobyteus/agent_team/context/team_node_config.py +76 -0
- autobyteus/agent_team/events/__init__.py +29 -0
- autobyteus/agent_team/events/agent_team_event_dispatcher.py +39 -0
- autobyteus/agent_team/events/agent_team_events.py +53 -0
- autobyteus/agent_team/events/agent_team_input_event_queue_manager.py +21 -0
- autobyteus/agent_team/exceptions.py +8 -0
- autobyteus/agent_team/factory/__init__.py +9 -0
- autobyteus/agent_team/factory/agent_team_factory.py +99 -0
- autobyteus/agent_team/handlers/__init__.py +19 -0
- autobyteus/agent_team/handlers/agent_team_event_handler_registry.py +23 -0
- autobyteus/agent_team/handlers/base_agent_team_event_handler.py +16 -0
- autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +27 -0
- autobyteus/agent_team/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +48 -0
- autobyteus/agent_team/phases/__init__.py +11 -0
- autobyteus/agent_team/phases/agent_team_operational_phase.py +19 -0
- autobyteus/agent_team/phases/agent_team_phase_manager.py +48 -0
- autobyteus/agent_team/runtime/__init__.py +13 -0
- autobyteus/agent_team/runtime/agent_team_runtime.py +82 -0
- autobyteus/agent_team/runtime/agent_team_worker.py +117 -0
- autobyteus/agent_team/shutdown_steps/__init__.py +17 -0
- autobyteus/agent_team/shutdown_steps/agent_team_shutdown_orchestrator.py +35 -0
- autobyteus/agent_team/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/agent_team/shutdown_steps/base_agent_team_shutdown_step.py +16 -0
- autobyteus/agent_team/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/agent_team/shutdown_steps/sub_team_shutdown_step.py +41 -0
- autobyteus/agent_team/streaming/__init__.py +26 -0
- autobyteus/agent_team/streaming/agent_event_bridge.py +48 -0
- autobyteus/agent_team/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/agent_team/streaming/agent_team_event_notifier.py +64 -0
- autobyteus/agent_team/streaming/agent_team_event_stream.py +33 -0
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +32 -0
- autobyteus/agent_team/streaming/agent_team_stream_events.py +56 -0
- autobyteus/agent_team/streaming/team_event_bridge.py +50 -0
- autobyteus/agent_team/task_notification/__init__.py +11 -0
- autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +164 -0
- autobyteus/agent_team/task_notification/task_notification_mode.py +24 -0
- autobyteus/agent_team/utils/__init__.py +9 -0
- autobyteus/agent_team/utils/wait_for_idle.py +46 -0
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/agent_team_tui/__init__.py +4 -0
- autobyteus/cli/agent_team_tui/app.py +210 -0
- autobyteus/cli/agent_team_tui/state.py +180 -0
- autobyteus/cli/agent_team_tui/widgets/__init__.py +6 -0
- autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +320 -0
- autobyteus/cli/agent_team_tui/widgets/logo.py +20 -0
- autobyteus/cli/agent_team_tui/widgets/renderables.py +77 -0
- autobyteus/cli/agent_team_tui/widgets/shared.py +60 -0
- autobyteus/cli/agent_team_tui/widgets/status_bar.py +14 -0
- autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +82 -0
- autobyteus/cli/cli_display.py +1 -1
- autobyteus/cli/workflow_tui/__init__.py +4 -0
- autobyteus/cli/workflow_tui/app.py +210 -0
- autobyteus/cli/workflow_tui/state.py +189 -0
- autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
- autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
- autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
- autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
- autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
- autobyteus/events/event_types.py +8 -0
- autobyteus/llm/api/autobyteus_llm.py +11 -12
- autobyteus/llm/api/lmstudio_llm.py +34 -0
- autobyteus/llm/api/ollama_llm.py +8 -13
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/autobyteus_provider.py +73 -46
- autobyteus/llm/llm_factory.py +103 -139
- autobyteus/llm/lmstudio_provider.py +104 -0
- autobyteus/llm/models.py +83 -53
- autobyteus/llm/ollama_provider.py +69 -61
- autobyteus/llm/ollama_provider_resolver.py +1 -0
- autobyteus/llm/providers.py +13 -12
- autobyteus/llm/runtimes.py +11 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/task_management/__init__.py +43 -0
- autobyteus/task_management/base_task_board.py +68 -0
- autobyteus/task_management/converters/__init__.py +11 -0
- autobyteus/task_management/converters/task_board_converter.py +64 -0
- autobyteus/task_management/converters/task_plan_converter.py +48 -0
- autobyteus/task_management/deliverable.py +16 -0
- autobyteus/task_management/deliverables/__init__.py +8 -0
- autobyteus/task_management/deliverables/file_deliverable.py +15 -0
- autobyteus/task_management/events.py +27 -0
- autobyteus/task_management/in_memory_task_board.py +126 -0
- autobyteus/task_management/schemas/__init__.py +15 -0
- autobyteus/task_management/schemas/deliverable_schema.py +13 -0
- autobyteus/task_management/schemas/plan_definition.py +35 -0
- autobyteus/task_management/schemas/task_status_report.py +27 -0
- autobyteus/task_management/task_plan.py +110 -0
- autobyteus/task_management/tools/__init__.py +14 -0
- autobyteus/task_management/tools/get_task_board_status.py +68 -0
- autobyteus/task_management/tools/publish_task_plan.py +113 -0
- autobyteus/task_management/tools/update_task_status.py +135 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/bash/bash_executor.py +61 -15
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
- autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
- autobyteus/tools/browser/standalone/navigate_to.py +2 -0
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
- autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
- autobyteus/tools/file/file_reader.py +36 -9
- autobyteus/tools/file/file_writer.py +37 -9
- autobyteus/tools/functional_tool.py +5 -4
- autobyteus/tools/image_downloader.py +2 -0
- autobyteus/tools/mcp/config_service.py +63 -58
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +14 -2
- autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +14 -2
- autobyteus/tools/mcp/server_instance_manager.py +30 -4
- autobyteus/tools/mcp/tool_registrar.py +106 -51
- autobyteus/tools/parameter_schema.py +17 -11
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +36 -37
- autobyteus/tools/registry/tool_registry.py +50 -2
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +15 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +78 -3
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +23 -3
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +6 -0
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +7 -0
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +6 -4
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +23 -7
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +14 -25
- autobyteus/tools/usage/providers/__init__.py +2 -12
- autobyteus/tools/usage/providers/tool_manifest_provider.py +36 -29
- autobyteus/tools/usage/registries/__init__.py +7 -12
- autobyteus/tools/usage/registries/tool_formatter_pair.py +15 -0
- autobyteus/tools/usage/registries/tool_formatting_registry.py +58 -0
- autobyteus/tools/usage/registries/tool_usage_parser_registry.py +55 -0
- autobyteus/workflow/agentic_workflow.py +93 -0
- autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
- autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
- autobyteus/workflow/context/__init__.py +17 -0
- autobyteus/workflow/context/team_manager.py +147 -0
- autobyteus/workflow/context/workflow_config.py +30 -0
- autobyteus/workflow/context/workflow_context.py +61 -0
- autobyteus/workflow/context/workflow_node_config.py +76 -0
- autobyteus/workflow/context/workflow_runtime_state.py +53 -0
- autobyteus/workflow/events/__init__.py +29 -0
- autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
- autobyteus/workflow/events/workflow_events.py +53 -0
- autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
- autobyteus/workflow/exceptions.py +8 -0
- autobyteus/workflow/factory/__init__.py +9 -0
- autobyteus/workflow/factory/workflow_factory.py +99 -0
- autobyteus/workflow/handlers/__init__.py +19 -0
- autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
- autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
- autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
- autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
- autobyteus/workflow/phases/__init__.py +11 -0
- autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
- autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
- autobyteus/workflow/runtime/__init__.py +13 -0
- autobyteus/workflow/runtime/workflow_runtime.py +82 -0
- autobyteus/workflow/runtime/workflow_worker.py +117 -0
- autobyteus/workflow/shutdown_steps/__init__.py +17 -0
- autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
- autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
- autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
- autobyteus/workflow/streaming/__init__.py +26 -0
- autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
- autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
- autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
- autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
- autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
- autobyteus/workflow/utils/__init__.py +9 -0
- autobyteus/workflow/utils/wait_for_idle.py +46 -0
- autobyteus/workflow/workflow_builder.py +151 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/METADATA +16 -14
- autobyteus-1.1.5.dist-info/RECORD +455 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/agent_team/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +262 -0
- examples/run_google_slides_agent.py +287 -0
- examples/run_mcp_browser_client.py +174 -0
- examples/run_mcp_google_slides_client.py +270 -0
- examples/run_mcp_list_tools.py +189 -0
- examples/run_poem_writer.py +284 -0
- examples/run_sqlite_agent.py +295 -0
- autobyteus/agent/context/agent_phase_manager.py +0 -264
- autobyteus/agent/context/phases.py +0 -49
- autobyteus/agent/group/__init__.py +0 -0
- autobyteus/agent/group/agent_group.py +0 -164
- autobyteus/agent/group/agent_group_context.py +0 -81
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
- autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
- autobyteus/agent/workflow/__init__.py +0 -11
- autobyteus/agent/workflow/agentic_workflow.py +0 -89
- autobyteus/tools/mcp/call_handlers/__init__.py +0 -16
- autobyteus/tools/mcp/call_handlers/base_handler.py +0 -40
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +0 -76
- autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +0 -55
- autobyteus/tools/mcp/registrar.py +0 -202
- autobyteus/tools/usage/providers/json_example_provider.py +0 -32
- autobyteus/tools/usage/providers/json_schema_provider.py +0 -35
- autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +0 -28
- autobyteus/tools/usage/providers/xml_example_provider.py +0 -28
- autobyteus/tools/usage/providers/xml_schema_provider.py +0 -29
- autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +0 -26
- autobyteus/tools/usage/registries/json_example_formatter_registry.py +0 -51
- autobyteus/tools/usage/registries/json_schema_formatter_registry.py +0 -51
- autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +0 -42
- autobyteus/tools/usage/registries/xml_example_formatter_registry.py +0 -30
- autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +0 -33
- autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +0 -30
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- autobyteus-1.1.3.dist-info/RECORD +0 -312
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.5.dist-info}/WHEEL +0 -0
- {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"))
|
autobyteus/cli/cli_display.py
CHANGED
|
@@ -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.
|
|
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,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
|