autobyteus 1.1.8__py3-none-any.whl → 1.1.9__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/bootstrap_steps/system_prompt_processing_step.py +6 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +17 -19
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +6 -3
- autobyteus/agent/handlers/tool_result_event_handler.py +61 -18
- autobyteus/agent/handlers/user_input_message_event_handler.py +19 -10
- autobyteus/agent/hooks/base_phase_hook.py +17 -0
- autobyteus/agent/hooks/hook_registry.py +15 -27
- autobyteus/agent/input_processor/base_user_input_processor.py +17 -1
- autobyteus/agent/input_processor/processor_registry.py +15 -27
- autobyteus/agent/llm_response_processor/base_processor.py +17 -1
- autobyteus/agent/llm_response_processor/processor_registry.py +15 -24
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +14 -0
- autobyteus/agent/message/agent_input_user_message.py +15 -2
- autobyteus/agent/message/send_message_to.py +1 -1
- autobyteus/agent/processor_option.py +17 -0
- autobyteus/agent/sender_type.py +1 -0
- autobyteus/agent/system_prompt_processor/base_processor.py +17 -1
- autobyteus/agent/system_prompt_processor/processor_registry.py +15 -27
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +10 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +17 -1
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +15 -1
- autobyteus/agent/workspace/base_workspace.py +1 -1
- autobyteus/agent/workspace/workspace_definition.py +1 -1
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +1 -1
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +2 -2
- autobyteus/agent_team/task_notification/__init__.py +4 -0
- autobyteus/agent_team/task_notification/activation_policy.py +70 -0
- autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +56 -122
- autobyteus/agent_team/task_notification/task_activator.py +66 -0
- autobyteus/cli/agent_team_tui/state.py +17 -20
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +1 -1
- autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +1 -1
- autobyteus/events/event_types.py +2 -2
- autobyteus/llm/api/gemini_llm.py +45 -54
- autobyteus/llm/api/qwen_llm.py +25 -0
- autobyteus/llm/autobyteus_provider.py +8 -2
- autobyteus/llm/llm_factory.py +16 -0
- autobyteus/multimedia/audio/api/autobyteus_audio_client.py +4 -1
- autobyteus/multimedia/audio/api/gemini_audio_client.py +84 -153
- autobyteus/multimedia/audio/audio_client_factory.py +47 -22
- autobyteus/multimedia/audio/audio_model.py +13 -6
- autobyteus/multimedia/audio/autobyteus_audio_provider.py +8 -2
- autobyteus/multimedia/audio/base_audio_client.py +3 -1
- autobyteus/multimedia/image/api/autobyteus_image_client.py +12 -5
- autobyteus/multimedia/image/api/gemini_image_client.py +72 -130
- autobyteus/multimedia/image/api/openai_image_client.py +4 -2
- autobyteus/multimedia/image/autobyteus_image_provider.py +8 -2
- autobyteus/multimedia/image/base_image_client.py +6 -2
- autobyteus/multimedia/image/image_client_factory.py +20 -19
- autobyteus/multimedia/image/image_model.py +13 -6
- autobyteus/multimedia/providers.py +1 -0
- autobyteus/task_management/__init__.py +9 -10
- autobyteus/task_management/base_task_board.py +14 -6
- autobyteus/task_management/converters/__init__.py +0 -2
- autobyteus/task_management/converters/task_board_converter.py +7 -16
- autobyteus/task_management/events.py +6 -6
- autobyteus/task_management/in_memory_task_board.py +48 -38
- autobyteus/task_management/schemas/__init__.py +2 -2
- autobyteus/task_management/schemas/{plan_definition.py → task_definition.py} +5 -6
- autobyteus/task_management/schemas/task_status_report.py +0 -1
- autobyteus/task_management/task.py +60 -0
- autobyteus/task_management/tools/__init__.py +4 -2
- autobyteus/task_management/tools/get_my_tasks.py +80 -0
- autobyteus/task_management/tools/get_task_board_status.py +3 -3
- autobyteus/task_management/tools/publish_task.py +77 -0
- autobyteus/task_management/tools/publish_tasks.py +74 -0
- autobyteus/task_management/tools/update_task_status.py +5 -5
- autobyteus/tools/__init__.py +3 -1
- autobyteus/tools/base_tool.py +4 -4
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +1 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +1 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +1 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +1 -1
- autobyteus/tools/browser/standalone/navigate_to.py +1 -1
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +1 -1
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +1 -1
- autobyteus/tools/browser/standalone/webpage_reader.py +1 -1
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +1 -1
- autobyteus/tools/functional_tool.py +1 -1
- autobyteus/tools/google_search.py +1 -1
- autobyteus/tools/image_downloader.py +1 -1
- autobyteus/tools/mcp/factory.py +1 -1
- autobyteus/tools/mcp/schema_mapper.py +1 -1
- autobyteus/tools/mcp/tool.py +1 -1
- autobyteus/tools/multimedia/__init__.py +2 -0
- autobyteus/tools/multimedia/audio_tools.py +10 -20
- autobyteus/tools/multimedia/image_tools.py +21 -22
- autobyteus/tools/multimedia/media_reader_tool.py +117 -0
- autobyteus/tools/pydantic_schema_converter.py +1 -1
- autobyteus/tools/registry/tool_definition.py +1 -1
- autobyteus/tools/timer.py +1 -1
- autobyteus/tools/tool_meta.py +1 -1
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +1 -1
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +1 -1
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +59 -3
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +1 -1
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +1 -1
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +1 -1
- autobyteus/{tools → utils}/parameter_schema.py +1 -1
- {autobyteus-1.1.8.dist-info → autobyteus-1.1.9.dist-info}/METADATA +2 -2
- {autobyteus-1.1.8.dist-info → autobyteus-1.1.9.dist-info}/RECORD +105 -99
- examples/run_poem_writer.py +1 -1
- autobyteus/task_management/converters/task_plan_converter.py +0 -48
- autobyteus/task_management/task_plan.py +0 -110
- autobyteus/task_management/tools/publish_task_plan.py +0 -101
- {autobyteus-1.1.8.dist-info → autobyteus-1.1.9.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.8.dist-info → autobyteus-1.1.9.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.8.dist-info → autobyteus-1.1.9.dist-info}/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ from typing import Optional, List, Dict, Any
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
|
|
6
6
|
from .context_file import ContextFile # Import the new ContextFile dataclass
|
|
7
|
+
from autobyteus.agent.sender_type import SenderType
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
@@ -15,11 +16,14 @@ class AgentInputUserMessage:
|
|
|
15
16
|
allowing users to provide various documents and media as context via a single list.
|
|
16
17
|
"""
|
|
17
18
|
content: str
|
|
19
|
+
sender_type: SenderType = SenderType.USER
|
|
18
20
|
context_files: Optional[List[ContextFile]] = field(default=None)
|
|
19
21
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
20
22
|
|
|
21
23
|
def __post_init__(self):
|
|
22
24
|
# Basic type validation that dataclasses don't do automatically for mutable defaults or complex types
|
|
25
|
+
if not isinstance(self.sender_type, SenderType):
|
|
26
|
+
raise TypeError(f"AgentInputUserMessage 'sender_type' must be a SenderType enum. Got {type(self.sender_type)}")
|
|
23
27
|
if self.context_files is not None and not (isinstance(self.context_files, list) and all(isinstance(cf, ContextFile) for cf in self.context_files)):
|
|
24
28
|
raise TypeError("AgentInputUserMessage 'context_files' must be a list of ContextFile objects if provided.")
|
|
25
29
|
if not isinstance(self.metadata, dict): # Should be caught by default_factory, but good practice
|
|
@@ -30,7 +34,7 @@ class AgentInputUserMessage:
|
|
|
30
34
|
if logger.isEnabledFor(logging.DEBUG):
|
|
31
35
|
num_context_files = len(self.context_files) if self.context_files else 0
|
|
32
36
|
logger.debug(
|
|
33
|
-
f"AgentInputUserMessage initialized. Content: '{self.content[:50]}...', "
|
|
37
|
+
f"AgentInputUserMessage initialized. SenderType: {self.sender_type.value}, Content: '{self.content[:50]}...', "
|
|
34
38
|
f"Num ContextFiles: {num_context_files}, "
|
|
35
39
|
f"Metadata keys: {list(self.metadata.keys())}"
|
|
36
40
|
)
|
|
@@ -44,6 +48,7 @@ class AgentInputUserMessage:
|
|
|
44
48
|
|
|
45
49
|
return {
|
|
46
50
|
"content": self.content,
|
|
51
|
+
"sender_type": self.sender_type.value,
|
|
47
52
|
"context_files": context_files_dict_list,
|
|
48
53
|
"metadata": self.metadata,
|
|
49
54
|
}
|
|
@@ -55,6 +60,13 @@ class AgentInputUserMessage:
|
|
|
55
60
|
if not isinstance(content, str): # Ensure content is string
|
|
56
61
|
raise ValueError("AgentInputUserMessage 'content' in dictionary must be a string.")
|
|
57
62
|
|
|
63
|
+
sender_type_val = data.get("sender_type", "user")
|
|
64
|
+
try:
|
|
65
|
+
sender_type = SenderType(sender_type_val)
|
|
66
|
+
except ValueError:
|
|
67
|
+
logger.warning(f"Invalid sender_type '{sender_type_val}' in AgentInputUserMessage data. Defaulting to USER.")
|
|
68
|
+
sender_type = SenderType.USER
|
|
69
|
+
|
|
58
70
|
context_files_data = data.get("context_files")
|
|
59
71
|
context_files_list: Optional[List[ContextFile]] = None
|
|
60
72
|
if context_files_data is not None:
|
|
@@ -68,6 +80,7 @@ class AgentInputUserMessage:
|
|
|
68
80
|
|
|
69
81
|
return cls(
|
|
70
82
|
content=content,
|
|
83
|
+
sender_type=sender_type,
|
|
71
84
|
context_files=context_files_list,
|
|
72
85
|
metadata=metadata
|
|
73
86
|
)
|
|
@@ -82,5 +95,5 @@ class AgentInputUserMessage:
|
|
|
82
95
|
|
|
83
96
|
meta_repr = f", metadata_keys={list(self.metadata.keys())}" if self.metadata else ""
|
|
84
97
|
|
|
85
|
-
return (f"AgentInputUserMessage(content='{content_preview}'"
|
|
98
|
+
return (f"AgentInputUserMessage(sender_type='{self.sender_type.value}', content='{content_preview}'"
|
|
86
99
|
f"{context_repr}{meta_repr})")
|
|
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
4
4
|
|
|
5
5
|
from autobyteus.tools.base_tool import BaseTool
|
|
6
6
|
from autobyteus.tools.tool_category import ToolCategory
|
|
7
|
-
from autobyteus.
|
|
7
|
+
from autobyteus.utils.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
8
8
|
from autobyteus.tools.tool_config import ToolConfig
|
|
9
9
|
# This import is for type hinting only and avoids circular dependencies at runtime
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent/processor_option.py
|
|
2
|
+
"""
|
|
3
|
+
Defines common data transfer objects for agent component options.
|
|
4
|
+
"""
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class ProcessorOption:
|
|
9
|
+
"""A data class representing a processor option for configuration."""
|
|
10
|
+
name: str
|
|
11
|
+
is_mandatory: bool
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class HookOption:
|
|
15
|
+
"""A data class representing a hook option for configuration."""
|
|
16
|
+
name: str
|
|
17
|
+
is_mandatory: bool
|
autobyteus/agent/sender_type.py
CHANGED
|
@@ -9,6 +9,7 @@ class SenderType(str, Enum):
|
|
|
9
9
|
USER = "user" # A message originating from an external human user.
|
|
10
10
|
AGENT = "agent" # A message from another agent within the same team or a different team.
|
|
11
11
|
SYSTEM = "system" # An automated message from an internal system component.
|
|
12
|
+
TOOL = "tool" # A message generated as the result of a tool execution.
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
# --- System Sender Identification ---
|
|
@@ -24,6 +24,22 @@ class BaseSystemPromptProcessor(ABC, metaclass=SystemPromptProcessorMeta):
|
|
|
24
24
|
"""
|
|
25
25
|
return cls.__name__
|
|
26
26
|
|
|
27
|
+
@classmethod
|
|
28
|
+
def get_order(cls) -> int:
|
|
29
|
+
"""
|
|
30
|
+
Returns the execution order for this processor. Lower numbers execute earlier.
|
|
31
|
+
Defaults to 500 (normal priority).
|
|
32
|
+
"""
|
|
33
|
+
return 500
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def is_mandatory(cls) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Returns True if this processor is mandatory for the agent to function correctly.
|
|
39
|
+
Defaults to False (optional).
|
|
40
|
+
"""
|
|
41
|
+
return False
|
|
42
|
+
|
|
27
43
|
@abstractmethod
|
|
28
44
|
def process(self,
|
|
29
45
|
system_prompt: str,
|
|
@@ -45,4 +61,4 @@ class BaseSystemPromptProcessor(ABC, metaclass=SystemPromptProcessorMeta):
|
|
|
45
61
|
raise NotImplementedError("Subclasses must implement the 'process' method.")
|
|
46
62
|
|
|
47
63
|
def __repr__(self) -> str:
|
|
48
|
-
return f"
|
|
64
|
+
return f"<{self.__class__.__name__}>"
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
|
4
4
|
|
|
5
5
|
from autobyteus.utils.singleton import SingletonMeta
|
|
6
|
+
from autobyteus.agent.processor_option import ProcessorOption
|
|
6
7
|
from .processor_definition import SystemPromptProcessorDefinition # Relative import
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
@@ -24,14 +25,6 @@ class SystemPromptProcessorRegistry(metaclass=SingletonMeta):
|
|
|
24
25
|
def register_processor(self, definition: SystemPromptProcessorDefinition) -> None:
|
|
25
26
|
"""
|
|
26
27
|
Registers a system prompt processor definition.
|
|
27
|
-
If a definition with the same name already exists, it will be overwritten,
|
|
28
|
-
and a warning will be logged.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
definition: The SystemPromptProcessorDefinition object to register.
|
|
32
|
-
|
|
33
|
-
Raises:
|
|
34
|
-
TypeError: If the definition is not an instance of SystemPromptProcessorDefinition.
|
|
35
28
|
"""
|
|
36
29
|
if not isinstance(definition, SystemPromptProcessorDefinition):
|
|
37
30
|
raise TypeError(f"Expected SystemPromptProcessorDefinition instance, got {type(definition).__name__}.")
|
|
@@ -46,12 +39,6 @@ class SystemPromptProcessorRegistry(metaclass=SingletonMeta):
|
|
|
46
39
|
def get_processor_definition(self, name: str) -> Optional[SystemPromptProcessorDefinition]:
|
|
47
40
|
"""
|
|
48
41
|
Retrieves a system prompt processor definition by its name.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
name: The name of the system prompt processor definition to retrieve.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
The SystemPromptProcessorDefinition object if found, otherwise None.
|
|
55
42
|
"""
|
|
56
43
|
if not isinstance(name, str):
|
|
57
44
|
logger.warning(f"Attempted to retrieve system prompt processor definition with non-string name: {type(name).__name__}.")
|
|
@@ -64,12 +51,6 @@ class SystemPromptProcessorRegistry(metaclass=SingletonMeta):
|
|
|
64
51
|
def get_processor(self, name: str) -> Optional['BaseSystemPromptProcessor']:
|
|
65
52
|
"""
|
|
66
53
|
Retrieves an instance of a system prompt processor by its name.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
name: The name of the system prompt processor to retrieve.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
An instance of the BaseSystemPromptProcessor if found and instantiable, otherwise None.
|
|
73
54
|
"""
|
|
74
55
|
definition = self.get_processor_definition(name)
|
|
75
56
|
if definition:
|
|
@@ -83,19 +64,26 @@ class SystemPromptProcessorRegistry(metaclass=SingletonMeta):
|
|
|
83
64
|
|
|
84
65
|
def list_processor_names(self) -> List[str]:
|
|
85
66
|
"""
|
|
86
|
-
Returns
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
A list of strings, where each string is a registered processor name.
|
|
67
|
+
Returns an unordered list of names of all registered system prompt processor definitions.
|
|
90
68
|
"""
|
|
91
69
|
return list(self._definitions.keys())
|
|
92
70
|
|
|
71
|
+
def get_ordered_processor_options(self) -> List[ProcessorOption]:
|
|
72
|
+
"""
|
|
73
|
+
Returns a list of ProcessorOption objects, sorted by their execution order.
|
|
74
|
+
"""
|
|
75
|
+
definitions = list(self._definitions.values())
|
|
76
|
+
sorted_definitions = sorted(definitions, key=lambda d: d.processor_class.get_order())
|
|
77
|
+
return [
|
|
78
|
+
ProcessorOption(
|
|
79
|
+
name=d.name,
|
|
80
|
+
is_mandatory=d.processor_class.is_mandatory()
|
|
81
|
+
) for d in sorted_definitions
|
|
82
|
+
]
|
|
83
|
+
|
|
93
84
|
def get_all_definitions(self) -> Dict[str, SystemPromptProcessorDefinition]:
|
|
94
85
|
"""
|
|
95
86
|
Returns a shallow copy of the dictionary containing all registered system prompt processor definitions.
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
A dictionary where keys are processor names and values are SystemPromptProcessorDefinition objects.
|
|
99
87
|
"""
|
|
100
88
|
return dict(self._definitions)
|
|
101
89
|
|
|
@@ -32,6 +32,16 @@ class ToolManifestInjectorProcessor(BaseSystemPromptProcessor):
|
|
|
32
32
|
def get_name(cls) -> str:
|
|
33
33
|
return "ToolManifestInjector"
|
|
34
34
|
|
|
35
|
+
@classmethod
|
|
36
|
+
def get_order(cls) -> int:
|
|
37
|
+
"""Explicitly set to default, as it's often the only system prompt processor."""
|
|
38
|
+
return 500
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def is_mandatory(cls) -> bool:
|
|
42
|
+
"""This processor is essential for the LLM to know which tools are available."""
|
|
43
|
+
return True
|
|
44
|
+
|
|
35
45
|
def process(self, system_prompt: str, tool_instances: Dict[str, 'BaseTool'], agent_id: str, context: 'AgentContext') -> str:
|
|
36
46
|
try:
|
|
37
47
|
prompt_template = PromptTemplate(template=system_prompt)
|
|
@@ -26,6 +26,22 @@ class BaseToolExecutionResultProcessor(ABC, metaclass=ToolExecutionResultProcess
|
|
|
26
26
|
"""
|
|
27
27
|
return cls.__name__
|
|
28
28
|
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_order(cls) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Returns the execution order for this processor. Lower numbers execute earlier.
|
|
33
|
+
Defaults to 500 (normal priority).
|
|
34
|
+
"""
|
|
35
|
+
return 500
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def is_mandatory(cls) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Returns True if this processor is mandatory for the agent to function correctly.
|
|
41
|
+
Defaults to False (optional).
|
|
42
|
+
"""
|
|
43
|
+
return False
|
|
44
|
+
|
|
29
45
|
@abstractmethod
|
|
30
46
|
async def process(self,
|
|
31
47
|
event: 'ToolResultEvent',
|
|
@@ -43,4 +59,4 @@ class BaseToolExecutionResultProcessor(ABC, metaclass=ToolExecutionResultProcess
|
|
|
43
59
|
raise NotImplementedError("Subclasses must implement the 'process' method.")
|
|
44
60
|
|
|
45
61
|
def __repr__(self) -> str:
|
|
46
|
-
return f"
|
|
62
|
+
return f"<{self.__class__.__name__}>"
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
4
4
|
|
|
5
5
|
from autobyteus.utils.singleton import SingletonMeta
|
|
6
|
+
from autobyteus.agent.processor_option import ProcessorOption
|
|
6
7
|
from .processor_definition import ToolExecutionResultProcessorDefinition
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
@@ -56,10 +57,23 @@ class ToolExecutionResultProcessorRegistry(metaclass=SingletonMeta):
|
|
|
56
57
|
|
|
57
58
|
def list_processor_names(self) -> List[str]:
|
|
58
59
|
"""
|
|
59
|
-
Returns
|
|
60
|
+
Returns an unordered list of names of all registered processor definitions.
|
|
60
61
|
"""
|
|
61
62
|
return list(self._definitions.keys())
|
|
62
63
|
|
|
64
|
+
def get_ordered_processor_options(self) -> List[ProcessorOption]:
|
|
65
|
+
"""
|
|
66
|
+
Returns a list of ProcessorOption objects, sorted by their execution order.
|
|
67
|
+
"""
|
|
68
|
+
definitions = list(self._definitions.values())
|
|
69
|
+
sorted_definitions = sorted(definitions, key=lambda d: d.processor_class.get_order())
|
|
70
|
+
return [
|
|
71
|
+
ProcessorOption(
|
|
72
|
+
name=d.name,
|
|
73
|
+
is_mandatory=d.processor_class.is_mandatory()
|
|
74
|
+
) for d in sorted_definitions
|
|
75
|
+
]
|
|
76
|
+
|
|
63
77
|
def get_all_definitions(self) -> Dict[str, ToolExecutionResultProcessorDefinition]:
|
|
64
78
|
"""
|
|
65
79
|
Returns a dictionary of all registered processor definitions.
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import uuid
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from typing import Optional, Any, Dict, TYPE_CHECKING
|
|
6
|
-
from autobyteus.
|
|
6
|
+
from autobyteus.utils.parameter_schema import ParameterSchema
|
|
7
7
|
from autobyteus.agent.workspace.workspace_meta import WorkspaceMeta
|
|
8
8
|
from autobyteus.agent.workspace.workspace_config import WorkspaceConfig
|
|
9
9
|
|
|
@@ -4,7 +4,7 @@ for a specific type of agent workspace.
|
|
|
4
4
|
"""
|
|
5
5
|
import logging
|
|
6
6
|
from typing import Type, Optional, TYPE_CHECKING
|
|
7
|
-
from autobyteus.
|
|
7
|
+
from autobyteus.utils.parameter_schema import ParameterSchema
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
|
|
@@ -30,7 +30,7 @@ class TeamContextInitializationStep(BaseAgentTeamBootstrapStep):
|
|
|
30
30
|
if notifier:
|
|
31
31
|
# The notifier, a long-lived component, subscribes to events
|
|
32
32
|
# from the task_board, another long-lived component.
|
|
33
|
-
notifier.subscribe_from(sender=task_board, event=EventType.
|
|
33
|
+
notifier.subscribe_from(sender=task_board, event=EventType.TASK_BOARD_TASKS_ADDED, listener=notifier.handle_and_publish_task_board_event)
|
|
34
34
|
notifier.subscribe_from(sender=task_board, event=EventType.TASK_BOARD_STATUS_UPDATED, listener=notifier.handle_and_publish_task_board_event)
|
|
35
35
|
logger.info(f"Team '{team_id}': Successfully bridged TaskBoard events to the team notifier.")
|
|
36
36
|
else:
|
|
@@ -3,7 +3,7 @@ from typing import Optional, Any
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
from autobyteus.agent_team.phases.agent_team_operational_phase import AgentTeamOperationalPhase
|
|
5
5
|
from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent
|
|
6
|
-
from autobyteus.task_management.events import
|
|
6
|
+
from autobyteus.task_management.events import TasksAddedEvent, TaskStatusUpdatedEvent
|
|
7
7
|
# Need to use a forward reference string to avoid circular import at runtime
|
|
8
8
|
from typing import TYPE_CHECKING, Union
|
|
9
9
|
if TYPE_CHECKING:
|
|
@@ -29,4 +29,4 @@ class SubTeamEventRebroadcastPayload(BaseModel):
|
|
|
29
29
|
sub_team_event: "AgentTeamStreamEvent" = Field(..., description="The original, unmodified event from the sub-team's stream")
|
|
30
30
|
|
|
31
31
|
# --- Payload for events originating from the "TASK_BOARD" source ---
|
|
32
|
-
TaskBoardEventPayload = Union[
|
|
32
|
+
TaskBoardEventPayload = Union[TasksAddedEvent, TaskStatusUpdatedEvent]
|
|
@@ -4,8 +4,12 @@ This package contains components for automatically notifying agents of runnable
|
|
|
4
4
|
"""
|
|
5
5
|
from .system_event_driven_agent_task_notifier import SystemEventDrivenAgentTaskNotifier
|
|
6
6
|
from .task_notification_mode import TaskNotificationMode
|
|
7
|
+
from .activation_policy import ActivationPolicy
|
|
8
|
+
from .task_activator import TaskActivator
|
|
7
9
|
|
|
8
10
|
__all__ = [
|
|
9
11
|
"SystemEventDrivenAgentTaskNotifier",
|
|
10
12
|
"TaskNotificationMode",
|
|
13
|
+
"ActivationPolicy",
|
|
14
|
+
"TaskActivator",
|
|
11
15
|
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/agent_team/task_notification/activation_policy.py
|
|
2
|
+
"""
|
|
3
|
+
Defines the policy for deciding which agents should be activated based on task runnability.
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Set
|
|
7
|
+
|
|
8
|
+
from autobyteus.task_management.task import Task
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class ActivationPolicy:
|
|
13
|
+
"""
|
|
14
|
+
Encapsulates the "Single Wave" notification logic for an agent team.
|
|
15
|
+
|
|
16
|
+
This class maintains a stateful set of agents that have already been
|
|
17
|
+
activated. It decides which new agents should receive a "start work"
|
|
18
|
+
notification based on a list of currently runnable tasks.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, team_id: str):
|
|
21
|
+
"""
|
|
22
|
+
Initializes the ActivationPolicy.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
team_id: The ID of the team this policy belongs to, for logging.
|
|
26
|
+
"""
|
|
27
|
+
self._team_id = team_id
|
|
28
|
+
self._activated_agents: Set[str] = set()
|
|
29
|
+
logger.debug(f"ActivationPolicy initialized for team '{self._team_id}'.")
|
|
30
|
+
|
|
31
|
+
def reset(self):
|
|
32
|
+
"""
|
|
33
|
+
Resets the activation state. This should be called when a new batch of
|
|
34
|
+
tasks is published to the task board, signifying a new plan or a
|
|
35
|
+
significant change in scope.
|
|
36
|
+
"""
|
|
37
|
+
logger.info(f"Team '{self._team_id}': ActivationPolicy state has been reset. All agents are now considered inactive.")
|
|
38
|
+
self._activated_agents.clear()
|
|
39
|
+
|
|
40
|
+
def determine_activations(self, runnable_tasks: List[Task]) -> List[str]:
|
|
41
|
+
"""
|
|
42
|
+
Determines which agents should be activated based on a list of runnable tasks.
|
|
43
|
+
|
|
44
|
+
An agent is selected for activation if they have one or more runnable tasks
|
|
45
|
+
and have not already been activated in the current work cycle.
|
|
46
|
+
|
|
47
|
+
This method is stateful: it updates its internal set of activated agents
|
|
48
|
+
with the new agents it returns.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
runnable_tasks: A list of tasks that are currently runnable.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A list of unique agent names that should be activated.
|
|
55
|
+
"""
|
|
56
|
+
if not runnable_tasks:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
agents_with_runnable_tasks = {task.assignee_name for task in runnable_tasks}
|
|
60
|
+
|
|
61
|
+
# Determine which of these agents have not yet been activated.
|
|
62
|
+
new_agents_to_activate = list(agents_with_runnable_tasks - self._activated_agents)
|
|
63
|
+
|
|
64
|
+
if new_agents_to_activate:
|
|
65
|
+
# Update the state to remember that these agents have now been activated.
|
|
66
|
+
self._activated_agents.update(new_agents_to_activate)
|
|
67
|
+
logger.info(f"Team '{self._team_id}': Policy determined {len(new_agents_to_activate)} new agent(s) to activate: {new_agents_to_activate}. "
|
|
68
|
+
f"Total activated agents is now {len(self._activated_agents)}.")
|
|
69
|
+
|
|
70
|
+
return new_agents_to_activate
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py
|
|
2
|
-
import asyncio
|
|
3
2
|
import logging
|
|
4
|
-
from typing import
|
|
3
|
+
from typing import Union, TYPE_CHECKING
|
|
5
4
|
|
|
6
5
|
from autobyteus.events.event_types import EventType
|
|
7
|
-
from autobyteus.
|
|
8
|
-
from autobyteus.agent.message import AgentInputUserMessage
|
|
9
|
-
from autobyteus.task_management.events import TaskPlanPublishedEvent, TaskStatusUpdatedEvent
|
|
6
|
+
from autobyteus.task_management.events import TasksAddedEvent, TaskStatusUpdatedEvent
|
|
10
7
|
from autobyteus.task_management.base_task_board import TaskStatus
|
|
11
|
-
|
|
8
|
+
|
|
9
|
+
# Import the new, separated components
|
|
10
|
+
from .activation_policy import ActivationPolicy
|
|
11
|
+
from .task_activator import TaskActivator
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from autobyteus.task_management.base_task_board import BaseTaskBoard
|
|
@@ -18,8 +18,11 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
class SystemEventDrivenAgentTaskNotifier:
|
|
20
20
|
"""
|
|
21
|
-
An internal component that monitors a TaskBoard and
|
|
22
|
-
|
|
21
|
+
An internal component that monitors a TaskBoard and orchestrates agent
|
|
22
|
+
activation based on task runnability.
|
|
23
|
+
|
|
24
|
+
This class acts as a conductor, delegating the logic for *when* to activate
|
|
25
|
+
to an ActivationPolicy and the action of *how* to activate to a TaskActivator.
|
|
23
26
|
"""
|
|
24
27
|
def __init__(self, task_board: 'BaseTaskBoard', team_manager: 'TeamManager'):
|
|
25
28
|
"""
|
|
@@ -27,138 +30,69 @@ class SystemEventDrivenAgentTaskNotifier:
|
|
|
27
30
|
|
|
28
31
|
Args:
|
|
29
32
|
task_board: The team's shared task board instance.
|
|
30
|
-
team_manager: The team's manager for
|
|
33
|
+
team_manager: The team's manager for activating agents.
|
|
31
34
|
"""
|
|
32
35
|
if not task_board or not team_manager:
|
|
33
36
|
raise ValueError("TaskBoard and TeamManager are required for the notifier.")
|
|
34
37
|
|
|
35
38
|
self._task_board = task_board
|
|
36
39
|
self._team_manager = team_manager
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
|
|
41
|
+
# Instantiate the components that hold the actual logic and action
|
|
42
|
+
self._policy = ActivationPolicy(team_id=self._team_manager.team_id)
|
|
43
|
+
self._activator = TaskActivator(team_manager=self._team_manager)
|
|
44
|
+
|
|
45
|
+
logger.info(f"SystemEventDrivenAgentTaskNotifier orchestrator initialized for team '{self._team_manager.team_id}'.")
|
|
39
46
|
|
|
40
47
|
def start_monitoring(self):
|
|
41
48
|
"""
|
|
42
49
|
Subscribes to task board events to begin monitoring for runnable tasks.
|
|
43
|
-
This should be called once during the agent team's bootstrap process.
|
|
44
50
|
"""
|
|
45
|
-
self._task_board.subscribe(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
EventType.TASK_BOARD_STATUS_UPDATED,
|
|
51
|
-
self._handle_task_board_update
|
|
52
|
-
)
|
|
53
|
-
logger.info(f"Team '{self._team_manager.team_id}': Task notifier is now monitoring TaskBoard events.")
|
|
54
|
-
|
|
55
|
-
async def _handle_task_board_update(self, payload: Union[TaskPlanPublishedEvent, TaskStatusUpdatedEvent], **kwargs):
|
|
51
|
+
self._task_board.subscribe(EventType.TASK_BOARD_TASKS_ADDED, self._handle_tasks_changed)
|
|
52
|
+
self._task_board.subscribe(EventType.TASK_BOARD_STATUS_UPDATED, self._handle_tasks_changed)
|
|
53
|
+
logger.info(f"Team '{self._team_manager.team_id}': Task notifier orchestrator is now monitoring TaskBoard events.")
|
|
54
|
+
|
|
55
|
+
async def _handle_tasks_changed(self, payload: Union[TasksAddedEvent, TaskStatusUpdatedEvent], **kwargs):
|
|
56
56
|
"""
|
|
57
|
-
|
|
58
|
-
payload to decide when to check for and notify agents of newly runnable tasks.
|
|
57
|
+
Orchestrates the agent activation workflow upon any change to the task board.
|
|
59
58
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
self._dispatched_task_ids.clear()
|
|
63
|
-
await self._scan_and_notify_all_runnable_tasks()
|
|
64
|
-
|
|
65
|
-
elif isinstance(payload, TaskStatusUpdatedEvent):
|
|
66
|
-
# Only trigger a check for dependent tasks if a task has been completed,
|
|
67
|
-
# as this is the only status change that can unblock dependent tasks.
|
|
68
|
-
if payload.new_status == TaskStatus.COMPLETED:
|
|
69
|
-
logger.info(f"Team '{self._team_manager.team_id}': Task '{payload.task_id}' completed. Checking for newly unblocked dependent tasks.")
|
|
70
|
-
await self._check_and_notify_dependent_tasks(payload.task_id)
|
|
71
|
-
else:
|
|
72
|
-
logger.debug(f"Team '{self._team_manager.team_id}': Task '{payload.task_id}' status updated to '{payload.new_status.value}'. No dependent task check needed.")
|
|
73
|
-
else:
|
|
74
|
-
# This case should ideally not be hit with the new strong typing, but is kept as a safeguard.
|
|
75
|
-
logger.warning(f"Team '{self._team_manager.team_id}': Task notifier received an unhandled payload type: {type(payload)}")
|
|
59
|
+
team_id = self._team_manager.team_id
|
|
60
|
+
logger.info(f"Team '{team_id}': Task board changed ({type(payload).__name__}). Orchestrating activation check.")
|
|
76
61
|
|
|
62
|
+
# If a new batch of tasks was added, it's a new "wave" of work.
|
|
63
|
+
# We must reset the policy's memory of who has been activated.
|
|
64
|
+
if isinstance(payload, TasksAddedEvent):
|
|
65
|
+
logger.info(f"Team '{team_id}': New tasks added. Resetting activation policy.")
|
|
66
|
+
self._policy.reset()
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
|
|
83
|
-
if not getattr(self._task_board, 'current_plan', None):
|
|
68
|
+
# 1. DATA FETCHING: Get the current state of runnable tasks from the board.
|
|
69
|
+
runnable_tasks = self._task_board.get_next_runnable_tasks()
|
|
70
|
+
|
|
71
|
+
if not runnable_tasks:
|
|
72
|
+
logger.debug(f"Team '{team_id}': No runnable tasks found after change. No action needed.")
|
|
84
73
|
return
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
|
|
75
|
+
# 2. POLICY DECISION: Ask the policy to decide which agents to activate.
|
|
76
|
+
# The policy contains all the complex state and logic.
|
|
77
|
+
agents_to_activate = self._policy.determine_activations(runnable_tasks)
|
|
88
78
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Now, check if this child task is fully runnable (all its parents are done)
|
|
93
|
-
all_deps_met = all(
|
|
94
|
-
task_statuses.get(dep_id) == TaskStatus.COMPLETED for dep_id in child_task.dependencies
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
if all_deps_met and child_task.task_id not in self._dispatched_task_ids:
|
|
98
|
-
await self._dispatch_notification_for_task(child_task)
|
|
79
|
+
if not agents_to_activate:
|
|
80
|
+
logger.info(f"Team '{team_id}': Runnable tasks exist, but policy determined no new agents need activation.")
|
|
81
|
+
return
|
|
99
82
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if task.task_id
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
async def _dispatch_notification_for_task(self, task: Task):
|
|
113
|
-
"""
|
|
114
|
-
Constructs and sends a context-rich notification for a single runnable task
|
|
115
|
-
by treating it as a user message to trigger the full processing pipeline.
|
|
116
|
-
It tags the message with metadata to indicate its system origin.
|
|
117
|
-
"""
|
|
118
|
-
try:
|
|
119
|
-
team_id = self._team_manager.team_id
|
|
120
|
-
logger.info(f"Team '{team_id}': Dispatching notification for runnable task '{task.task_name}' to assignee '{task.assignee_name}'.")
|
|
121
|
-
|
|
122
|
-
context_from_parents = []
|
|
123
|
-
if task.dependencies:
|
|
124
|
-
parent_task_deliverables_info = []
|
|
125
|
-
for dep_id in task.dependencies:
|
|
126
|
-
parent_task = getattr(self._task_board, '_task_map', {}).get(dep_id)
|
|
127
|
-
if parent_task and parent_task.file_deliverables:
|
|
128
|
-
deliverables_str = "\n".join(
|
|
129
|
-
[f" - File: {d.file_path}, Summary: {d.summary}" for d in parent_task.file_deliverables]
|
|
130
|
-
)
|
|
131
|
-
parent_task_deliverables_info.append(
|
|
132
|
-
f"The parent task '{parent_task.task_name}' produced the following deliverables:\n{deliverables_str}"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if parent_task_deliverables_info:
|
|
136
|
-
context_from_parents.append(
|
|
137
|
-
"Your task is now unblocked. Here is the context from the completed parent task(s):\n" +
|
|
138
|
-
"\n\n".join(parent_task_deliverables_info)
|
|
83
|
+
# 3. ACTION EXECUTION: Tell the activator to perform the activations.
|
|
84
|
+
for agent_name in agents_to_activate:
|
|
85
|
+
# First, update the status for all of that agent's runnable tasks to QUEUED.
|
|
86
|
+
# This is a state change action, which the orchestrator is responsible for.
|
|
87
|
+
agent_runnable_tasks = [t for t in runnable_tasks if t.assignee_name == agent_name]
|
|
88
|
+
for task in agent_runnable_tasks:
|
|
89
|
+
# We only need to queue tasks that are NOT_STARTED.
|
|
90
|
+
if self._task_board.task_statuses.get(task.task_id) == TaskStatus.NOT_STARTED:
|
|
91
|
+
self._task_board.update_task_status(
|
|
92
|
+
task_id=task.task_id,
|
|
93
|
+
status=TaskStatus.QUEUED,
|
|
94
|
+
agent_name="SystemTaskNotifier"
|
|
139
95
|
)
|
|
140
|
-
|
|
141
|
-
message_parts: List[str] = [f"Your task '{task.task_name}' is now ready to start."]
|
|
142
|
-
if context_from_parents:
|
|
143
|
-
message_parts.extend(context_from_parents)
|
|
144
|
-
|
|
145
|
-
message_parts.append(f"\nYour task description:\n{task.description}")
|
|
146
|
-
|
|
147
|
-
content = "\n\n".join(message_parts)
|
|
148
96
|
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
content=content,
|
|
152
|
-
metadata={'source': 'system_task_notifier'}
|
|
153
|
-
)
|
|
154
|
-
event = ProcessUserMessageEvent(
|
|
155
|
-
user_message=user_message,
|
|
156
|
-
target_agent_name=task.assignee_name
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
# Use the existing method for dispatching user messages.
|
|
160
|
-
await self._team_manager.dispatch_user_message_to_agent(event)
|
|
161
|
-
self._dispatched_task_ids.add(task.task_id)
|
|
162
|
-
|
|
163
|
-
except Exception as e:
|
|
164
|
-
logger.error(f"Team '{self._team_manager.team_id}': Failed to dispatch notification for task '{task.task_id}': {e}", exc_info=True)
|
|
97
|
+
# Now, trigger the single activation notification.
|
|
98
|
+
await self._activator.activate_agent(agent_name)
|