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,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,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)
|