autobyteus 1.1.8__py3-none-any.whl → 1.2.0__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/clients/__init__.py +10 -0
- autobyteus/clients/autobyteus_client.py +318 -0
- autobyteus/clients/cert_utils.py +105 -0
- autobyteus/clients/certificates/cert.pem +34 -0
- autobyteus/events/event_types.py +2 -2
- autobyteus/llm/api/autobyteus_llm.py +1 -1
- autobyteus/llm/api/gemini_llm.py +45 -54
- autobyteus/llm/api/qwen_llm.py +25 -0
- autobyteus/llm/api/zhipu_llm.py +26 -0
- autobyteus/llm/autobyteus_provider.py +9 -3
- autobyteus/llm/llm_factory.py +39 -0
- autobyteus/llm/ollama_provider_resolver.py +1 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +3 -0
- autobyteus/llm/token_counter/zhipu_token_counter.py +24 -0
- autobyteus/multimedia/audio/api/autobyteus_audio_client.py +5 -2
- 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 +9 -3
- autobyteus/multimedia/audio/base_audio_client.py +3 -1
- autobyteus/multimedia/image/api/autobyteus_image_client.py +13 -6
- 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 +9 -3
- 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 +10 -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} +6 -7
- autobyteus/task_management/schemas/task_status_report.py +1 -2
- autobyteus/task_management/task.py +60 -0
- autobyteus/task_management/tools/__init__.py +6 -2
- autobyteus/task_management/tools/assign_task_to.py +125 -0
- 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 +54 -16
- 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/download_media_tool.py +136 -0
- autobyteus/tools/file/file_editor.py +200 -0
- autobyteus/tools/functional_tool.py +1 -1
- autobyteus/tools/google_search.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/usage/parsers/_string_decoders.py +18 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +9 -1
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +15 -1
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +4 -1
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +4 -1
- autobyteus/{tools → utils}/parameter_schema.py +1 -1
- {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/METADATA +4 -3
- {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/RECORD +122 -108
- 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/tools/image_downloader.py +0 -99
- autobyteus/tools/pdf_downloader.py +0 -89
- {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.8.dist-info → autobyteus-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -5,23 +5,23 @@ Defines the Pydantic models for events emitted by a TaskBoard.
|
|
|
5
5
|
from typing import List, Optional
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from autobyteus.task_management.
|
|
8
|
+
from autobyteus.task_management.task import Task
|
|
9
9
|
from autobyteus.task_management.base_task_board import TaskStatus
|
|
10
10
|
from .deliverable import FileDeliverable
|
|
11
11
|
|
|
12
12
|
class BaseTaskBoardEvent(BaseModel):
|
|
13
13
|
"""Base class for all task board events."""
|
|
14
14
|
team_id: str
|
|
15
|
-
plan_id: Optional[str]
|
|
16
15
|
|
|
17
|
-
class
|
|
18
|
-
"""
|
|
19
|
-
|
|
16
|
+
class TasksAddedEvent(BaseTaskBoardEvent):
|
|
17
|
+
"""
|
|
18
|
+
Payload for when one or more tasks are added to the board.
|
|
19
|
+
"""
|
|
20
|
+
tasks: List[Task]
|
|
20
21
|
|
|
21
22
|
class TaskStatusUpdatedEvent(BaseTaskBoardEvent):
|
|
22
23
|
"""Payload for when a task's status is updated."""
|
|
23
24
|
task_id: str
|
|
24
25
|
new_status: TaskStatus
|
|
25
26
|
agent_name: str
|
|
26
|
-
# This field is added to ensure listeners get the full state, including deliverables.
|
|
27
27
|
deliverables: Optional[List[FileDeliverable]] = None
|
|
@@ -8,9 +8,9 @@ from typing import Optional, List, Dict, Any
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
|
|
10
10
|
from autobyteus.events.event_types import EventType
|
|
11
|
-
from .
|
|
11
|
+
from .task import Task
|
|
12
12
|
from .base_task_board import BaseTaskBoard, TaskStatus
|
|
13
|
-
from .events import
|
|
13
|
+
from .events import TasksAddedEvent, TaskStatusUpdatedEvent
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -23,37 +23,63 @@ class InMemoryTaskBoard(BaseTaskBoard):
|
|
|
23
23
|
"""
|
|
24
24
|
Initializes the InMemoryTaskBoard.
|
|
25
25
|
"""
|
|
26
|
-
# BaseTaskBoard now handles EventEmitter initialization
|
|
27
26
|
super().__init__(team_id=team_id)
|
|
28
|
-
self.current_plan: Optional[TaskPlan] = None
|
|
29
27
|
self.task_statuses: Dict[str, TaskStatus] = {}
|
|
30
28
|
self._task_map: Dict[str, Task] = {}
|
|
31
29
|
logger.info(f"InMemoryTaskBoard initialized for team '{self.team_id}'.")
|
|
32
30
|
|
|
33
|
-
def
|
|
31
|
+
def add_tasks(self, tasks: List[Task]) -> bool:
|
|
34
32
|
"""
|
|
35
|
-
|
|
33
|
+
Adds new tasks to the board. This is an additive-only operation.
|
|
36
34
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
for task in tasks:
|
|
36
|
+
self.tasks.append(task)
|
|
37
|
+
self.task_statuses[task.task_id] = TaskStatus.NOT_STARTED
|
|
38
|
+
self._task_map[task.task_id] = task
|
|
40
39
|
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
self._task_map = {task.task_id: task for task in plan.tasks}
|
|
44
|
-
|
|
45
|
-
logger.info(f"Team '{self.team_id}': New TaskPlan '{plan.plan_id}' loaded. Emitting event.")
|
|
40
|
+
self._hydrate_all_dependencies()
|
|
41
|
+
logger.info(f"Team '{self.team_id}': Added {len(tasks)} new task(s) to the board. Emitting TasksAddedEvent.")
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
event_payload = TaskPlanPublishedEvent(
|
|
43
|
+
event_payload = TasksAddedEvent(
|
|
49
44
|
team_id=self.team_id,
|
|
50
|
-
|
|
51
|
-
plan=plan
|
|
45
|
+
tasks=tasks,
|
|
52
46
|
)
|
|
53
|
-
self.emit(EventType.
|
|
54
|
-
|
|
47
|
+
self.emit(EventType.TASK_BOARD_TASKS_ADDED, payload=event_payload)
|
|
55
48
|
return True
|
|
56
49
|
|
|
50
|
+
def add_task(self, task: Task) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Adds a single new task to the board by wrapping it in a list and calling add_tasks.
|
|
53
|
+
"""
|
|
54
|
+
return self.add_tasks([task])
|
|
55
|
+
|
|
56
|
+
def _hydrate_all_dependencies(self):
|
|
57
|
+
"""
|
|
58
|
+
Re-calculates all dependencies to ensure they are all valid task_ids.
|
|
59
|
+
This robustly handles dependencies that are already IDs and those that are names.
|
|
60
|
+
"""
|
|
61
|
+
name_to_id_map = {task.task_name: task.task_id for task in self.tasks}
|
|
62
|
+
all_task_ids = set(self._task_map.keys())
|
|
63
|
+
|
|
64
|
+
for task in self.tasks:
|
|
65
|
+
if not task.dependencies:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
resolved_deps = []
|
|
69
|
+
for dep in task.dependencies:
|
|
70
|
+
# Case 1: The dependency is already a valid task_id on the board.
|
|
71
|
+
if dep in all_task_ids:
|
|
72
|
+
resolved_deps.append(dep)
|
|
73
|
+
# Case 2: The dependency is a task_name that can be resolved.
|
|
74
|
+
elif dep in name_to_id_map:
|
|
75
|
+
resolved_deps.append(name_to_id_map[dep])
|
|
76
|
+
# Case 3: The dependency is invalid.
|
|
77
|
+
else:
|
|
78
|
+
logger.warning(f"Team '{self.team_id}': Dependency '{dep}' for task '{task.task_name}' could not be resolved to a known task ID or name.")
|
|
79
|
+
|
|
80
|
+
task.dependencies = resolved_deps
|
|
81
|
+
|
|
82
|
+
|
|
57
83
|
def update_task_status(self, task_id: str, status: TaskStatus, agent_name: str) -> bool:
|
|
58
84
|
"""
|
|
59
85
|
Updates the status of a specific task and emits an event.
|
|
@@ -67,40 +93,27 @@ class InMemoryTaskBoard(BaseTaskBoard):
|
|
|
67
93
|
log_msg = f"Team '{self.team_id}': Status of task '{task_id}' updated from '{old_status.value if isinstance(old_status, Enum) else old_status}' to '{status.value}' by agent '{agent_name}'."
|
|
68
94
|
logger.info(log_msg)
|
|
69
95
|
|
|
70
|
-
# Find the task to get its deliverables for the event payload
|
|
71
96
|
task = self._task_map.get(task_id)
|
|
72
97
|
task_deliverables = task.file_deliverables if task else None
|
|
73
98
|
|
|
74
|
-
# Emit event
|
|
75
99
|
event_payload = TaskStatusUpdatedEvent(
|
|
76
100
|
team_id=self.team_id,
|
|
77
|
-
plan_id=self.current_plan.plan_id if self.current_plan else None,
|
|
78
101
|
task_id=task_id,
|
|
79
102
|
new_status=status,
|
|
80
103
|
agent_name=agent_name,
|
|
81
104
|
deliverables=task_deliverables
|
|
82
105
|
)
|
|
83
106
|
self.emit(EventType.TASK_BOARD_STATUS_UPDATED, payload=event_payload)
|
|
84
|
-
|
|
85
107
|
return True
|
|
86
108
|
|
|
87
109
|
def get_status_overview(self) -> Dict[str, Any]:
|
|
88
110
|
"""
|
|
89
111
|
Returns a serializable dictionary of the board's current state.
|
|
112
|
+
The overall_goal is now fetched from the context via the converter.
|
|
90
113
|
"""
|
|
91
|
-
if not self.current_plan:
|
|
92
|
-
return {
|
|
93
|
-
"plan_id": None,
|
|
94
|
-
"overall_goal": None,
|
|
95
|
-
"task_statuses": {},
|
|
96
|
-
"tasks": []
|
|
97
|
-
}
|
|
98
|
-
|
|
99
114
|
return {
|
|
100
|
-
"plan_id": self.current_plan.plan_id,
|
|
101
|
-
"overall_goal": self.current_plan.overall_goal,
|
|
102
115
|
"task_statuses": {task_id: status.value for task_id, status in self.task_statuses.items()},
|
|
103
|
-
"tasks": [task.model_dump() for task in self.
|
|
116
|
+
"tasks": [task.model_dump() for task in self.tasks]
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
def get_next_runnable_tasks(self) -> List[Task]:
|
|
@@ -108,9 +121,6 @@ class InMemoryTaskBoard(BaseTaskBoard):
|
|
|
108
121
|
Calculates which tasks can be executed now based on dependencies and statuses.
|
|
109
122
|
"""
|
|
110
123
|
runnable_tasks: List[Task] = []
|
|
111
|
-
if not self.current_plan:
|
|
112
|
-
return runnable_tasks
|
|
113
|
-
|
|
114
124
|
for task_id, status in self.task_statuses.items():
|
|
115
125
|
if status == TaskStatus.NOT_STARTED:
|
|
116
126
|
task = self._task_map.get(task_id)
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Exposes the public schema models for the task management module.
|
|
4
4
|
"""
|
|
5
|
-
from .
|
|
5
|
+
from .task_definition import TasksDefinitionSchema, TaskDefinitionSchema
|
|
6
6
|
from .task_status_report import TaskStatusReportSchema, TaskStatusReportItemSchema
|
|
7
7
|
from .deliverable_schema import FileDeliverableSchema
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
|
-
"
|
|
10
|
+
"TasksDefinitionSchema",
|
|
11
11
|
"TaskDefinitionSchema",
|
|
12
12
|
"TaskStatusReportSchema",
|
|
13
13
|
"TaskStatusReportItemSchema",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Defines the Pydantic models for task
|
|
2
|
+
Defines the Pydantic models for task "definitions".
|
|
3
3
|
|
|
4
4
|
These models represent the exact structure that an LLM is expected to generate.
|
|
5
5
|
They serve as a blueprint or definition for a plan, which the system then uses
|
|
@@ -13,20 +13,19 @@ class TaskDefinitionSchema(BaseModel):
|
|
|
13
13
|
"""A Pydantic model representing a single task as defined by an LLM."""
|
|
14
14
|
task_name: str = Field(..., description="A short, unique, descriptive name for this task within the plan (e.g., 'setup_project', 'implement_scraper'). Used for defining dependencies.")
|
|
15
15
|
assignee_name: str = Field(..., description="The name of the agent or sub-team assigned to this task.")
|
|
16
|
-
description: str = Field(..., description="A detailed description of the task.")
|
|
16
|
+
description: str = Field(..., description="A clear, detailed, and unambiguous description of what this task entails. Provide all necessary context for the assignee to complete the work. For example, if the task involves a file, specify its full, absolute path. If it requires creating a file, specify where it should be saved. Mention any specific requirements or expected outputs.")
|
|
17
17
|
dependencies: List[str] = Field(
|
|
18
18
|
default_factory=list,
|
|
19
19
|
description="A list of 'task_name' values for tasks that must be completed first."
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
class
|
|
23
|
-
"""A Pydantic model representing a
|
|
24
|
-
|
|
25
|
-
tasks: List[TaskDefinitionSchema] = Field(..., description="The list of tasks that make up this plan.")
|
|
22
|
+
class TasksDefinitionSchema(BaseModel):
|
|
23
|
+
"""A Pydantic model representing a list of tasks as generated by an LLM."""
|
|
24
|
+
tasks: List[TaskDefinitionSchema] = Field(..., description="The list of tasks to be published.")
|
|
26
25
|
|
|
27
26
|
@field_validator('tasks')
|
|
28
27
|
def task_names_must_be_unique(cls, tasks: List[TaskDefinitionSchema]) -> List[TaskDefinitionSchema]:
|
|
29
|
-
"""Ensures that the LLM-provided task_names are unique within
|
|
28
|
+
"""Ensures that the LLM-provided task_names are unique within this list."""
|
|
30
29
|
seen_names = set()
|
|
31
30
|
for task in tasks:
|
|
32
31
|
if task.task_name in seen_names:
|
|
@@ -16,12 +16,11 @@ class TaskStatusReportItemSchema(BaseModel):
|
|
|
16
16
|
"""Represents the status of a single task in an LLM-friendly format."""
|
|
17
17
|
task_name: str = Field(..., description="The unique, descriptive name for this task.")
|
|
18
18
|
assignee_name: str = Field(..., description="The name of the agent or sub-team assigned to this task.")
|
|
19
|
-
description: str = Field(..., description="A detailed description of the task.")
|
|
19
|
+
description: str = Field(..., description="A clear, detailed, and unambiguous description of what this task entails. Provide all necessary context for the assignee to complete the work. For example, if the task involves a file, specify its full, absolute path. If it requires creating a file, specify where it should be saved. Mention any specific requirements or expected outputs.")
|
|
20
20
|
dependencies: List[str] = Field(..., description="A list of 'task_name' values for tasks that must be completed first.")
|
|
21
21
|
status: TaskStatus = Field(..., description="The current status of this task.")
|
|
22
22
|
file_deliverables: List[FileDeliverable] = Field(default_factory=list, description="A list of files submitted as deliverables for this task.")
|
|
23
23
|
|
|
24
24
|
class TaskStatusReportSchema(BaseModel):
|
|
25
25
|
"""Represents a full task board status report in an LLM-friendly format."""
|
|
26
|
-
overall_goal: str = Field(..., description="The high-level objective of the entire plan.")
|
|
27
26
|
tasks: List[TaskStatusReportItemSchema] = Field(..., description="The list of tasks and their current statuses.")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/task_management/task.py
|
|
2
|
+
"""
|
|
3
|
+
Defines the data structures for a task.
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import List, Any
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
|
+
|
|
10
|
+
# To avoid circular import, we use a string forward reference.
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from autobyteus.task_management.deliverable import FileDeliverable
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def generate_task_id():
|
|
18
|
+
"""Generates a unique task identifier."""
|
|
19
|
+
return f"task_{uuid.uuid4().hex}"
|
|
20
|
+
|
|
21
|
+
class Task(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Represents a single, discrete unit of work.
|
|
24
|
+
"""
|
|
25
|
+
task_name: str = Field(..., description="A short, unique, descriptive name for this task within the plan (e.g., 'setup_project', 'implement_scraper'). Used for defining dependencies.")
|
|
26
|
+
|
|
27
|
+
task_id: str = Field(default_factory=generate_task_id, description="A unique system-generated identifier for this task within the plan.")
|
|
28
|
+
|
|
29
|
+
assignee_name: str = Field(..., description="The unique name of the agent or sub-team responsible for executing this task (e.g., 'SoftwareEngineer', 'ResearchTeam').")
|
|
30
|
+
description: str = Field(..., description="A clear and concise description of what this task entails.")
|
|
31
|
+
|
|
32
|
+
dependencies: List[str] = Field(
|
|
33
|
+
default_factory=list,
|
|
34
|
+
description="A list of 'task_name' values for tasks that must be completed before this one can be started."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# This is the updated field as per user request.
|
|
38
|
+
file_deliverables: List["FileDeliverable"] = Field(
|
|
39
|
+
default_factory=list,
|
|
40
|
+
description="A list of file deliverables that were produced as a result of completing this task."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@model_validator(mode='before')
|
|
44
|
+
@classmethod
|
|
45
|
+
def handle_local_id_compatibility(cls, data: Any) -> Any:
|
|
46
|
+
"""Handles backward compatibility for the 'local_id' field."""
|
|
47
|
+
if isinstance(data, dict) and 'local_id' in data:
|
|
48
|
+
data['task_name'] = data.pop('local_id')
|
|
49
|
+
# Compatibility for old artifact field
|
|
50
|
+
if isinstance(data, dict) and 'produced_artifact_ids' in data:
|
|
51
|
+
del data['produced_artifact_ids']
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
def model_post_init(self, __context: Any) -> None:
|
|
55
|
+
"""Called after the model is initialized and validated."""
|
|
56
|
+
logger.debug(f"Task created: Name='{self.task_name}', SystemID='{self.task_id}', Assignee='{self.assignee_name}'")
|
|
57
|
+
|
|
58
|
+
# This is necessary for Pydantic v2 to correctly handle the recursive model
|
|
59
|
+
from autobyteus.task_management.deliverable import FileDeliverable
|
|
60
|
+
Task.model_rebuild()
|
|
@@ -4,11 +4,15 @@ This package contains the class-based tools related to task and project
|
|
|
4
4
|
management within an agent team.
|
|
5
5
|
"""
|
|
6
6
|
from .get_task_board_status import GetTaskBoardStatus
|
|
7
|
-
from .
|
|
7
|
+
from .publish_tasks import PublishTasks
|
|
8
|
+
from .publish_task import PublishTask
|
|
8
9
|
from .update_task_status import UpdateTaskStatus
|
|
10
|
+
from .assign_task_to import AssignTaskTo
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
11
13
|
"GetTaskBoardStatus",
|
|
12
|
-
"
|
|
14
|
+
"PublishTasks",
|
|
15
|
+
"PublishTask",
|
|
13
16
|
"UpdateTaskStatus",
|
|
17
|
+
"AssignTaskTo",
|
|
14
18
|
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/task_management/tools/assign_task_to.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
8
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
9
|
+
from autobyteus.utils.parameter_schema import ParameterSchema
|
|
10
|
+
from autobyteus.tools.pydantic_schema_converter import pydantic_to_parameter_schema
|
|
11
|
+
from autobyteus.task_management.schemas import TaskDefinitionSchema
|
|
12
|
+
from autobyteus.task_management.task import Task
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from autobyteus.agent.context import AgentContext
|
|
16
|
+
from autobyteus.agent_team.context import AgentTeamContext
|
|
17
|
+
from autobyteus.agent_team.context.team_manager import TeamManager
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
class AssignTaskTo(BaseTool):
|
|
22
|
+
"""A tool for one agent to directly create and assign a single task to another agent."""
|
|
23
|
+
|
|
24
|
+
CATEGORY = ToolCategory.TASK_MANAGEMENT
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_name(cls) -> str:
|
|
28
|
+
return "AssignTaskTo"
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_description(cls) -> str:
|
|
32
|
+
return (
|
|
33
|
+
"Creates and assigns a single new task to a specific team member, and sends them a direct notification "
|
|
34
|
+
"with the task details. Use this to delegate a well-defined piece of work you have identified."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
39
|
+
# The schema is the same as for defining a single task.
|
|
40
|
+
return pydantic_to_parameter_schema(TaskDefinitionSchema)
|
|
41
|
+
|
|
42
|
+
async def _execute(self, context: 'AgentContext', **kwargs: Any) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Executes the tool by adding the task to the central TaskBoard and then
|
|
45
|
+
sending a direct message to the assignee with the task's details.
|
|
46
|
+
"""
|
|
47
|
+
agent_name = context.config.name
|
|
48
|
+
task_name = kwargs.get("task_name", "unnamed task")
|
|
49
|
+
assignee_name = kwargs.get("assignee_name")
|
|
50
|
+
logger.info(f"Agent '{agent_name}' is executing AssignTaskTo for task '{task_name}' assigned to '{assignee_name}'.")
|
|
51
|
+
|
|
52
|
+
# --- Get Team Context and Task Board ---
|
|
53
|
+
team_context: Optional['AgentTeamContext'] = context.custom_data.get("team_context")
|
|
54
|
+
if not team_context:
|
|
55
|
+
error_msg = "Error: Team context is not available. Cannot access the task board or send messages."
|
|
56
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
57
|
+
return error_msg
|
|
58
|
+
|
|
59
|
+
task_board = getattr(team_context.state, 'task_board', None)
|
|
60
|
+
if not task_board:
|
|
61
|
+
error_msg = "Error: Task board has not been initialized for this team."
|
|
62
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
63
|
+
return error_msg
|
|
64
|
+
|
|
65
|
+
# --- Action 1: Add the task to the Task Board ---
|
|
66
|
+
try:
|
|
67
|
+
task_def_schema = TaskDefinitionSchema(**kwargs)
|
|
68
|
+
new_task = Task(**task_def_schema.model_dump())
|
|
69
|
+
except (ValidationError, ValueError) as e:
|
|
70
|
+
error_msg = f"Invalid task definition provided: {e}"
|
|
71
|
+
logger.warning(f"Agent '{agent_name}' provided an invalid definition for AssignTaskTo: {error_msg}")
|
|
72
|
+
return f"Error: {error_msg}"
|
|
73
|
+
|
|
74
|
+
if not task_board.add_task(new_task):
|
|
75
|
+
error_msg = f"Failed to publish task '{new_task.task_name}' to the board for an unknown reason."
|
|
76
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
77
|
+
return f"Error: {error_msg}"
|
|
78
|
+
|
|
79
|
+
logger.info(f"Agent '{agent_name}' successfully published task '{new_task.task_name}' to the task board.")
|
|
80
|
+
|
|
81
|
+
# --- Action 2: Send a direct notification message to the assignee ---
|
|
82
|
+
team_manager: Optional['TeamManager'] = team_context.team_manager
|
|
83
|
+
if not team_manager:
|
|
84
|
+
# This is a degraded state, but the primary action (publishing) succeeded.
|
|
85
|
+
warning_msg = (f"Successfully published task '{new_task.task_name}', but could not send a direct notification "
|
|
86
|
+
"because the TeamManager is not available.")
|
|
87
|
+
logger.warning(f"Agent '{agent_name}': {warning_msg}")
|
|
88
|
+
return warning_msg
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Local import to break potential circular dependency at module load time.
|
|
92
|
+
from autobyteus.agent_team.events.agent_team_events import InterAgentMessageRequestEvent
|
|
93
|
+
|
|
94
|
+
notification_content = (
|
|
95
|
+
f"You have been assigned a new task directly from agent '{agent_name}'.\n\n"
|
|
96
|
+
f"**Task Name**: '{new_task.task_name}'\n"
|
|
97
|
+
f"**Description**: {new_task.description}\n"
|
|
98
|
+
)
|
|
99
|
+
if new_task.dependencies:
|
|
100
|
+
# Resolve dependency names for the message
|
|
101
|
+
id_to_name_map = {task.task_id: task.task_name for task in task_board.tasks}
|
|
102
|
+
dep_names = [id_to_name_map.get(dep_id, str(dep_id)) for dep_id in new_task.dependencies]
|
|
103
|
+
notification_content += f"**Dependencies**: {', '.join(dep_names)}\n"
|
|
104
|
+
|
|
105
|
+
notification_content += "\nThis task has been logged on the team's task board. You can begin work when its dependencies are met."
|
|
106
|
+
|
|
107
|
+
event = InterAgentMessageRequestEvent(
|
|
108
|
+
sender_agent_id=context.agent_id,
|
|
109
|
+
recipient_name=new_task.assignee_name,
|
|
110
|
+
content=notification_content,
|
|
111
|
+
message_type="task_assignment"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
await team_manager.dispatch_inter_agent_message_request(event)
|
|
115
|
+
logger.info(f"Agent '{agent_name}' successfully dispatched a notification message for task '{new_task.task_name}' to '{new_task.assignee_name}'.")
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
# Again, this is a degraded state. The main goal was achieved.
|
|
119
|
+
warning_msg = (f"Successfully published task '{new_task.task_name}', but failed to send the direct notification message. "
|
|
120
|
+
f"Error: {e}")
|
|
121
|
+
logger.error(f"Agent '{agent_name}': {warning_msg}", exc_info=True)
|
|
122
|
+
return warning_msg
|
|
123
|
+
|
|
124
|
+
success_msg = f"Successfully assigned task '{new_task.task_name}' to agent '{new_task.assignee_name}' and sent a notification."
|
|
125
|
+
return success_msg
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/task_management/tools/get_my_tasks.py
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, List
|
|
5
|
+
|
|
6
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
7
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
8
|
+
from autobyteus.task_management.schemas import TaskDefinitionSchema
|
|
9
|
+
from autobyteus.task_management.base_task_board import TaskStatus
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from autobyteus.agent.context import AgentContext
|
|
13
|
+
from autobyteus.agent_team.context import AgentTeamContext
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class GetMyTasks(BaseTool):
|
|
18
|
+
"""A tool for an agent to inspect its own assigned tasks from the central TaskBoard."""
|
|
19
|
+
|
|
20
|
+
CATEGORY = ToolCategory.TASK_MANAGEMENT
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_name(cls) -> str:
|
|
24
|
+
return "GetMyTasks"
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_description(cls) -> str:
|
|
28
|
+
return (
|
|
29
|
+
"Retrieves the list of tasks currently assigned to you from the team's shared task board. "
|
|
30
|
+
"This is your personal to-do list. Use this to understand your current workload and decide what to do next."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def get_argument_schema(cls) -> Optional[None]:
|
|
35
|
+
# This tool takes no arguments.
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
async def _execute(self, context: 'AgentContext') -> str:
|
|
39
|
+
"""
|
|
40
|
+
Executes the tool by fetching tasks from the team's TaskBoard and
|
|
41
|
+
filtering them for the current agent.
|
|
42
|
+
"""
|
|
43
|
+
agent_name = context.config.name
|
|
44
|
+
logger.info(f"Agent '{agent_name}' is executing GetMyTasks.")
|
|
45
|
+
|
|
46
|
+
team_context: Optional['AgentTeamContext'] = context.custom_data.get("team_context")
|
|
47
|
+
if not team_context:
|
|
48
|
+
error_msg = "Error: Team context is not available. Cannot access the task board."
|
|
49
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
50
|
+
return error_msg
|
|
51
|
+
|
|
52
|
+
task_board = getattr(team_context.state, 'task_board', None)
|
|
53
|
+
if not task_board:
|
|
54
|
+
error_msg = "Error: Task board has not been initialized for this team."
|
|
55
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
56
|
+
return error_msg
|
|
57
|
+
|
|
58
|
+
# Filter the tasks from the central board for this agent.
|
|
59
|
+
# An agent should only see tasks that are specifically for them and are ready to be worked on.
|
|
60
|
+
my_tasks = [
|
|
61
|
+
task for task in task_board.tasks
|
|
62
|
+
if task.assignee_name == agent_name and task_board.task_statuses.get(task.task_id) == TaskStatus.QUEUED
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
if not my_tasks:
|
|
66
|
+
return "Your personal task queue is empty. You have no new tasks assigned and ready to be started."
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Convert the internal Task objects back to the LLM-friendly schema.
|
|
70
|
+
tasks_for_llm = [
|
|
71
|
+
TaskDefinitionSchema.model_validate(task).model_dump() for task in my_tasks
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
logger.info(f"Agent '{agent_name}' retrieved {len(tasks_for_llm)} tasks from the central task board.")
|
|
75
|
+
return json.dumps(tasks_for_llm, indent=2)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
error_msg = f"An unexpected error occurred while formatting your tasks: {e}"
|
|
79
|
+
logger.error(f"Agent '{agent_name}': {error_msg}", exc_info=True)
|
|
80
|
+
return f"Error: {error_msg}"
|
|
@@ -25,8 +25,8 @@ class GetTaskBoardStatus(BaseTool):
|
|
|
25
25
|
@classmethod
|
|
26
26
|
def get_description(cls) -> str:
|
|
27
27
|
return (
|
|
28
|
-
"Retrieves the current status of the team's task board, including the
|
|
29
|
-
"
|
|
28
|
+
"Retrieves the current status of the team's task board, including the status of all individual tasks. "
|
|
29
|
+
"Returns the status as a structured, LLM-friendly JSON string."
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
@@ -57,7 +57,7 @@ class GetTaskBoardStatus(BaseTool):
|
|
|
57
57
|
status_report_schema = TaskBoardConverter.to_schema(task_board)
|
|
58
58
|
|
|
59
59
|
if not status_report_schema:
|
|
60
|
-
return "The task board is currently empty. No
|
|
60
|
+
return "The task board is currently empty. No tasks have been published."
|
|
61
61
|
|
|
62
62
|
logger.info(f"Agent '{context.agent_id}' successfully retrieved and formatted task board status.")
|
|
63
63
|
return status_report_schema.model_dump_json(indent=2)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/task_management/tools/publish_task.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Dict, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from autobyteus.tools.base_tool import BaseTool
|
|
8
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
9
|
+
from autobyteus.utils.parameter_schema import ParameterSchema
|
|
10
|
+
from autobyteus.tools.pydantic_schema_converter import pydantic_to_parameter_schema
|
|
11
|
+
from autobyteus.task_management.schemas import TaskDefinitionSchema
|
|
12
|
+
from autobyteus.task_management.task import Task
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from autobyteus.agent.context import AgentContext
|
|
16
|
+
from autobyteus.agent_team.context import AgentTeamContext
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
class PublishTask(BaseTool):
|
|
21
|
+
"""A tool for any agent to add a single new task to the team's task board."""
|
|
22
|
+
|
|
23
|
+
CATEGORY = ToolCategory.TASK_MANAGEMENT
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def get_name(cls) -> str:
|
|
27
|
+
return "PublishTask"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_description(cls) -> str:
|
|
31
|
+
return (
|
|
32
|
+
"Adds a single new task to the team's shared task board. This is an additive action "
|
|
33
|
+
"and does not affect existing tasks. Use this to create follow-up tasks or delegate new work."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
38
|
+
# The schema for this tool is effectively the schema of a single task definition.
|
|
39
|
+
return pydantic_to_parameter_schema(TaskDefinitionSchema)
|
|
40
|
+
|
|
41
|
+
async def _execute(self, context: 'AgentContext', **kwargs: Any) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Executes the tool by validating the task object and adding it to the board.
|
|
44
|
+
"""
|
|
45
|
+
agent_name = context.config.name
|
|
46
|
+
task_name = kwargs.get("task_name", "unnamed task")
|
|
47
|
+
logger.info(f"Agent '{agent_name}' is executing PublishTask for task '{task_name}'.")
|
|
48
|
+
|
|
49
|
+
team_context: Optional['AgentTeamContext'] = context.custom_data.get("team_context")
|
|
50
|
+
if not team_context:
|
|
51
|
+
error_msg = "Error: Team context is not available. Cannot access the task board."
|
|
52
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
53
|
+
return error_msg
|
|
54
|
+
|
|
55
|
+
task_board = getattr(team_context.state, 'task_board', None)
|
|
56
|
+
if not task_board:
|
|
57
|
+
error_msg = "Error: Task board has not been initialized for this team."
|
|
58
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
59
|
+
return error_msg
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
task_def_schema = TaskDefinitionSchema(**kwargs)
|
|
63
|
+
new_task = Task(**task_def_schema.model_dump())
|
|
64
|
+
except (ValidationError, ValueError) as e:
|
|
65
|
+
error_msg = f"Invalid task definition provided: {e}"
|
|
66
|
+
logger.warning(f"Agent '{agent_name}' provided an invalid definition for PublishTask: {error_msg}")
|
|
67
|
+
return f"Error: {error_msg}"
|
|
68
|
+
|
|
69
|
+
if task_board.add_task(new_task):
|
|
70
|
+
success_msg = f"Successfully published new task '{new_task.task_name}' to the task board."
|
|
71
|
+
logger.info(f"Agent '{agent_name}': {success_msg}")
|
|
72
|
+
return success_msg
|
|
73
|
+
else:
|
|
74
|
+
# This path is less likely now but kept for robustness.
|
|
75
|
+
error_msg = "Failed to publish task to the board for an unknown reason."
|
|
76
|
+
logger.error(f"Agent '{agent_name}': {error_msg}")
|
|
77
|
+
return f"Error: {error_msg}"
|