autobyteus 1.1.3__py3-none-any.whl → 1.1.4__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/agent_config.py +36 -5
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
- 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 +1 -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 +68 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_worker.py +1 -0
- 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/cli/__init__.py +1 -1
- 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 +3 -0
- autobyteus/llm/api/lmstudio_llm.py +37 -0
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/llm_factory.py +2 -0
- autobyteus/llm/lmstudio_provider.py +89 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/bash/bash_executor.py +2 -1
- 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/tool_registrar.py +3 -1
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +12 -8
- autobyteus/tools/registry/tool_registry.py +50 -2
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +14 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -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.4.dist-info}/METADATA +16 -14
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/RECORD +134 -65
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +260 -0
- examples/run_google_slides_agent.py +286 -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 +274 -0
- examples/run_sqlite_agent.py +293 -0
- examples/workflow/__init__.py +1 -0
- examples/workflow/run_basic_research_workflow.py +189 -0
- examples/workflow/run_code_review_workflow.py +269 -0
- examples/workflow/run_debate_workflow.py +212 -0
- examples/workflow/run_workflow_with_tui.py +153 -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/registrar.py +0 -202
- 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 → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/factory/workflow_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.workflow.agentic_workflow import AgenticWorkflow
|
|
8
|
+
from autobyteus.workflow.context.workflow_config import WorkflowConfig
|
|
9
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
10
|
+
from autobyteus.workflow.context.workflow_runtime_state import WorkflowRuntimeState
|
|
11
|
+
from autobyteus.workflow.context.team_manager import TeamManager
|
|
12
|
+
from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
|
|
13
|
+
from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
|
|
14
|
+
from autobyteus.workflow.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
|
|
15
|
+
from autobyteus.workflow.handlers.lifecycle_workflow_event_handler import LifecycleWorkflowEventHandler
|
|
16
|
+
from autobyteus.workflow.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
|
|
17
|
+
from autobyteus.workflow.handlers.tool_approval_workflow_event_handler import ToolApprovalWorkflowEventHandler
|
|
18
|
+
from autobyteus.workflow.events.workflow_events import (
|
|
19
|
+
ProcessUserMessageEvent,
|
|
20
|
+
WorkflowReadyEvent,
|
|
21
|
+
WorkflowErrorEvent,
|
|
22
|
+
InterAgentMessageRequestEvent,
|
|
23
|
+
ToolApprovalWorkflowEvent
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
class WorkflowFactory(metaclass=SingletonMeta):
|
|
29
|
+
"""
|
|
30
|
+
A singleton factory for creating and managing AgenticWorkflow instances.
|
|
31
|
+
It orchestrates the assembly of all core workflow components.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._active_workflows: Dict[str, AgenticWorkflow] = {}
|
|
35
|
+
logger.info("WorkflowFactory (Singleton) initialized.")
|
|
36
|
+
|
|
37
|
+
def _get_default_event_handler_registry(self) -> WorkflowEventHandlerRegistry:
|
|
38
|
+
"""Returns a registry with default handlers for a new workflow."""
|
|
39
|
+
registry = WorkflowEventHandlerRegistry()
|
|
40
|
+
registry.register(ProcessUserMessageEvent, ProcessUserMessageEventHandler())
|
|
41
|
+
registry.register(InterAgentMessageRequestEvent, InterAgentMessageRequestEventHandler())
|
|
42
|
+
registry.register(ToolApprovalWorkflowEvent, ToolApprovalWorkflowEventHandler())
|
|
43
|
+
lifecycle_handler = LifecycleWorkflowEventHandler()
|
|
44
|
+
registry.register(WorkflowReadyEvent, lifecycle_handler)
|
|
45
|
+
registry.register(WorkflowErrorEvent, lifecycle_handler)
|
|
46
|
+
return registry
|
|
47
|
+
|
|
48
|
+
def create_workflow(self, config: WorkflowConfig) -> AgenticWorkflow:
|
|
49
|
+
"""
|
|
50
|
+
Creates a new workflow based on the provided WorkflowConfig, stores it,
|
|
51
|
+
and returns its facade (AgenticWorkflow).
|
|
52
|
+
"""
|
|
53
|
+
if not isinstance(config, WorkflowConfig):
|
|
54
|
+
raise TypeError(f"Expected WorkflowConfig instance, got {type(config).__name__}.")
|
|
55
|
+
|
|
56
|
+
workflow_id = f"workflow_{uuid.uuid4().hex[:8]}"
|
|
57
|
+
while workflow_id in self._active_workflows:
|
|
58
|
+
workflow_id = f"workflow_{uuid.uuid4().hex[:8]}"
|
|
59
|
+
|
|
60
|
+
# --- Component Assembly as per new architecture ---
|
|
61
|
+
state = WorkflowRuntimeState(workflow_id=workflow_id)
|
|
62
|
+
context = WorkflowContext(workflow_id=workflow_id, config=config, state=state)
|
|
63
|
+
|
|
64
|
+
handler_registry = self._get_default_event_handler_registry()
|
|
65
|
+
runtime = WorkflowRuntime(context=context, event_handler_registry=handler_registry)
|
|
66
|
+
|
|
67
|
+
team_manager = TeamManager(
|
|
68
|
+
workflow_id=workflow_id,
|
|
69
|
+
runtime=runtime,
|
|
70
|
+
multiplexer=runtime.multiplexer # Pass multiplexer created in runtime
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
context.state.team_manager = team_manager
|
|
74
|
+
|
|
75
|
+
workflow = AgenticWorkflow(runtime=runtime)
|
|
76
|
+
|
|
77
|
+
self._active_workflows[workflow_id] = workflow
|
|
78
|
+
logger.info(f"Workflow '{workflow_id}' created and stored successfully.")
|
|
79
|
+
return workflow
|
|
80
|
+
|
|
81
|
+
def get_workflow(self, workflow_id: str) -> Optional[AgenticWorkflow]:
|
|
82
|
+
"""Retrieves an active workflow instance by its ID."""
|
|
83
|
+
return self._active_workflows.get(workflow_id)
|
|
84
|
+
|
|
85
|
+
async def remove_workflow(self, workflow_id: str, shutdown_timeout: float = 10.0) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Removes a workflow from the factory's management and gracefully stops it.
|
|
88
|
+
"""
|
|
89
|
+
workflow = self._active_workflows.pop(workflow_id, None)
|
|
90
|
+
if workflow:
|
|
91
|
+
logger.info(f"Removing workflow '{workflow_id}'. Attempting graceful shutdown.")
|
|
92
|
+
await workflow.stop(timeout=shutdown_timeout)
|
|
93
|
+
return True
|
|
94
|
+
logger.warning(f"Workflow with ID '{workflow_id}' not found for removal.")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def list_active_workflow_ids(self) -> List[str]:
|
|
98
|
+
"""Returns a list of IDs of all active workflows managed by this factory."""
|
|
99
|
+
return list(self._active_workflows.keys())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Event handlers for the workflow runtime.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
6
|
+
from autobyteus.workflow.handlers.lifecycle_workflow_event_handler import LifecycleWorkflowEventHandler
|
|
7
|
+
from autobyteus.workflow.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
|
|
8
|
+
from autobyteus.workflow.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
|
|
9
|
+
from autobyteus.workflow.handlers.tool_approval_workflow_event_handler import ToolApprovalWorkflowEventHandler
|
|
10
|
+
from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BaseWorkflowEventHandler",
|
|
14
|
+
"LifecycleWorkflowEventHandler",
|
|
15
|
+
"InterAgentMessageRequestEventHandler",
|
|
16
|
+
"ProcessUserMessageEventHandler",
|
|
17
|
+
"ToolApprovalWorkflowEventHandler",
|
|
18
|
+
"WorkflowEventHandlerRegistry",
|
|
19
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/base_workflow_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.workflow.context.workflow_context import WorkflowContext
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class BaseWorkflowEventHandler(ABC):
|
|
12
|
+
"""Abstract base class for workflow event handlers."""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
async def handle(self, event: Any, context: 'WorkflowContext') -> None:
|
|
16
|
+
raise NotImplementedError("Subclasses must implement the 'handle' method.")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
6
|
+
from autobyteus.workflow.events.workflow_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.workflow.agentic_workflow import AgenticWorkflow
|
|
10
|
+
from autobyteus.agent.agent import Agent
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class InterAgentMessageRequestEventHandler(BaseWorkflowEventHandler):
|
|
18
|
+
"""
|
|
19
|
+
Handles requests to send messages between nodes (agents or sub-workflows).
|
|
20
|
+
It relies on the TeamManager to handle on-demand startup of the recipient.
|
|
21
|
+
"""
|
|
22
|
+
async def handle(self, event: InterAgentMessageRequestEvent, context: 'WorkflowContext') -> None:
|
|
23
|
+
workflow_id = context.workflow_id
|
|
24
|
+
team_manager = context.team_manager
|
|
25
|
+
|
|
26
|
+
if not team_manager:
|
|
27
|
+
logger.error(f"Workflow '{workflow_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(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"Workflow '{workflow_id}': {msg}", exc_info=True)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if isinstance(target_node, AgenticWorkflow):
|
|
39
|
+
# If target is a sub-workflow, post a user message to it.
|
|
40
|
+
# The sub-workflow will route it to its own coordinator.
|
|
41
|
+
message_for_workflow = AgentInputUserMessage(content=event.content)
|
|
42
|
+
await target_node.post_message(message_for_workflow)
|
|
43
|
+
logger.info(f"Workflow '{workflow_id}': Successfully posted message from '{event.sender_agent_id}' to sub-workflow '{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"Workflow '{workflow_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"Workflow '{workflow_id}': {msg}", exc_info=True)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
6
|
+
from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent, WorkflowReadyEvent, WorkflowErrorEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class LifecycleWorkflowEventHandler(BaseWorkflowEventHandler):
|
|
14
|
+
"""Logs various lifecycle events for a workflow."""
|
|
15
|
+
async def handle(self, event: BaseWorkflowEvent, context: 'WorkflowContext') -> None:
|
|
16
|
+
workflow_id = context.workflow_id
|
|
17
|
+
current_phase = context.state.current_phase.value
|
|
18
|
+
|
|
19
|
+
if isinstance(event, WorkflowReadyEvent):
|
|
20
|
+
logger.info(f"Workflow '{workflow_id}' Logged WorkflowReadyEvent. Current phase: {current_phase}")
|
|
21
|
+
elif isinstance(event, WorkflowErrorEvent):
|
|
22
|
+
logger.error(
|
|
23
|
+
f"Workflow '{workflow_id}' Logged WorkflowErrorEvent: {event.error_message}. "
|
|
24
|
+
f"Details: {event.exception_details}. Current phase: {current_phase}"
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
logger.warning(f"LifecycleWorkflowEventHandler received unhandled event type: {type(event).__name__}")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/process_user_message_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
5
|
+
from autobyteus.workflow.events.workflow_events import ProcessUserMessageEvent
|
|
6
|
+
from autobyteus.agent.agent import Agent
|
|
7
|
+
from autobyteus.workflow.agentic_workflow import AgenticWorkflow
|
|
8
|
+
from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class ProcessUserMessageEventHandler(BaseWorkflowEventHandler):
|
|
16
|
+
"""Handles user messages by routing them to the specified target agent or sub-workflow."""
|
|
17
|
+
async def handle(self, event: ProcessUserMessageEvent, context: 'WorkflowContext') -> None:
|
|
18
|
+
await context.phase_manager.notify_processing_started()
|
|
19
|
+
|
|
20
|
+
team_manager = context.team_manager
|
|
21
|
+
if not team_manager:
|
|
22
|
+
msg = f"Workflow '{context.workflow_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(event.target_agent_name)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
msg = f"Workflow '{context.workflow_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"Workflow '{context.workflow_id}': Routed user message to agent node '{event.target_agent_name}'.")
|
|
38
|
+
elif isinstance(target_node, AgenticWorkflow):
|
|
39
|
+
await target_node.post_message(event.user_message)
|
|
40
|
+
logger.info(f"Workflow '{context.workflow_id}': Routed user message to sub-workflow 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"Workflow '{context.workflow_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,39 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
6
|
+
from autobyteus.workflow.events.workflow_events import ToolApprovalWorkflowEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class ToolApprovalWorkflowEventHandler(BaseWorkflowEventHandler):
|
|
14
|
+
"""
|
|
15
|
+
Handles tool approval events by routing them to the correct agent.
|
|
16
|
+
"""
|
|
17
|
+
async def handle(self, event: ToolApprovalWorkflowEvent, context: 'WorkflowContext') -> None:
|
|
18
|
+
workflow_id = context.workflow_id
|
|
19
|
+
team_manager = context.team_manager
|
|
20
|
+
|
|
21
|
+
if not team_manager:
|
|
22
|
+
msg = f"Workflow '{workflow_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
|
+
target_agent = await team_manager.ensure_agent_is_ready(event.agent_name)
|
|
28
|
+
if not target_agent:
|
|
29
|
+
msg = f"Workflow '{workflow_id}': Target agent '{event.agent_name}' for approval not found or failed to start."
|
|
30
|
+
logger.error(msg)
|
|
31
|
+
await context.phase_manager.notify_error_occurred(msg, f"Agent '{event.agent_name}' not found or failed to start.")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
logger.info(f"Workflow '{workflow_id}': Posting tool approval (Approved: {event.is_approved}) to agent '{event.agent_name}' for invocation '{event.tool_invocation_id}'.")
|
|
35
|
+
await target_agent.post_tool_execution_approval(
|
|
36
|
+
tool_invocation_id=event.tool_invocation_id,
|
|
37
|
+
is_approved=event.is_approved,
|
|
38
|
+
reason=event.reason
|
|
39
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/handlers/workflow_event_handler_registry.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Type, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
|
|
6
|
+
from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class WorkflowEventHandlerRegistry:
|
|
11
|
+
"""Manages registration and retrieval of workflow event handlers."""
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._handlers: Dict[Type[BaseWorkflowEvent], BaseWorkflowEventHandler] = {}
|
|
14
|
+
logger.info("WorkflowEventHandlerRegistry initialized.")
|
|
15
|
+
|
|
16
|
+
def register(self, event_class: Type[BaseWorkflowEvent], handler_instance: BaseWorkflowEventHandler):
|
|
17
|
+
if not issubclass(event_class, BaseWorkflowEvent):
|
|
18
|
+
raise TypeError("Can only register handlers for BaseWorkflowEvent 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[BaseWorkflowEvent]) -> Optional[BaseWorkflowEventHandler]:
|
|
23
|
+
return self._handlers.get(event_class)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/phases/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
This package contains components for defining and managing workflow operational phases.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
|
|
6
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"WorkflowOperationalPhase",
|
|
10
|
+
"WorkflowPhaseManager",
|
|
11
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/phases/workflow_operational_phase.py
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
class WorkflowOperationalPhase(str, Enum):
|
|
5
|
+
"""Defines the operational phases of an AgenticWorkflow."""
|
|
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 [WorkflowOperationalPhase.SHUTDOWN_COMPLETE, WorkflowOperationalPhase.ERROR]
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return self.value
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/phases/workflow_phase_manager.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
9
|
+
from autobyteus.workflow.streaming.workflow_event_notifier import WorkflowExternalEventNotifier
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class WorkflowPhaseManager:
|
|
14
|
+
"""Manages the operational phase of a workflow."""
|
|
15
|
+
def __init__(self, context: 'WorkflowContext', notifier: 'WorkflowExternalEventNotifier'):
|
|
16
|
+
self.context = context
|
|
17
|
+
self.notifier = notifier
|
|
18
|
+
self.context.state.current_phase = WorkflowOperationalPhase.UNINITIALIZED
|
|
19
|
+
logger.debug(f"WorkflowPhaseManager initialized for workflow '{context.workflow_id}'.")
|
|
20
|
+
|
|
21
|
+
async def _transition_phase(self, new_phase: WorkflowOperationalPhase, 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"Workflow '{self.context.workflow_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(WorkflowOperationalPhase.BOOTSTRAPPING)
|
|
31
|
+
|
|
32
|
+
async def notify_initialization_complete(self):
|
|
33
|
+
await self._transition_phase(WorkflowOperationalPhase.IDLE)
|
|
34
|
+
|
|
35
|
+
async def notify_processing_started(self):
|
|
36
|
+
await self._transition_phase(WorkflowOperationalPhase.PROCESSING)
|
|
37
|
+
|
|
38
|
+
async def notify_processing_complete_and_idle(self):
|
|
39
|
+
await self._transition_phase(WorkflowOperationalPhase.IDLE)
|
|
40
|
+
|
|
41
|
+
async def notify_error_occurred(self, error_message: str, error_details: Optional[str] = None):
|
|
42
|
+
await self._transition_phase(WorkflowOperationalPhase.ERROR, {"error_message": error_message, "error_details": error_details})
|
|
43
|
+
|
|
44
|
+
async def notify_shutdown_initiated(self):
|
|
45
|
+
await self._transition_phase(WorkflowOperationalPhase.SHUTTING_DOWN)
|
|
46
|
+
|
|
47
|
+
async def notify_final_shutdown_complete(self):
|
|
48
|
+
await self._transition_phase(WorkflowOperationalPhase.SHUTDOWN_COMPLETE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/runtime/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
The workflow runtime contains the active execution components for a workflow,
|
|
4
|
+
including the main WorkflowRuntime controller and the WorkflowWorker that runs
|
|
5
|
+
in a dedicated thread.
|
|
6
|
+
"""
|
|
7
|
+
from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
|
|
8
|
+
from autobyteus.workflow.runtime.workflow_worker import WorkflowWorker
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"WorkflowRuntime",
|
|
12
|
+
"WorkflowWorker",
|
|
13
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/workflow/runtime/workflow_runtime.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
7
|
+
from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
|
|
8
|
+
from autobyteus.workflow.runtime.workflow_worker import WorkflowWorker
|
|
9
|
+
from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent
|
|
10
|
+
from autobyteus.workflow.streaming.workflow_event_notifier import WorkflowExternalEventNotifier
|
|
11
|
+
from autobyteus.workflow.streaming.agent_event_multiplexer import AgentEventMultiplexer
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
class WorkflowRuntime:
|
|
19
|
+
"""The active execution engine for a workflow, managing the worker."""
|
|
20
|
+
def __init__(self, context: WorkflowContext, event_handler_registry: 'WorkflowEventHandlerRegistry'):
|
|
21
|
+
self.context = context
|
|
22
|
+
self.notifier = WorkflowExternalEventNotifier(workflow_id=self.context.workflow_id, runtime_ref=self)
|
|
23
|
+
self.phase_manager = WorkflowPhaseManager(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 = WorkflowWorker(self.context, event_handler_registry)
|
|
29
|
+
|
|
30
|
+
self.multiplexer = AgentEventMultiplexer(
|
|
31
|
+
workflow_id=self.context.workflow_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"WorkflowRuntime initialized for workflow '{self.context.workflow_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
|
+
workflow_id = self.context.workflow_id
|
|
48
|
+
try:
|
|
49
|
+
future.result()
|
|
50
|
+
logger.info(f"WorkflowRuntime '{workflow_id}': Worker thread completed.")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error(f"WorkflowRuntime '{workflow_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: BaseWorkflowEvent):
|
|
67
|
+
if not self._worker.is_alive:
|
|
68
|
+
raise RuntimeError("Workflow worker is not active.")
|
|
69
|
+
def _coro_factory():
|
|
70
|
+
async def _enqueue():
|
|
71
|
+
from autobyteus.workflow.events.workflow_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/workflow/runtime/workflow_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.workflow.events.workflow_event_dispatcher import WorkflowEventDispatcher
|
|
8
|
+
from autobyteus.workflow.bootstrap_steps.workflow_bootstrapper import WorkflowBootstrapper
|
|
9
|
+
from autobyteus.workflow.shutdown_steps.workflow_shutdown_orchestrator import WorkflowShutdownOrchestrator
|
|
10
|
+
from autobyteus.agent.runtime.agent_thread_pool_manager import AgentThreadPoolManager
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.workflow.context.workflow_context import WorkflowContext
|
|
14
|
+
from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
class WorkflowWorker:
|
|
19
|
+
"""Encapsulates the core event processing loop for a workflow."""
|
|
20
|
+
def __init__(self, context: 'WorkflowContext', event_handler_registry: 'WorkflowEventHandlerRegistry'):
|
|
21
|
+
self.context = context
|
|
22
|
+
self.phase_manager = self.context.phase_manager
|
|
23
|
+
self.event_dispatcher = WorkflowEventDispatcher(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"WorkflowWorker initialized for workflow '{self.context.workflow_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("WorkflowWorker 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"WorkflowWorker '{self.context.workflow_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 = WorkflowBootstrapper()
|
|
73
|
+
if not await bootstrapper.run(self.context, self.phase_manager):
|
|
74
|
+
logger.critical(f"Workflow '{self.context.workflow_id}' failed to initialize. Shutting down.")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
logger.info(f"Workflow '{self.context.workflow_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"Workflow '{self.context.workflow_id}' shutdown signal received. Cleaning up.")
|
|
98
|
+
shutdown_orchestrator = WorkflowShutdownOrchestrator()
|
|
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 workflow worker '{self.context.workflow_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/workflow/shutdown_steps/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Defines individual, self-contained steps for the workflow shutdown process.
|
|
4
|
+
"""
|
|
5
|
+
from autobyteus.workflow.shutdown_steps.base_workflow_shutdown_step import BaseWorkflowShutdownStep
|
|
6
|
+
from autobyteus.workflow.shutdown_steps.agent_team_shutdown_step import AgentTeamShutdownStep
|
|
7
|
+
from autobyteus.workflow.shutdown_steps.sub_workflow_shutdown_step import SubWorkflowShutdownStep
|
|
8
|
+
from autobyteus.workflow.shutdown_steps.bridge_cleanup_step import BridgeCleanupStep
|
|
9
|
+
from autobyteus.workflow.shutdown_steps.workflow_shutdown_orchestrator import WorkflowShutdownOrchestrator
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BaseWorkflowShutdownStep",
|
|
13
|
+
"AgentTeamShutdownStep",
|
|
14
|
+
"SubWorkflowShutdownStep",
|
|
15
|
+
"BridgeCleanupStep",
|
|
16
|
+
"WorkflowShutdownOrchestrator",
|
|
17
|
+
]
|