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,117 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/runtime/agent_team_worker.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import concurrent.futures
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Callable, Awaitable, Any
|
|
6
|
+
|
|
7
|
+
from autobyteus.agent_team.events.agent_team_event_dispatcher import AgentTeamEventDispatcher
|
|
8
|
+
from autobyteus.agent_team.bootstrap_steps.agent_team_bootstrapper import AgentTeamBootstrapper
|
|
9
|
+
from autobyteus.agent_team.shutdown_steps.agent_team_shutdown_orchestrator import AgentTeamShutdownOrchestrator
|
|
10
|
+
from autobyteus.agent.runtime.agent_thread_pool_manager import AgentThreadPoolManager
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
14
|
+
from autobyteus.agent_team.handlers.agent_team_event_handler_registry import AgentTeamEventHandlerRegistry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
class AgentTeamWorker:
|
|
19
|
+
"""Encapsulates the core event processing loop for an agent team."""
|
|
20
|
+
def __init__(self, context: 'AgentTeamContext', event_handler_registry: 'AgentTeamEventHandlerRegistry'):
|
|
21
|
+
self.context = context
|
|
22
|
+
self.phase_manager = self.context.phase_manager
|
|
23
|
+
self.event_dispatcher = AgentTeamEventDispatcher(event_handler_registry, self.phase_manager)
|
|
24
|
+
|
|
25
|
+
self._thread_pool_manager = AgentThreadPoolManager()
|
|
26
|
+
self._thread_future: Optional[concurrent.futures.Future] = None
|
|
27
|
+
self._worker_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
28
|
+
self._async_stop_event: Optional[asyncio.Event] = None
|
|
29
|
+
self._is_active: bool = False
|
|
30
|
+
self._stop_initiated: bool = False
|
|
31
|
+
self._done_callbacks: list[Callable[[concurrent.futures.Future], None]] = []
|
|
32
|
+
logger.info(f"AgentTeamWorker initialized for team '{self.context.team_id}'.")
|
|
33
|
+
|
|
34
|
+
def get_worker_loop(self) -> Optional[asyncio.AbstractEventLoop]:
|
|
35
|
+
"""Returns the worker's event loop if it's running."""
|
|
36
|
+
return self._worker_loop if self._worker_loop and self._worker_loop.is_running() else None
|
|
37
|
+
|
|
38
|
+
def add_done_callback(self, callback: Callable):
|
|
39
|
+
if self._thread_future:
|
|
40
|
+
self._thread_future.add_done_callback(callback)
|
|
41
|
+
else:
|
|
42
|
+
self._done_callbacks.append(callback)
|
|
43
|
+
|
|
44
|
+
def schedule_coroutine(self, coro_factory: Callable[[], Awaitable[Any]]) -> concurrent.futures.Future:
|
|
45
|
+
if not self._worker_loop:
|
|
46
|
+
raise RuntimeError("AgentTeamWorker loop is not available.")
|
|
47
|
+
return asyncio.run_coroutine_threadsafe(coro_factory(), self._worker_loop)
|
|
48
|
+
|
|
49
|
+
def start(self):
|
|
50
|
+
if self._is_active:
|
|
51
|
+
return
|
|
52
|
+
self._is_active = True
|
|
53
|
+
self._thread_future = self._thread_pool_manager.submit_task(self._run_managed_loop)
|
|
54
|
+
for cb in self._done_callbacks:
|
|
55
|
+
self._thread_future.add_done_callback(cb)
|
|
56
|
+
self._done_callbacks.clear()
|
|
57
|
+
|
|
58
|
+
def _run_managed_loop(self):
|
|
59
|
+
try:
|
|
60
|
+
self._worker_loop = asyncio.new_event_loop()
|
|
61
|
+
asyncio.set_event_loop(self._worker_loop)
|
|
62
|
+
self._async_stop_event = asyncio.Event()
|
|
63
|
+
self._worker_loop.run_until_complete(self.async_run())
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"AgentTeamWorker '{self.context.team_id}' event loop crashed: {e}", exc_info=True)
|
|
66
|
+
finally:
|
|
67
|
+
if self._worker_loop:
|
|
68
|
+
self._worker_loop.close()
|
|
69
|
+
self._is_active = False
|
|
70
|
+
|
|
71
|
+
async def async_run(self):
|
|
72
|
+
bootstrapper = AgentTeamBootstrapper()
|
|
73
|
+
if not await bootstrapper.run(self.context, self.phase_manager):
|
|
74
|
+
logger.critical(f"Team '{self.context.team_id}' failed to initialize. Shutting down.")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
logger.info(f"Team '{self.context.team_id}' entering main event loop.")
|
|
78
|
+
while not self._async_stop_event.is_set():
|
|
79
|
+
try:
|
|
80
|
+
# Combine queues for a single wait point
|
|
81
|
+
user_message_task = asyncio.create_task(self.context.state.input_event_queues.user_message_queue.get())
|
|
82
|
+
system_task = asyncio.create_task(self.context.state.input_event_queues.internal_system_event_queue.get())
|
|
83
|
+
done, pending = await asyncio.wait([user_message_task, system_task], return_when=asyncio.FIRST_COMPLETED, timeout=0.2)
|
|
84
|
+
|
|
85
|
+
for task in pending:
|
|
86
|
+
task.cancel()
|
|
87
|
+
|
|
88
|
+
if not done:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
event = done.pop().result()
|
|
92
|
+
await self.event_dispatcher.dispatch(event, self.context)
|
|
93
|
+
|
|
94
|
+
except asyncio.TimeoutError:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
logger.info(f"Team '{self.context.team_id}' shutdown signal received. Cleaning up.")
|
|
98
|
+
shutdown_orchestrator = AgentTeamShutdownOrchestrator()
|
|
99
|
+
await shutdown_orchestrator.run(self.context)
|
|
100
|
+
|
|
101
|
+
async def stop(self, timeout: float = 10.0):
|
|
102
|
+
if not self._is_active or self._stop_initiated:
|
|
103
|
+
return
|
|
104
|
+
self._stop_initiated = True
|
|
105
|
+
if self._worker_loop:
|
|
106
|
+
self._worker_loop.call_soon_threadsafe(self._async_stop_event.set)
|
|
107
|
+
if self._thread_future:
|
|
108
|
+
try:
|
|
109
|
+
# FIX: Use asyncio.wait_for() to handle the timeout correctly.
|
|
110
|
+
await asyncio.wait_for(asyncio.wrap_future(self._thread_future), timeout=timeout)
|
|
111
|
+
except asyncio.TimeoutError:
|
|
112
|
+
logger.warning(f"Timeout waiting for team worker '{self.context.team_id}' to terminate.")
|
|
113
|
+
self._is_active = False
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def is_alive(self) -> bool:
|
|
117
|
+
return self._thread_future is not None and not self._thread_future.done()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Defines individual, self-contained steps for the agent team shutdown process.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.agent_team.shutdown_steps.base_agent_team_shutdown_step import BaseAgentTeamShutdownStep
|
|
6
|
+
from autobyteus.agent_team.shutdown_steps.agent_team_shutdown_step import AgentTeamShutdownStep
|
|
7
|
+
from autobyteus.agent_team.shutdown_steps.sub_team_shutdown_step import SubTeamShutdownStep
|
|
8
|
+
from autobyteus.agent_team.shutdown_steps.bridge_cleanup_step import BridgeCleanupStep
|
|
9
|
+
from autobyteus.agent_team.shutdown_steps.agent_team_shutdown_orchestrator import AgentTeamShutdownOrchestrator
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BaseAgentTeamShutdownStep",
|
|
13
|
+
"AgentTeamShutdownStep",
|
|
14
|
+
"SubTeamShutdownStep",
|
|
15
|
+
"BridgeCleanupStep",
|
|
16
|
+
"AgentTeamShutdownOrchestrator",
|
|
17
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/agent_team_shutdown_orchestrator.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.shutdown_steps.base_agent_team_shutdown_step import BaseAgentTeamShutdownStep
|
|
6
|
+
from autobyteus.agent_team.shutdown_steps.bridge_cleanup_step import BridgeCleanupStep
|
|
7
|
+
from autobyteus.agent_team.shutdown_steps.sub_team_shutdown_step import SubTeamShutdownStep
|
|
8
|
+
from autobyteus.agent_team.shutdown_steps.agent_team_shutdown_step import AgentTeamShutdownStep
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class AgentTeamShutdownOrchestrator:
|
|
16
|
+
"""Orchestrates the agent team's shutdown process."""
|
|
17
|
+
def __init__(self, steps: Optional[List[BaseAgentTeamShutdownStep]] = None):
|
|
18
|
+
self.shutdown_steps = steps or [
|
|
19
|
+
BridgeCleanupStep(),
|
|
20
|
+
SubTeamShutdownStep(),
|
|
21
|
+
AgentTeamShutdownStep(),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
async def run(self, context: 'AgentTeamContext') -> bool:
|
|
25
|
+
team_id = context.team_id
|
|
26
|
+
logger.info(f"Team '{team_id}': Shutdown orchestrator starting.")
|
|
27
|
+
|
|
28
|
+
all_successful = True
|
|
29
|
+
for step in self.shutdown_steps:
|
|
30
|
+
if not await step.execute(context):
|
|
31
|
+
logger.error(f"Team '{team_id}': Shutdown step {step.__class__.__name__} failed.")
|
|
32
|
+
all_successful = False
|
|
33
|
+
|
|
34
|
+
logger.info(f"Team '{team_id}': Shutdown orchestration completed.")
|
|
35
|
+
return all_successful
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/agent_team_shutdown_step.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from autobyteus.agent_team.shutdown_steps.base_agent_team_shutdown_step import BaseAgentTeamShutdownStep
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class AgentTeamShutdownStep(BaseAgentTeamShutdownStep):
|
|
14
|
+
"""Shutdown step to gracefully stop all running agents in the team."""
|
|
15
|
+
async def execute(self, context: 'AgentTeamContext') -> bool:
|
|
16
|
+
team_id = context.team_id
|
|
17
|
+
logger.info(f"Team '{team_id}': Executing AgentTeamShutdownStep.")
|
|
18
|
+
|
|
19
|
+
team_manager = context.team_manager
|
|
20
|
+
if not team_manager:
|
|
21
|
+
logger.warning(f"Team '{team_id}': No TeamManager found, cannot shut down agents.")
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
# Get the list of all created agents from the single source of truth.
|
|
25
|
+
all_agents = team_manager.get_all_agents()
|
|
26
|
+
running_agents = [agent for agent in all_agents if agent.is_running]
|
|
27
|
+
|
|
28
|
+
if not running_agents:
|
|
29
|
+
logger.info(f"Team '{team_id}': No running agents to shut down.")
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
logger.info(f"Team '{team_id}': Shutting down {len(running_agents)} running agents.")
|
|
33
|
+
stop_tasks = [agent.stop(timeout=10.0) for agent in running_agents]
|
|
34
|
+
results = await asyncio.gather(*stop_tasks, return_exceptions=True)
|
|
35
|
+
|
|
36
|
+
all_successful = True
|
|
37
|
+
for agent, result in zip(running_agents, results):
|
|
38
|
+
if isinstance(result, Exception):
|
|
39
|
+
logger.error(f"Team '{team_id}': Error stopping agent '{agent.agent_id}': {result}", exc_info=result)
|
|
40
|
+
all_successful = False
|
|
41
|
+
|
|
42
|
+
return all_successful
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/base_agent_team_shutdown_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class BaseAgentTeamShutdownStep(ABC):
|
|
12
|
+
"""Abstract base class for individual steps in the agent team shutdown process."""
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def execute(self, context: 'AgentTeamContext') -> bool:
|
|
15
|
+
"""Executes the shutdown step."""
|
|
16
|
+
raise NotImplementedError("Subclasses must implement the 'execute' method.")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/bridge_cleanup_step.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.shutdown_steps.base_agent_team_shutdown_step import BaseAgentTeamShutdownStep
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class BridgeCleanupStep(BaseAgentTeamShutdownStep):
|
|
13
|
+
"""Shutdown step to gracefully stop all AgentEventBridge instances via the multiplexer."""
|
|
14
|
+
async def execute(self, context: 'AgentTeamContext') -> bool:
|
|
15
|
+
team_id = context.team_id
|
|
16
|
+
logger.info(f"Team '{team_id}': Executing BridgeCleanupStep.")
|
|
17
|
+
|
|
18
|
+
multiplexer = context.multiplexer
|
|
19
|
+
if not multiplexer:
|
|
20
|
+
logger.warning(f"Team '{team_id}': No AgentEventMultiplexer found, cannot shut down event bridges.")
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
await multiplexer.shutdown()
|
|
25
|
+
return True
|
|
26
|
+
except Exception as e:
|
|
27
|
+
logger.error(f"Team '{team_id}': Error shutting down agent event bridges via multiplexer: {e}", exc_info=True)
|
|
28
|
+
return False
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/shutdown_steps/sub_team_shutdown_step.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from autobyteus.agent_team.shutdown_steps.base_agent_team_shutdown_step import BaseAgentTeamShutdownStep
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class SubTeamShutdownStep(BaseAgentTeamShutdownStep):
|
|
14
|
+
"""Shutdown step to gracefully stop all running sub-teams."""
|
|
15
|
+
async def execute(self, context: 'AgentTeamContext') -> bool:
|
|
16
|
+
team_id = context.team_id
|
|
17
|
+
logger.info(f"Team '{team_id}': Executing SubTeamShutdownStep.")
|
|
18
|
+
|
|
19
|
+
team_manager = context.team_manager
|
|
20
|
+
if not team_manager:
|
|
21
|
+
logger.warning(f"Team '{team_id}': No TeamManager found, cannot shut down sub-teams.")
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
all_sub_teams = team_manager.get_all_sub_teams()
|
|
25
|
+
running_sub_teams = [wf for wf in all_sub_teams if wf.is_running]
|
|
26
|
+
|
|
27
|
+
if not running_sub_teams:
|
|
28
|
+
logger.info(f"Team '{team_id}': No running sub-teams to shut down.")
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
logger.info(f"Team '{team_id}': Shutting down {len(running_sub_teams)} running sub-teams.")
|
|
32
|
+
stop_tasks = [team.stop(timeout=20.0) for team in running_sub_teams]
|
|
33
|
+
results = await asyncio.gather(*stop_tasks, return_exceptions=True)
|
|
34
|
+
|
|
35
|
+
all_successful = True
|
|
36
|
+
for team, result in zip(running_sub_teams, results):
|
|
37
|
+
if isinstance(result, Exception):
|
|
38
|
+
logger.error(f"Team '{team_id}': Error stopping sub-team '{team.name}': {result}", exc_info=result)
|
|
39
|
+
all_successful = False
|
|
40
|
+
|
|
41
|
+
return all_successful
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Components related to agent team output streaming.
|
|
4
|
+
"""
|
|
5
|
+
from .agent_team_event_notifier import AgentTeamExternalEventNotifier
|
|
6
|
+
from .agent_team_event_stream import AgentTeamEventStream
|
|
7
|
+
from .agent_team_stream_events import AgentTeamStreamEvent, AgentTeamStreamDataPayload
|
|
8
|
+
from .agent_team_stream_event_payloads import (
|
|
9
|
+
BaseTeamSpecificPayload,
|
|
10
|
+
AgentTeamPhaseTransitionData,
|
|
11
|
+
AgentEventRebroadcastPayload,
|
|
12
|
+
)
|
|
13
|
+
from .agent_event_bridge import AgentEventBridge
|
|
14
|
+
from .agent_event_multiplexer import AgentEventMultiplexer
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentTeamExternalEventNotifier",
|
|
18
|
+
"AgentTeamEventStream",
|
|
19
|
+
"AgentTeamStreamEvent",
|
|
20
|
+
"AgentTeamStreamDataPayload",
|
|
21
|
+
"BaseTeamSpecificPayload",
|
|
22
|
+
"AgentTeamPhaseTransitionData",
|
|
23
|
+
"AgentEventRebroadcastPayload",
|
|
24
|
+
"AgentEventBridge",
|
|
25
|
+
"AgentEventMultiplexer",
|
|
26
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_event_bridge.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from autobyteus.agent.streaming.agent_event_stream import AgentEventStream
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.agent.agent import Agent
|
|
10
|
+
from autobyteus.agent_team.streaming.agent_team_event_notifier import AgentTeamExternalEventNotifier
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class AgentEventBridge:
|
|
15
|
+
"""
|
|
16
|
+
A dedicated component that bridges events from a single agent's event stream
|
|
17
|
+
to the main team notifier. This is the core of the multiplexing pattern.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, agent: 'Agent', agent_name: str, notifier: 'AgentTeamExternalEventNotifier', loop: asyncio.AbstractEventLoop):
|
|
20
|
+
self._agent_name = agent_name
|
|
21
|
+
self._notifier = notifier
|
|
22
|
+
self._stream = AgentEventStream(agent)
|
|
23
|
+
self._task: asyncio.Task = loop.create_task(self._run())
|
|
24
|
+
logger.info(f"AgentEventBridge created and task started for agent '{agent_name}'.")
|
|
25
|
+
|
|
26
|
+
async def _run(self):
|
|
27
|
+
"""The background task that consumes from the stream and publishes to the notifier."""
|
|
28
|
+
try:
|
|
29
|
+
async for event in self._stream.all_events():
|
|
30
|
+
self._notifier.publish_agent_event(self._agent_name, event)
|
|
31
|
+
except asyncio.CancelledError:
|
|
32
|
+
logger.info(f"AgentEventBridge task for '{self._agent_name}' was cancelled.")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logger.error(f"Error in AgentEventBridge for '{self._agent_name}': {e}", exc_info=True)
|
|
35
|
+
finally:
|
|
36
|
+
logger.debug(f"AgentEventBridge task for '{self._agent_name}' is finishing.")
|
|
37
|
+
|
|
38
|
+
async def cancel(self):
|
|
39
|
+
"""Gracefully stops the bridge."""
|
|
40
|
+
logger.info(f"Cancelling AgentEventBridge for '{self._agent_name}'.")
|
|
41
|
+
if not self._task.done():
|
|
42
|
+
self._task.cancel()
|
|
43
|
+
try:
|
|
44
|
+
await self._task
|
|
45
|
+
except asyncio.CancelledError:
|
|
46
|
+
pass # Expected
|
|
47
|
+
await self._stream.close()
|
|
48
|
+
logger.info(f"AgentEventBridge for '{self._agent_name}' cancelled successfully.")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_event_multiplexer.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from autobyteus.agent_team.streaming.agent_event_bridge import AgentEventBridge
|
|
7
|
+
from autobyteus.agent_team.streaming.team_event_bridge import TeamEventBridge
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from autobyteus.agent.agent import Agent
|
|
11
|
+
from autobyteus.agent_team.agent_team import AgentTeam
|
|
12
|
+
from autobyteus.agent_team.streaming.agent_team_event_notifier import AgentTeamExternalEventNotifier
|
|
13
|
+
from autobyteus.agent_team.runtime.agent_team_worker import AgentTeamWorker
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class AgentEventMultiplexer:
|
|
18
|
+
"""
|
|
19
|
+
Manages the lifecycle of event bridges for all nodes (agents and sub-teams).
|
|
20
|
+
It creates, tracks, and shuts down the bridges that forward node events
|
|
21
|
+
to the agent team's main event stream.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, team_id: str, notifier: 'AgentTeamExternalEventNotifier', worker_ref: 'AgentTeamWorker'):
|
|
24
|
+
self._team_id = team_id
|
|
25
|
+
self._notifier = notifier
|
|
26
|
+
self._worker = worker_ref
|
|
27
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
28
|
+
self._agent_bridges: Dict[str, AgentEventBridge] = {}
|
|
29
|
+
self._team_bridges: Dict[str, TeamEventBridge] = {}
|
|
30
|
+
logger.info(f"AgentEventMultiplexer initialized for team '{self._team_id}'.")
|
|
31
|
+
|
|
32
|
+
def _get_loop(self) -> asyncio.AbstractEventLoop:
|
|
33
|
+
"""Retrieves the event loop from the worker on-demand."""
|
|
34
|
+
if self._loop is None or self._loop.is_closed():
|
|
35
|
+
self._loop = self._worker.get_worker_loop()
|
|
36
|
+
if self._loop is None:
|
|
37
|
+
raise RuntimeError(f"Agent team worker loop for team '{self._team_id}' is not available or not running.")
|
|
38
|
+
return self._loop
|
|
39
|
+
|
|
40
|
+
def start_bridging_agent_events(self, agent: 'Agent', agent_name: str):
|
|
41
|
+
"""Creates and starts an AgentEventBridge for a direct agent node."""
|
|
42
|
+
if agent_name in self._agent_bridges:
|
|
43
|
+
logger.warning(f"Event bridge for agent '{agent_name}' already exists. Skipping creation.")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
bridge = AgentEventBridge(agent=agent, agent_name=agent_name, notifier=self._notifier, loop=self._get_loop())
|
|
47
|
+
self._agent_bridges[agent_name] = bridge
|
|
48
|
+
logger.info(f"AgentEventMultiplexer started agent event bridge for '{agent_name}'.")
|
|
49
|
+
|
|
50
|
+
def start_bridging_team_events(self, sub_team: 'AgentTeam', node_name: str):
|
|
51
|
+
"""Creates and starts a TeamEventBridge for a sub-team node."""
|
|
52
|
+
if node_name in self._team_bridges:
|
|
53
|
+
logger.warning(f"Event bridge for sub-team '{node_name}' already exists. Skipping creation.")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
bridge = TeamEventBridge(sub_team=sub_team, sub_team_node_name=node_name, parent_notifier=self._notifier, loop=self._get_loop())
|
|
57
|
+
self._team_bridges[node_name] = bridge
|
|
58
|
+
logger.info(f"AgentEventMultiplexer started team event bridge for '{node_name}'.")
|
|
59
|
+
|
|
60
|
+
async def shutdown(self):
|
|
61
|
+
"""Gracefully shuts down all active event bridges."""
|
|
62
|
+
logger.info(f"AgentEventMultiplexer for '{self._team_id}' shutting down all event bridges.")
|
|
63
|
+
agent_bridge_tasks = [b.cancel() for b in self._agent_bridges.values()]
|
|
64
|
+
team_bridge_tasks = [b.cancel() for b in self._team_bridges.values()]
|
|
65
|
+
|
|
66
|
+
await asyncio.gather(*(agent_bridge_tasks + team_bridge_tasks), return_exceptions=True)
|
|
67
|
+
|
|
68
|
+
self._agent_bridges.clear()
|
|
69
|
+
self._team_bridges.clear()
|
|
70
|
+
logger.info(f"All event bridges for team '{self._team_id}' have been shut down by multiplexer.")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_team_event_notifier.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, Dict, Any, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.events.event_emitter import EventEmitter
|
|
6
|
+
from autobyteus.events.event_types import EventType
|
|
7
|
+
from autobyteus.agent_team.phases.agent_team_operational_phase import AgentTeamOperationalPhase
|
|
8
|
+
from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent
|
|
9
|
+
from .agent_team_stream_events import AgentTeamStreamEvent, AgentEventRebroadcastPayload, AgentTeamPhaseTransitionData, SubTeamEventRebroadcastPayload
|
|
10
|
+
from autobyteus.task_management.events import BaseTaskBoardEvent
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.agent_team.runtime.agent_team_runtime import AgentTeamRuntime
|
|
14
|
+
from autobyteus.agent_team.streaming.agent_team_stream_events import AgentTeamStreamEvent as AgentTeamStreamEventTypeHint
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
class AgentTeamExternalEventNotifier(EventEmitter):
|
|
19
|
+
"""
|
|
20
|
+
Responsible for emitting unified AgentTeamStreamEvents for consumption by
|
|
21
|
+
external listeners (like a UI or the AgentTeamEventStream).
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, team_id: str, runtime_ref: 'AgentTeamRuntime'):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.team_id = team_id
|
|
26
|
+
self.runtime_ref = runtime_ref
|
|
27
|
+
logger.debug(f"AgentTeamExternalEventNotifier initialized for team '{self.team_id}'.")
|
|
28
|
+
|
|
29
|
+
def _emit_event(self, event: 'AgentTeamStreamEventTypeHint'):
|
|
30
|
+
"""
|
|
31
|
+
Emits a fully-formed AgentTeamStreamEvent.
|
|
32
|
+
"""
|
|
33
|
+
self.emit(EventType.TEAM_STREAM_EVENT, payload=event)
|
|
34
|
+
|
|
35
|
+
def notify_phase_change(self, new_phase: AgentTeamOperationalPhase, old_phase: Optional[AgentTeamOperationalPhase], extra_data: Optional[Dict[str, Any]] = None):
|
|
36
|
+
"""
|
|
37
|
+
Notifies of an agent team phase transition by creating and emitting a
|
|
38
|
+
'TEAM' sourced event.
|
|
39
|
+
"""
|
|
40
|
+
payload_dict = { "new_phase": new_phase, "old_phase": old_phase, "error_message": extra_data.get("error_message") if extra_data else None }
|
|
41
|
+
filtered_payload_dict = {k: v for k, v in payload_dict.items() if v is not None}
|
|
42
|
+
event = AgentTeamStreamEvent(team_id=self.team_id, event_source_type="TEAM", data=AgentTeamPhaseTransitionData(**filtered_payload_dict))
|
|
43
|
+
self._emit_event(event)
|
|
44
|
+
|
|
45
|
+
def publish_agent_event(self, agent_name: str, agent_event: AgentStreamEvent):
|
|
46
|
+
"""
|
|
47
|
+
Wraps an event from a direct member agent and publishes it on the main team stream.
|
|
48
|
+
"""
|
|
49
|
+
event = AgentTeamStreamEvent(team_id=self.team_id, event_source_type="AGENT", data=AgentEventRebroadcastPayload(agent_name=agent_name, agent_event=agent_event))
|
|
50
|
+
self._emit_event(event)
|
|
51
|
+
|
|
52
|
+
def publish_sub_team_event(self, sub_team_node_name: str, sub_team_event: 'AgentTeamStreamEventTypeHint'):
|
|
53
|
+
"""
|
|
54
|
+
Wraps an event from a sub-team and publishes it on the parent team stream.
|
|
55
|
+
"""
|
|
56
|
+
event = AgentTeamStreamEvent(team_id=self.team_id, event_source_type="SUB_TEAM", data=SubTeamEventRebroadcastPayload(sub_team_node_name=sub_team_node_name, sub_team_event=sub_team_event))
|
|
57
|
+
self._emit_event(event)
|
|
58
|
+
|
|
59
|
+
def handle_and_publish_task_board_event(self, payload: BaseTaskBoardEvent, **kwargs):
|
|
60
|
+
"""
|
|
61
|
+
Listener for TaskBoard events. It wraps the event and publishes it on the main team stream.
|
|
62
|
+
"""
|
|
63
|
+
event = AgentTeamStreamEvent(team_id=self.team_id, event_source_type="TASK_BOARD", data=payload)
|
|
64
|
+
self._emit_event(event)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_team_event_stream.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import queue
|
|
4
|
+
from typing import AsyncIterator, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from autobyteus.events.event_types import EventType
|
|
7
|
+
from autobyteus.agent_team.streaming.agent_team_stream_events import AgentTeamStreamEvent
|
|
8
|
+
from autobyteus.agent.streaming.queue_streamer import stream_queue_items
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.agent_team.agent_team import AgentTeam
|
|
12
|
+
|
|
13
|
+
_SENTINEL = object()
|
|
14
|
+
|
|
15
|
+
class AgentTeamEventStream:
|
|
16
|
+
"""Consumes events from an AgentTeamExternalEventNotifier for a specific team."""
|
|
17
|
+
def __init__(self, team: 'AgentTeam'):
|
|
18
|
+
self.team_id = team.team_id
|
|
19
|
+
self._internal_q = queue.Queue()
|
|
20
|
+
self._notifier = team._runtime.notifier
|
|
21
|
+
self._notifier.subscribe(EventType.TEAM_STREAM_EVENT, self._handle_event)
|
|
22
|
+
|
|
23
|
+
def _handle_event(self, payload: AgentTeamStreamEvent, **kwargs):
|
|
24
|
+
if isinstance(payload, AgentTeamStreamEvent) and payload.team_id == self.team_id:
|
|
25
|
+
self._internal_q.put(payload)
|
|
26
|
+
|
|
27
|
+
async def close(self):
|
|
28
|
+
self._notifier.unsubscribe(EventType.TEAM_STREAM_EVENT, self._handle_event)
|
|
29
|
+
await asyncio.get_running_loop().run_in_executor(None, self._internal_q.put, _SENTINEL)
|
|
30
|
+
|
|
31
|
+
def all_events(self) -> AsyncIterator[AgentTeamStreamEvent]:
|
|
32
|
+
"""The primary method to consume all structured events from the agent team."""
|
|
33
|
+
return stream_queue_items(self._internal_q, _SENTINEL, f"team_{self.team_id}_stream")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from autobyteus.agent_team.phases.agent_team_operational_phase import AgentTeamOperationalPhase
|
|
5
|
+
from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent
|
|
6
|
+
from autobyteus.task_management.events import TaskPlanPublishedEvent, TaskStatusUpdatedEvent
|
|
7
|
+
# Need to use a forward reference string to avoid circular import at runtime
|
|
8
|
+
from typing import TYPE_CHECKING, Union
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from autobyteus.agent_team.streaming.agent_team_stream_events import AgentTeamStreamEvent
|
|
11
|
+
|
|
12
|
+
# --- Payloads for events originating from the "TEAM" source ---
|
|
13
|
+
class BaseTeamSpecificPayload(BaseModel):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
class AgentTeamPhaseTransitionData(BaseTeamSpecificPayload):
|
|
17
|
+
new_phase: AgentTeamOperationalPhase
|
|
18
|
+
old_phase: Optional[AgentTeamOperationalPhase] = None
|
|
19
|
+
error_message: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
# --- Payload for events originating from the "AGENT" source ---
|
|
22
|
+
class AgentEventRebroadcastPayload(BaseModel):
|
|
23
|
+
agent_name: str # The friendly name, e.g., "Researcher_1"
|
|
24
|
+
agent_event: AgentStreamEvent # The original, unmodified event from the agent
|
|
25
|
+
|
|
26
|
+
# --- Payload for events originating from the "SUB_TEAM" source ---
|
|
27
|
+
class SubTeamEventRebroadcastPayload(BaseModel):
|
|
28
|
+
sub_team_node_name: str # The friendly name of the sub-team node
|
|
29
|
+
sub_team_event: "AgentTeamStreamEvent" = Field(..., description="The original, unmodified event from the sub-team's stream")
|
|
30
|
+
|
|
31
|
+
# --- Payload for events originating from the "TASK_BOARD" source ---
|
|
32
|
+
TaskBoardEventPayload = Union[TaskPlanPublishedEvent, TaskStatusUpdatedEvent]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/streaming/agent_team_stream_events.py
|
|
2
|
+
import datetime
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Literal, Union
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
from .agent_team_stream_event_payloads import (
|
|
8
|
+
AgentTeamPhaseTransitionData, AgentEventRebroadcastPayload,
|
|
9
|
+
SubTeamEventRebroadcastPayload, TaskBoardEventPayload
|
|
10
|
+
)
|
|
11
|
+
from autobyteus.task_management.events import BaseTaskBoardEvent
|
|
12
|
+
|
|
13
|
+
# A union of all possible payloads for a "TEAM" sourced event.
|
|
14
|
+
TeamSpecificPayload = Union[AgentTeamPhaseTransitionData]
|
|
15
|
+
|
|
16
|
+
# The top-level discriminated union for the main event stream's payload.
|
|
17
|
+
AgentTeamStreamDataPayload = Union[TeamSpecificPayload, AgentEventRebroadcastPayload, SubTeamEventRebroadcastPayload, TaskBoardEventPayload]
|
|
18
|
+
|
|
19
|
+
class AgentTeamStreamEvent(BaseModel):
|
|
20
|
+
event_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
21
|
+
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
|
|
22
|
+
team_id: str
|
|
23
|
+
event_source_type: Literal["TEAM", "AGENT", "SUB_TEAM", "TASK_BOARD"]
|
|
24
|
+
data: AgentTeamStreamDataPayload
|
|
25
|
+
|
|
26
|
+
@model_validator(mode='after')
|
|
27
|
+
def check_data_matches_source_type(self) -> 'AgentTeamStreamEvent':
|
|
28
|
+
is_agent_event = self.event_source_type == "AGENT"
|
|
29
|
+
is_agent_payload = isinstance(self.data, AgentEventRebroadcastPayload)
|
|
30
|
+
|
|
31
|
+
is_sub_team_event = self.event_source_type == "SUB_TEAM"
|
|
32
|
+
is_sub_team_payload = isinstance(self.data, SubTeamEventRebroadcastPayload)
|
|
33
|
+
|
|
34
|
+
is_team_event = self.event_source_type == "TEAM"
|
|
35
|
+
is_team_payload = isinstance(self.data, AgentTeamPhaseTransitionData)
|
|
36
|
+
|
|
37
|
+
is_task_board_event = self.event_source_type == "TASK_BOARD"
|
|
38
|
+
is_task_board_payload = isinstance(self.data, BaseTaskBoardEvent)
|
|
39
|
+
|
|
40
|
+
if is_agent_event and not is_agent_payload:
|
|
41
|
+
raise ValueError("event_source_type is 'AGENT' but data is not an AgentEventRebroadcastPayload")
|
|
42
|
+
|
|
43
|
+
if is_sub_team_event and not is_sub_team_payload:
|
|
44
|
+
raise ValueError("event_source_type is 'SUB_TEAM' but data is not a SubTeamEventRebroadcastPayload")
|
|
45
|
+
|
|
46
|
+
if is_team_event and not is_team_payload:
|
|
47
|
+
raise ValueError("event_source_type is 'TEAM' but data is not a valid team-specific payload")
|
|
48
|
+
|
|
49
|
+
if is_task_board_event and not is_task_board_payload:
|
|
50
|
+
raise ValueError("event_source_type is 'TASK_BOARD' but data is not a TaskBoardEventPayload")
|
|
51
|
+
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
# This is necessary for Pydantic v2 to correctly handle the recursive model
|
|
55
|
+
from .agent_team_stream_event_payloads import SubTeamEventRebroadcastPayload
|
|
56
|
+
SubTeamEventRebroadcastPayload.model_rebuild()
|