autobyteus 1.1.4__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/context/__init__.py +4 -2
- autobyteus/agent/context/agent_config.py +0 -4
- autobyteus/agent/context/agent_context_registry.py +73 -0
- autobyteus/agent/events/notifiers.py +4 -0
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +7 -2
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +19 -19
- autobyteus/agent/handlers/user_input_message_event_handler.py +15 -0
- autobyteus/agent/message/send_message_to.py +29 -23
- autobyteus/agent/runtime/agent_runtime.py +10 -2
- 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_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/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/events/event_types.py +7 -2
- autobyteus/llm/api/autobyteus_llm.py +11 -12
- autobyteus/llm/api/lmstudio_llm.py +10 -13
- autobyteus/llm/api/ollama_llm.py +8 -13
- autobyteus/llm/autobyteus_provider.py +73 -46
- autobyteus/llm/llm_factory.py +102 -140
- autobyteus/llm/lmstudio_provider.py +63 -48
- 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 -13
- autobyteus/llm/runtimes.py +11 -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/bash/bash_executor.py +59 -14
- 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 +103 -50
- autobyteus/tools/parameter_schema.py +17 -11
- autobyteus/tools/registry/tool_definition.py +24 -29
- autobyteus/tools/tool_category.py +1 -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-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/METADATA +3 -3
- {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/RECORD +146 -72
- examples/agent_team/__init__.py +1 -0
- examples/run_browser_agent.py +17 -15
- examples/run_google_slides_agent.py +17 -16
- examples/run_poem_writer.py +22 -12
- examples/run_sqlite_agent.py +17 -15
- 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/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
- examples/workflow/__init__.py +0 -1
- examples/workflow/run_basic_research_workflow.py +0 -189
- examples/workflow/run_code_review_workflow.py +0 -269
- examples/workflow/run_debate_workflow.py +0 -212
- examples/workflow/run_workflow_with_tui.py +0 -153
- {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/factory/agent_team_factory.py
|
|
2
|
+
import logging
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Optional, Dict, List
|
|
5
|
+
|
|
6
|
+
from autobyteus.utils.singleton import SingletonMeta
|
|
7
|
+
from autobyteus.agent_team.agent_team import AgentTeam
|
|
8
|
+
from autobyteus.agent_team.context.agent_team_config import AgentTeamConfig
|
|
9
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
10
|
+
from autobyteus.agent_team.context.agent_team_runtime_state import AgentTeamRuntimeState
|
|
11
|
+
from autobyteus.agent_team.context.team_manager import TeamManager
|
|
12
|
+
from autobyteus.agent_team.runtime.agent_team_runtime import AgentTeamRuntime
|
|
13
|
+
from autobyteus.agent_team.handlers.agent_team_event_handler_registry import AgentTeamEventHandlerRegistry
|
|
14
|
+
from autobyteus.agent_team.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
|
|
15
|
+
from autobyteus.agent_team.handlers.lifecycle_agent_team_event_handler import LifecycleAgentTeamEventHandler
|
|
16
|
+
from autobyteus.agent_team.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
|
|
17
|
+
from autobyteus.agent_team.handlers.tool_approval_team_event_handler import ToolApprovalTeamEventHandler
|
|
18
|
+
from autobyteus.agent_team.events.agent_team_events import (
|
|
19
|
+
ProcessUserMessageEvent,
|
|
20
|
+
AgentTeamReadyEvent,
|
|
21
|
+
AgentTeamErrorEvent,
|
|
22
|
+
InterAgentMessageRequestEvent,
|
|
23
|
+
ToolApprovalTeamEvent
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
class AgentTeamFactory(metaclass=SingletonMeta):
|
|
29
|
+
"""
|
|
30
|
+
A singleton factory for creating and managing AgentTeam instances.
|
|
31
|
+
It orchestrates the assembly of all core agent team components.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._active_teams: Dict[str, AgentTeam] = {}
|
|
35
|
+
logger.info("AgentTeamFactory (Singleton) initialized.")
|
|
36
|
+
|
|
37
|
+
def _get_default_event_handler_registry(self) -> AgentTeamEventHandlerRegistry:
|
|
38
|
+
"""Returns a registry with default handlers for a new agent team."""
|
|
39
|
+
registry = AgentTeamEventHandlerRegistry()
|
|
40
|
+
registry.register(ProcessUserMessageEvent, ProcessUserMessageEventHandler())
|
|
41
|
+
registry.register(InterAgentMessageRequestEvent, InterAgentMessageRequestEventHandler())
|
|
42
|
+
registry.register(ToolApprovalTeamEvent, ToolApprovalTeamEventHandler())
|
|
43
|
+
lifecycle_handler = LifecycleAgentTeamEventHandler()
|
|
44
|
+
registry.register(AgentTeamReadyEvent, lifecycle_handler)
|
|
45
|
+
registry.register(AgentTeamErrorEvent, lifecycle_handler)
|
|
46
|
+
return registry
|
|
47
|
+
|
|
48
|
+
def create_team(self, config: AgentTeamConfig) -> AgentTeam:
|
|
49
|
+
"""
|
|
50
|
+
Creates a new agent team based on the provided AgentTeamConfig, stores it,
|
|
51
|
+
and returns its facade (AgentTeam).
|
|
52
|
+
"""
|
|
53
|
+
if not isinstance(config, AgentTeamConfig):
|
|
54
|
+
raise TypeError(f"Expected AgentTeamConfig instance, got {type(config).__name__}.")
|
|
55
|
+
|
|
56
|
+
team_id = f"team_{uuid.uuid4().hex[:8]}"
|
|
57
|
+
while team_id in self._active_teams:
|
|
58
|
+
team_id = f"team_{uuid.uuid4().hex[:8]}"
|
|
59
|
+
|
|
60
|
+
# --- Component Assembly as per new architecture ---
|
|
61
|
+
state = AgentTeamRuntimeState(team_id=team_id)
|
|
62
|
+
context = AgentTeamContext(team_id=team_id, config=config, state=state)
|
|
63
|
+
|
|
64
|
+
handler_registry = self._get_default_event_handler_registry()
|
|
65
|
+
runtime = AgentTeamRuntime(context=context, event_handler_registry=handler_registry)
|
|
66
|
+
|
|
67
|
+
team_manager = TeamManager(
|
|
68
|
+
team_id=team_id,
|
|
69
|
+
runtime=runtime,
|
|
70
|
+
multiplexer=runtime.multiplexer # Pass multiplexer created in runtime
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
context.state.team_manager = team_manager
|
|
74
|
+
|
|
75
|
+
team = AgentTeam(runtime=runtime)
|
|
76
|
+
|
|
77
|
+
self._active_teams[team_id] = team
|
|
78
|
+
logger.info(f"Agent Team '{team_id}' created and stored successfully.")
|
|
79
|
+
return team
|
|
80
|
+
|
|
81
|
+
def get_team(self, team_id: str) -> Optional[AgentTeam]:
|
|
82
|
+
"""Retrieves an active agent team instance by its ID."""
|
|
83
|
+
return self._active_teams.get(team_id)
|
|
84
|
+
|
|
85
|
+
async def remove_team(self, team_id: str, shutdown_timeout: float = 10.0) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Removes an agent team from the factory's management and gracefully stops it.
|
|
88
|
+
"""
|
|
89
|
+
team = self._active_teams.pop(team_id, None)
|
|
90
|
+
if team:
|
|
91
|
+
logger.info(f"Removing agent team '{team_id}'. Attempting graceful shutdown.")
|
|
92
|
+
await team.stop(timeout=shutdown_timeout)
|
|
93
|
+
return True
|
|
94
|
+
logger.warning(f"Agent team with ID '{team_id}' not found for removal.")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def list_active_team_ids(self) -> List[str]:
|
|
98
|
+
"""Returns a list of IDs of all active agent teams managed by this factory."""
|
|
99
|
+
return list(self._active_teams.keys())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Event handlers for the agent team runtime.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
6
|
+
from autobyteus.agent_team.handlers.lifecycle_agent_team_event_handler import LifecycleAgentTeamEventHandler
|
|
7
|
+
from autobyteus.agent_team.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
|
|
8
|
+
from autobyteus.agent_team.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
|
|
9
|
+
from autobyteus.agent_team.handlers.tool_approval_team_event_handler import ToolApprovalTeamEventHandler
|
|
10
|
+
from autobyteus.agent_team.handlers.agent_team_event_handler_registry import AgentTeamEventHandlerRegistry
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BaseAgentTeamEventHandler",
|
|
14
|
+
"LifecycleAgentTeamEventHandler",
|
|
15
|
+
"InterAgentMessageRequestEventHandler",
|
|
16
|
+
"ProcessUserMessageEventHandler",
|
|
17
|
+
"ToolApprovalTeamEventHandler",
|
|
18
|
+
"AgentTeamEventHandlerRegistry",
|
|
19
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/agent_team_event_handler_registry.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Type, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
6
|
+
from autobyteus.agent_team.events.agent_team_events import BaseAgentTeamEvent
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class AgentTeamEventHandlerRegistry:
|
|
11
|
+
"""Manages registration and retrieval of agent team event handlers."""
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._handlers: Dict[Type[BaseAgentTeamEvent], BaseAgentTeamEventHandler] = {}
|
|
14
|
+
logger.info("AgentTeamEventHandlerRegistry initialized.")
|
|
15
|
+
|
|
16
|
+
def register(self, event_class: Type[BaseAgentTeamEvent], handler_instance: BaseAgentTeamEventHandler):
|
|
17
|
+
if not issubclass(event_class, BaseAgentTeamEvent):
|
|
18
|
+
raise TypeError("Can only register handlers for BaseAgentTeamEvent subclasses.")
|
|
19
|
+
self._handlers[event_class] = handler_instance
|
|
20
|
+
logger.info(f"Handler '{type(handler_instance).__name__}' registered for event '{event_class.__name__}'.")
|
|
21
|
+
|
|
22
|
+
def get_handler(self, event_class: Type[BaseAgentTeamEvent]) -> Optional[BaseAgentTeamEventHandler]:
|
|
23
|
+
return self._handlers.get(event_class)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/base_agent_team_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, 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 BaseAgentTeamEventHandler(ABC):
|
|
12
|
+
"""Abstract base class for agent team event handlers."""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
async def handle(self, event: Any, context: 'AgentTeamContext') -> None:
|
|
16
|
+
raise NotImplementedError("Subclasses must implement the 'handle' method.")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
6
|
+
from autobyteus.agent_team.events.agent_team_events import InterAgentMessageRequestEvent
|
|
7
|
+
from autobyteus.agent.message.inter_agent_message import InterAgentMessage
|
|
8
|
+
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
9
|
+
from autobyteus.agent_team.agent_team import AgentTeam
|
|
10
|
+
from autobyteus.agent.agent import Agent
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class InterAgentMessageRequestEventHandler(BaseAgentTeamEventHandler):
|
|
18
|
+
"""
|
|
19
|
+
Handles requests to send messages between nodes (agents or sub-teams).
|
|
20
|
+
It relies on the TeamManager to handle on-demand startup of the recipient.
|
|
21
|
+
"""
|
|
22
|
+
async def handle(self, event: InterAgentMessageRequestEvent, context: 'AgentTeamContext') -> None:
|
|
23
|
+
team_id = context.team_id
|
|
24
|
+
team_manager = context.team_manager
|
|
25
|
+
|
|
26
|
+
if not team_manager:
|
|
27
|
+
logger.error(f"Team '{team_id}': TeamManager not found. Cannot route message from '{event.sender_agent_id}' to '{event.recipient_name}'.")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
target_node = await team_manager.ensure_node_is_ready(name_or_agent_id=event.recipient_name)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
msg = f"Recipient node '{event.recipient_name}' not found or failed to start for message from '{event.sender_agent_id}'. Error: {e}"
|
|
34
|
+
logger.error(f"Team '{team_id}': {msg}", exc_info=True)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if isinstance(target_node, AgentTeam):
|
|
39
|
+
# If target is a sub-team, post a user message to it.
|
|
40
|
+
# The sub-team will route it to its own coordinator.
|
|
41
|
+
message_for_team = AgentInputUserMessage(content=event.content)
|
|
42
|
+
await target_node.post_message(message_for_team)
|
|
43
|
+
logger.info(f"Team '{team_id}': Successfully posted message from '{event.sender_agent_id}' to sub-team '{event.recipient_name}'.")
|
|
44
|
+
|
|
45
|
+
elif isinstance(target_node, Agent):
|
|
46
|
+
# If target is a regular agent, create and post an InterAgentMessage.
|
|
47
|
+
message_for_agent = InterAgentMessage.create_with_dynamic_message_type(
|
|
48
|
+
recipient_role_name=target_node.context.config.role,
|
|
49
|
+
recipient_agent_id=target_node.agent_id,
|
|
50
|
+
content=event.content,
|
|
51
|
+
message_type=event.message_type,
|
|
52
|
+
sender_agent_id=event.sender_agent_id
|
|
53
|
+
)
|
|
54
|
+
await target_node.post_inter_agent_message(message_for_agent)
|
|
55
|
+
logger.info(f"Team '{team_id}': Successfully posted message from '{event.sender_agent_id}' to agent '{event.recipient_name}'.")
|
|
56
|
+
else:
|
|
57
|
+
raise TypeError(f"Target node '{event.recipient_name}' is of an unsupported type: {type(target_node).__name__}")
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
msg = f"Error posting message to node '{event.recipient_name}': {e}"
|
|
61
|
+
logger.error(f"Team '{team_id}': {msg}", exc_info=True)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
6
|
+
from autobyteus.agent_team.events.agent_team_events import BaseAgentTeamEvent, AgentTeamReadyEvent, AgentTeamErrorEvent
|
|
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 LifecycleAgentTeamEventHandler(BaseAgentTeamEventHandler):
|
|
14
|
+
"""Logs various lifecycle events for an agent team."""
|
|
15
|
+
async def handle(self, event: BaseAgentTeamEvent, context: 'AgentTeamContext') -> None:
|
|
16
|
+
team_id = context.team_id
|
|
17
|
+
current_phase = context.state.current_phase.value
|
|
18
|
+
|
|
19
|
+
if isinstance(event, AgentTeamReadyEvent):
|
|
20
|
+
logger.info(f"Team '{team_id}' Logged AgentTeamReadyEvent. Current phase: {current_phase}")
|
|
21
|
+
elif isinstance(event, AgentTeamErrorEvent):
|
|
22
|
+
logger.error(
|
|
23
|
+
f"Team '{team_id}' Logged AgentTeamErrorEvent: {event.error_message}. "
|
|
24
|
+
f"Details: {event.exception_details}. Current phase: {current_phase}"
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
logger.warning(f"LifecycleAgentTeamEventHandler received unhandled event type: {type(event).__name__}")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/process_user_message_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
5
|
+
from autobyteus.agent_team.events.agent_team_events import ProcessUserMessageEvent
|
|
6
|
+
from autobyteus.agent.agent import Agent
|
|
7
|
+
from autobyteus.agent_team.agent_team import AgentTeam
|
|
8
|
+
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
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 ProcessUserMessageEventHandler(BaseAgentTeamEventHandler):
|
|
16
|
+
"""Handles user messages by routing them to the specified target agent or sub-team."""
|
|
17
|
+
async def handle(self, event: ProcessUserMessageEvent, context: 'AgentTeamContext') -> None:
|
|
18
|
+
await context.phase_manager.notify_processing_started()
|
|
19
|
+
|
|
20
|
+
team_manager = context.team_manager
|
|
21
|
+
if not team_manager:
|
|
22
|
+
msg = f"Team '{context.team_id}': TeamManager not found. Cannot route message."
|
|
23
|
+
logger.error(msg)
|
|
24
|
+
await context.phase_manager.notify_error_occurred(msg, "TeamManager is not initialized.")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
target_node = await team_manager.ensure_node_is_ready(name_or_agent_id=event.target_agent_name)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
msg = f"Team '{context.team_id}': Node '{event.target_agent_name}' not found or failed to start. Cannot route message. Error: {e}"
|
|
31
|
+
logger.error(msg, exc_info=True)
|
|
32
|
+
await context.phase_manager.notify_error_occurred(msg, f"Node '{event.target_agent_name}' not found or failed to start.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if isinstance(target_node, Agent):
|
|
36
|
+
await target_node.post_user_message(event.user_message)
|
|
37
|
+
logger.info(f"Team '{context.team_id}': Routed user message to agent node '{event.target_agent_name}'.")
|
|
38
|
+
elif isinstance(target_node, AgentTeam):
|
|
39
|
+
await target_node.post_message(event.user_message)
|
|
40
|
+
logger.info(f"Team '{context.team_id}': Routed user message to sub-team node '{event.target_agent_name}'.")
|
|
41
|
+
else:
|
|
42
|
+
msg = f"Target node '{event.target_agent_name}' is of an unsupported type: {type(target_node).__name__}"
|
|
43
|
+
logger.error(f"Team '{context.team_id}': {msg}")
|
|
44
|
+
await context.phase_manager.notify_error_occurred(msg, "")
|
|
45
|
+
|
|
46
|
+
await context.phase_manager.notify_processing_complete_and_idle()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/handlers/tool_approval_team_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.handlers.base_agent_team_event_handler import BaseAgentTeamEventHandler
|
|
6
|
+
from autobyteus.agent_team.events.agent_team_events import ToolApprovalTeamEvent
|
|
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 ToolApprovalTeamEventHandler(BaseAgentTeamEventHandler):
|
|
14
|
+
"""
|
|
15
|
+
Handles tool approval events by routing them to the correct agent.
|
|
16
|
+
"""
|
|
17
|
+
async def handle(self, event: ToolApprovalTeamEvent, context: 'AgentTeamContext') -> None:
|
|
18
|
+
team_id = context.team_id
|
|
19
|
+
team_manager = context.team_manager
|
|
20
|
+
|
|
21
|
+
if not team_manager:
|
|
22
|
+
msg = f"Team '{team_id}': TeamManager not found. Cannot route approval for agent '{event.agent_name}'."
|
|
23
|
+
logger.error(msg)
|
|
24
|
+
await context.phase_manager.notify_error_occurred(msg, "TeamManager is not initialized.")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
from autobyteus.agent.agent import Agent
|
|
28
|
+
target_node = await team_manager.ensure_node_is_ready(name_or_agent_id=event.agent_name)
|
|
29
|
+
if not isinstance(target_node, Agent):
|
|
30
|
+
msg = f"Team '{team_id}': Target node '{event.agent_name}' for approval is not an agent."
|
|
31
|
+
logger.error(msg)
|
|
32
|
+
await context.phase_manager.notify_error_occurred(msg, f"Node '{event.agent_name}' is not an agent.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
target_agent = target_node
|
|
36
|
+
|
|
37
|
+
if not target_agent:
|
|
38
|
+
msg = f"Team '{team_id}': Target agent '{event.agent_name}' for approval not found or failed to start."
|
|
39
|
+
logger.error(msg)
|
|
40
|
+
await context.phase_manager.notify_error_occurred(msg, f"Agent '{event.agent_name}' not found or failed to start.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
logger.info(f"Team '{team_id}': Posting tool approval (Approved: {event.is_approved}) to agent '{event.agent_name}' for invocation '{event.tool_invocation_id}'.")
|
|
44
|
+
await target_agent.post_tool_execution_approval(
|
|
45
|
+
tool_invocation_id=event.tool_invocation_id,
|
|
46
|
+
is_approved=event.is_approved,
|
|
47
|
+
reason=event.reason
|
|
48
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/phases/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
This package contains components for defining and managing agent team operational phases.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.agent_team.phases.agent_team_operational_phase import AgentTeamOperationalPhase
|
|
6
|
+
from autobyteus.agent_team.phases.agent_team_phase_manager import AgentTeamPhaseManager
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AgentTeamOperationalPhase",
|
|
10
|
+
"AgentTeamPhaseManager",
|
|
11
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/phases/agent_team_operational_phase.py
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
class AgentTeamOperationalPhase(str, Enum):
|
|
5
|
+
"""Defines the operational phases of an AgentTeam."""
|
|
6
|
+
UNINITIALIZED = "uninitialized"
|
|
7
|
+
BOOTSTRAPPING = "bootstrapping"
|
|
8
|
+
IDLE = "idle"
|
|
9
|
+
PROCESSING = "processing"
|
|
10
|
+
SHUTTING_DOWN = "shutting_down"
|
|
11
|
+
SHUTDOWN_COMPLETE = "shutdown_complete"
|
|
12
|
+
ERROR = "error"
|
|
13
|
+
|
|
14
|
+
def is_terminal(self) -> bool:
|
|
15
|
+
"""Checks if the phase is a terminal state."""
|
|
16
|
+
return self in [AgentTeamOperationalPhase.SHUTDOWN_COMPLETE, AgentTeamOperationalPhase.ERROR]
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return self.value
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/phases/agent_team_phase_manager.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.agent_team.phases.agent_team_operational_phase import AgentTeamOperationalPhase
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
9
|
+
from autobyteus.agent_team.streaming.agent_team_event_notifier import AgentTeamExternalEventNotifier
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class AgentTeamPhaseManager:
|
|
14
|
+
"""Manages the operational phase of an agent team."""
|
|
15
|
+
def __init__(self, context: 'AgentTeamContext', notifier: 'AgentTeamExternalEventNotifier'):
|
|
16
|
+
self.context = context
|
|
17
|
+
self.notifier = notifier
|
|
18
|
+
self.context.state.current_phase = AgentTeamOperationalPhase.UNINITIALIZED
|
|
19
|
+
logger.debug(f"AgentTeamPhaseManager initialized for team '{context.team_id}'.")
|
|
20
|
+
|
|
21
|
+
async def _transition_phase(self, new_phase: AgentTeamOperationalPhase, extra_data: Optional[dict] = None):
|
|
22
|
+
old_phase = self.context.state.current_phase
|
|
23
|
+
if old_phase == new_phase:
|
|
24
|
+
return
|
|
25
|
+
logger.info(f"Team '{self.context.team_id}' transitioning from {old_phase.value} to {new_phase.value}.")
|
|
26
|
+
self.context.state.current_phase = new_phase
|
|
27
|
+
self.notifier.notify_phase_change(new_phase, old_phase, extra_data)
|
|
28
|
+
|
|
29
|
+
async def notify_bootstrapping_started(self):
|
|
30
|
+
await self._transition_phase(AgentTeamOperationalPhase.BOOTSTRAPPING)
|
|
31
|
+
|
|
32
|
+
async def notify_initialization_complete(self):
|
|
33
|
+
await self._transition_phase(AgentTeamOperationalPhase.IDLE)
|
|
34
|
+
|
|
35
|
+
async def notify_processing_started(self):
|
|
36
|
+
await self._transition_phase(AgentTeamOperationalPhase.PROCESSING)
|
|
37
|
+
|
|
38
|
+
async def notify_processing_complete_and_idle(self):
|
|
39
|
+
await self._transition_phase(AgentTeamOperationalPhase.IDLE)
|
|
40
|
+
|
|
41
|
+
async def notify_error_occurred(self, error_message: str, error_details: Optional[str] = None):
|
|
42
|
+
await self._transition_phase(AgentTeamOperationalPhase.ERROR, {"error_message": error_message, "error_details": error_details})
|
|
43
|
+
|
|
44
|
+
async def notify_shutdown_initiated(self):
|
|
45
|
+
await self._transition_phase(AgentTeamOperationalPhase.SHUTTING_DOWN)
|
|
46
|
+
|
|
47
|
+
async def notify_final_shutdown_complete(self):
|
|
48
|
+
await self._transition_phase(AgentTeamOperationalPhase.SHUTDOWN_COMPLETE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/runtime/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
The agent team runtime contains the active execution components for a team,
|
|
4
|
+
including the main AgentTeamRuntime controller and the AgentTeamWorker that runs
|
|
5
|
+
in a dedicated thread.
|
|
6
|
+
"""
|
|
7
|
+
from autobyteus.agent_team.runtime.agent_team_runtime import AgentTeamRuntime
|
|
8
|
+
from autobyteus.agent_team.runtime.agent_team_worker import AgentTeamWorker
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AgentTeamRuntime",
|
|
12
|
+
"AgentTeamWorker",
|
|
13
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/runtime/agent_team_runtime.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from autobyteus.agent_team.context.agent_team_context import AgentTeamContext
|
|
7
|
+
from autobyteus.agent_team.phases.agent_team_phase_manager import AgentTeamPhaseManager
|
|
8
|
+
from autobyteus.agent_team.runtime.agent_team_worker import AgentTeamWorker
|
|
9
|
+
from autobyteus.agent_team.events.agent_team_events import BaseAgentTeamEvent
|
|
10
|
+
from autobyteus.agent_team.streaming.agent_team_event_notifier import AgentTeamExternalEventNotifier
|
|
11
|
+
from autobyteus.agent_team.streaming.agent_event_multiplexer import AgentEventMultiplexer
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from autobyteus.agent_team.handlers.agent_team_event_handler_registry import AgentTeamEventHandlerRegistry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
class AgentTeamRuntime:
|
|
19
|
+
"""The active execution engine for an agent team, managing the worker."""
|
|
20
|
+
def __init__(self, context: AgentTeamContext, event_handler_registry: 'AgentTeamEventHandlerRegistry'):
|
|
21
|
+
self.context = context
|
|
22
|
+
self.notifier = AgentTeamExternalEventNotifier(team_id=self.context.team_id, runtime_ref=self)
|
|
23
|
+
self.phase_manager = AgentTeamPhaseManager(context=self.context, notifier=self.notifier)
|
|
24
|
+
|
|
25
|
+
# --- FIX: Set the phase_manager_ref on the context's state BEFORE creating the worker ---
|
|
26
|
+
self.context.state.phase_manager_ref = self.phase_manager
|
|
27
|
+
|
|
28
|
+
self._worker = AgentTeamWorker(self.context, event_handler_registry)
|
|
29
|
+
|
|
30
|
+
self.multiplexer = AgentEventMultiplexer(
|
|
31
|
+
team_id=self.context.team_id,
|
|
32
|
+
notifier=self.notifier,
|
|
33
|
+
worker_ref=self._worker
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Set other references on the context's state object for access by other components
|
|
37
|
+
self.context.state.multiplexer_ref = self.multiplexer
|
|
38
|
+
|
|
39
|
+
self._worker.add_done_callback(self._handle_worker_completion)
|
|
40
|
+
logger.info(f"AgentTeamRuntime initialized for team '{self.context.team_id}'.")
|
|
41
|
+
|
|
42
|
+
def get_worker_loop(self) -> Optional[asyncio.AbstractEventLoop]:
|
|
43
|
+
"""Returns the worker's event loop if it's running."""
|
|
44
|
+
return self._worker.get_worker_loop()
|
|
45
|
+
|
|
46
|
+
def _handle_worker_completion(self, future: asyncio.Future):
|
|
47
|
+
team_id = self.context.team_id
|
|
48
|
+
try:
|
|
49
|
+
future.result()
|
|
50
|
+
logger.info(f"AgentTeamRuntime '{team_id}': Worker thread completed.")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error(f"AgentTeamRuntime '{team_id}': Worker thread terminated with exception: {e}", exc_info=True)
|
|
53
|
+
if not self.context.state.current_phase.is_terminal():
|
|
54
|
+
asyncio.run(self.phase_manager.notify_final_shutdown_complete())
|
|
55
|
+
|
|
56
|
+
def start(self):
|
|
57
|
+
if self._worker.is_alive:
|
|
58
|
+
return
|
|
59
|
+
self._worker.start()
|
|
60
|
+
|
|
61
|
+
async def stop(self, timeout: float = 10.0):
|
|
62
|
+
await self.phase_manager.notify_shutdown_initiated()
|
|
63
|
+
await self._worker.stop(timeout=timeout)
|
|
64
|
+
await self.phase_manager.notify_final_shutdown_complete()
|
|
65
|
+
|
|
66
|
+
async def submit_event(self, event: BaseAgentTeamEvent):
|
|
67
|
+
if not self._worker.is_alive:
|
|
68
|
+
raise RuntimeError("Agent team worker is not active.")
|
|
69
|
+
def _coro_factory():
|
|
70
|
+
async def _enqueue():
|
|
71
|
+
from autobyteus.agent_team.events.agent_team_events import ProcessUserMessageEvent
|
|
72
|
+
if isinstance(event, ProcessUserMessageEvent):
|
|
73
|
+
await self.context.state.input_event_queues.enqueue_user_message(event)
|
|
74
|
+
else:
|
|
75
|
+
await self.context.state.input_event_queues.enqueue_internal_system_event(event)
|
|
76
|
+
return _enqueue()
|
|
77
|
+
future = self._worker.schedule_coroutine(_coro_factory)
|
|
78
|
+
await asyncio.wrap_future(future)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def is_running(self) -> bool:
|
|
82
|
+
return self._worker.is_alive
|
|
@@ -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()
|