monoco-toolkit 0.3.11__py3-none-any.whl → 0.4.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.
- monoco/core/automation/__init__.py +40 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +805 -0
- monoco/core/config.py +29 -11
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/git.py +15 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +17 -0
- monoco/core/router/action.py +202 -0
- monoco/core/scheduler/__init__.py +63 -0
- monoco/core/scheduler/base.py +152 -0
- monoco/core/scheduler/engines.py +175 -0
- monoco/core/scheduler/events.py +197 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +63 -0
- monoco/core/watcher/base.py +382 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +192 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/events.py +34 -0
- monoco/daemon/scheduler.py +157 -201
- monoco/daemon/services.py +42 -243
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/worker.py +1 -1
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +133 -60
- monoco/features/issue/core.py +385 -40
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/features/spike/commands.py +5 -3
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/execution.py +0 -67
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -127
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/agent/session.py +0 -169
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Router Module - Layer 2 of the Event Automation Framework.
|
|
3
|
+
|
|
4
|
+
This module provides Action ABC and ActionResult for handlers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .action import (
|
|
8
|
+
Action,
|
|
9
|
+
ActionResult,
|
|
10
|
+
ActionStatus,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Action",
|
|
15
|
+
"ActionResult",
|
|
16
|
+
"ActionStatus",
|
|
17
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Action Abstractions - Layer 2 & 3 of the Event Automation Framework.
|
|
3
|
+
|
|
4
|
+
This module defines the Action ABC and ActionResult for handler return types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
from monoco.core.scheduler import AgentEvent, AgentEventType
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ActionStatus(Enum):
|
|
22
|
+
"""Status of an Action execution."""
|
|
23
|
+
PENDING = "pending"
|
|
24
|
+
RUNNING = "running"
|
|
25
|
+
SUCCESS = "success"
|
|
26
|
+
FAILED = "failed"
|
|
27
|
+
SKIPPED = "skipped"
|
|
28
|
+
CANCELLED = "cancelled"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ActionResult:
|
|
33
|
+
"""
|
|
34
|
+
Result of an Action execution.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
success: Whether the action succeeded
|
|
38
|
+
status: Detailed status
|
|
39
|
+
output: Output data from the action
|
|
40
|
+
error: Error message if failed
|
|
41
|
+
metadata: Additional metadata
|
|
42
|
+
started_at: Execution start time
|
|
43
|
+
completed_at: Execution completion time
|
|
44
|
+
"""
|
|
45
|
+
success: bool
|
|
46
|
+
status: ActionStatus
|
|
47
|
+
output: Any = None
|
|
48
|
+
error: Optional[str] = None
|
|
49
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
50
|
+
started_at: Optional[datetime] = None
|
|
51
|
+
completed_at: Optional[datetime] = None
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def success_result(
|
|
55
|
+
cls,
|
|
56
|
+
output: Any = None,
|
|
57
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
58
|
+
) -> "ActionResult":
|
|
59
|
+
"""Create a success result."""
|
|
60
|
+
return cls(
|
|
61
|
+
success=True,
|
|
62
|
+
status=ActionStatus.SUCCESS,
|
|
63
|
+
output=output,
|
|
64
|
+
metadata=metadata or {},
|
|
65
|
+
completed_at=datetime.now(),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def failure_result(
|
|
70
|
+
cls,
|
|
71
|
+
error: str,
|
|
72
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
73
|
+
) -> "ActionResult":
|
|
74
|
+
"""Create a failure result."""
|
|
75
|
+
return cls(
|
|
76
|
+
success=False,
|
|
77
|
+
status=ActionStatus.FAILED,
|
|
78
|
+
error=error,
|
|
79
|
+
metadata=metadata or {},
|
|
80
|
+
completed_at=datetime.now(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def skipped_result(
|
|
85
|
+
cls,
|
|
86
|
+
reason: str = "",
|
|
87
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
88
|
+
) -> "ActionResult":
|
|
89
|
+
"""Create a skipped result."""
|
|
90
|
+
return cls(
|
|
91
|
+
success=True,
|
|
92
|
+
status=ActionStatus.SKIPPED,
|
|
93
|
+
metadata={"reason": reason, **(metadata or {})},
|
|
94
|
+
completed_at=datetime.now(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Action(ABC):
|
|
99
|
+
"""
|
|
100
|
+
Abstract base class for Actions (Layer 3).
|
|
101
|
+
|
|
102
|
+
Actions are the units of work that respond to events.
|
|
103
|
+
They are executed by handlers when events match their conditions.
|
|
104
|
+
|
|
105
|
+
Responsibilities:
|
|
106
|
+
- Define execution conditions (can_execute)
|
|
107
|
+
- Implement execution logic (execute)
|
|
108
|
+
- Return execution results
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> class MyAction(Action):
|
|
112
|
+
... @property
|
|
113
|
+
... def name(self) -> str:
|
|
114
|
+
... return "MyAction"
|
|
115
|
+
...
|
|
116
|
+
... async def can_execute(self, event: AgentEvent) -> bool:
|
|
117
|
+
... return event.type == AgentEventType.ISSUE_CREATED
|
|
118
|
+
...
|
|
119
|
+
... async def execute(self, event: AgentEvent) -> ActionResult:
|
|
120
|
+
... # Do something
|
|
121
|
+
... return ActionResult.success_result()
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
125
|
+
self.config = config or {}
|
|
126
|
+
self._execution_count = 0
|
|
127
|
+
self._last_execution: Optional[datetime] = None
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
@abstractmethod
|
|
131
|
+
def name(self) -> str:
|
|
132
|
+
"""Return the unique name of this action."""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
async def can_execute(self, event: AgentEvent) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Check if this action should execute for the given event.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
event: The event to check
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if the action should execute, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
@abstractmethod
|
|
149
|
+
async def execute(self, event: AgentEvent) -> ActionResult:
|
|
150
|
+
"""
|
|
151
|
+
Execute the action.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
event: The event that triggered this action
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
ActionResult indicating success/failure
|
|
158
|
+
"""
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
async def __call__(self, event: AgentEvent) -> ActionResult:
|
|
162
|
+
"""
|
|
163
|
+
Make action callable - checks conditions then executes.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
event: The event to process
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
ActionResult
|
|
170
|
+
"""
|
|
171
|
+
self._last_execution = datetime.now()
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
if not await self.can_execute(event):
|
|
175
|
+
return ActionResult.skipped_result(
|
|
176
|
+
reason="Conditions not met",
|
|
177
|
+
metadata={"action": self.name, "event_type": event.type.value},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self._execution_count += 1
|
|
181
|
+
result = await self.execute(event)
|
|
182
|
+
|
|
183
|
+
# Ensure result has timestamps
|
|
184
|
+
if result.started_at is None:
|
|
185
|
+
result.started_at = self._last_execution
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Action {self.name} failed: {e}", exc_info=True)
|
|
191
|
+
return ActionResult.failure_result(
|
|
192
|
+
error=str(e),
|
|
193
|
+
metadata={"action": self.name, "event_type": event.type.value},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
197
|
+
"""Get action statistics."""
|
|
198
|
+
return {
|
|
199
|
+
"name": self.name,
|
|
200
|
+
"execution_count": self._execution_count,
|
|
201
|
+
"last_execution": self._last_execution.isoformat() if self._last_execution else None,
|
|
202
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentScheduler - Core scheduling abstraction layer for Monoco.
|
|
3
|
+
|
|
4
|
+
This module provides the high-level AgentScheduler abstraction that decouples
|
|
5
|
+
scheduling policies from specific Agent Provider implementations.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- AgentScheduler: Abstract base class for all schedulers
|
|
9
|
+
- AgentTask: Data class representing a task to be scheduled
|
|
10
|
+
- AgentStatus: Enum for task lifecycle states
|
|
11
|
+
- EngineAdapter: Abstract base for agent engine adapters
|
|
12
|
+
- EngineFactory: Factory for creating engine adapters
|
|
13
|
+
- EventBus: Central event system for agent scheduling
|
|
14
|
+
- AgentEventType: Event types for agent lifecycle
|
|
15
|
+
|
|
16
|
+
Implementations:
|
|
17
|
+
- LocalProcessScheduler: Local process-based scheduler (default)
|
|
18
|
+
- Future: DockerScheduler, RemoteScheduler, etc.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .base import (
|
|
22
|
+
AgentStatus,
|
|
23
|
+
AgentTask,
|
|
24
|
+
AgentScheduler,
|
|
25
|
+
)
|
|
26
|
+
from .engines import (
|
|
27
|
+
EngineAdapter,
|
|
28
|
+
EngineFactory,
|
|
29
|
+
GeminiAdapter,
|
|
30
|
+
ClaudeAdapter,
|
|
31
|
+
QwenAdapter,
|
|
32
|
+
KimiAdapter,
|
|
33
|
+
)
|
|
34
|
+
from .events import (
|
|
35
|
+
AgentEventType,
|
|
36
|
+
AgentEvent,
|
|
37
|
+
EventBus,
|
|
38
|
+
EventHandler,
|
|
39
|
+
event_bus,
|
|
40
|
+
)
|
|
41
|
+
from .local import LocalProcessScheduler
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# Base abstractions
|
|
45
|
+
"AgentStatus",
|
|
46
|
+
"AgentTask",
|
|
47
|
+
"AgentScheduler",
|
|
48
|
+
# Engine adapters
|
|
49
|
+
"EngineAdapter",
|
|
50
|
+
"EngineFactory",
|
|
51
|
+
"GeminiAdapter",
|
|
52
|
+
"ClaudeAdapter",
|
|
53
|
+
"QwenAdapter",
|
|
54
|
+
"KimiAdapter",
|
|
55
|
+
# Events
|
|
56
|
+
"AgentEventType",
|
|
57
|
+
"AgentEvent",
|
|
58
|
+
"EventBus",
|
|
59
|
+
"EventHandler",
|
|
60
|
+
"event_bus",
|
|
61
|
+
# Implementations
|
|
62
|
+
"LocalProcessScheduler",
|
|
63
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base abstractions for AgentScheduler.
|
|
3
|
+
|
|
4
|
+
Defines the core AgentScheduler ABC, AgentTask dataclass, and AgentStatus enum.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentStatus(Enum):
|
|
15
|
+
"""
|
|
16
|
+
Lifecycle states for an agent task.
|
|
17
|
+
|
|
18
|
+
States:
|
|
19
|
+
PENDING: Task is queued, waiting for resources
|
|
20
|
+
RUNNING: Task is actively executing
|
|
21
|
+
COMPLETED: Task finished successfully
|
|
22
|
+
FAILED: Task failed with an error
|
|
23
|
+
TERMINATED: Task was manually terminated
|
|
24
|
+
TIMEOUT: Task exceeded its time limit
|
|
25
|
+
"""
|
|
26
|
+
PENDING = "pending"
|
|
27
|
+
RUNNING = "running"
|
|
28
|
+
COMPLETED = "completed"
|
|
29
|
+
FAILED = "failed"
|
|
30
|
+
TERMINATED = "terminated"
|
|
31
|
+
TIMEOUT = "timeout"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class AgentTask:
|
|
36
|
+
"""
|
|
37
|
+
Data class representing a task to be scheduled.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
task_id: Unique identifier for the task
|
|
41
|
+
role_name: Name of the agent role (e.g., "Engineer", "Architect")
|
|
42
|
+
issue_id: Associated issue ID
|
|
43
|
+
prompt: The instruction/context to send to the agent
|
|
44
|
+
engine: Agent engine to use (e.g., "gemini", "claude")
|
|
45
|
+
timeout: Maximum execution time in seconds
|
|
46
|
+
metadata: Additional task metadata
|
|
47
|
+
created_at: Task creation timestamp
|
|
48
|
+
"""
|
|
49
|
+
task_id: str
|
|
50
|
+
role_name: str
|
|
51
|
+
issue_id: str
|
|
52
|
+
prompt: str
|
|
53
|
+
engine: str = "gemini"
|
|
54
|
+
timeout: int = 900
|
|
55
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
57
|
+
|
|
58
|
+
def __post_init__(self):
|
|
59
|
+
"""Ensure created_at is set."""
|
|
60
|
+
if self.created_at is None:
|
|
61
|
+
self.created_at = datetime.now()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AgentScheduler(ABC):
|
|
65
|
+
"""
|
|
66
|
+
High-level scheduling abstraction that decouples scheduling policies
|
|
67
|
+
from specific Agent Provider implementations.
|
|
68
|
+
|
|
69
|
+
Responsibilities:
|
|
70
|
+
- Task scheduling and lifecycle management
|
|
71
|
+
- Resource quota control (concurrency limits)
|
|
72
|
+
- Status monitoring and event publishing
|
|
73
|
+
|
|
74
|
+
Implementations:
|
|
75
|
+
- LocalProcessScheduler: Local process mode (current)
|
|
76
|
+
- DockerScheduler: Container mode (future)
|
|
77
|
+
- RemoteScheduler: Remote service mode (future)
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> scheduler = LocalProcessScheduler(max_concurrent=5)
|
|
81
|
+
>>> task = AgentTask(
|
|
82
|
+
... task_id="uuid-123",
|
|
83
|
+
... role_name="Engineer",
|
|
84
|
+
... issue_id="FEAT-123",
|
|
85
|
+
... prompt="Implement feature X",
|
|
86
|
+
... engine="gemini"
|
|
87
|
+
... )
|
|
88
|
+
>>> session_id = await scheduler.schedule(task)
|
|
89
|
+
>>> status = scheduler.get_status(session_id)
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
async def schedule(self, task: AgentTask) -> str:
|
|
94
|
+
"""
|
|
95
|
+
Schedule a task for execution.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
task: The task to schedule
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
session_id: Unique identifier for the scheduled session
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
RuntimeError: If scheduling fails
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
async def terminate(self, session_id: str) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Terminate a running or pending task.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
session_id: The session ID to terminate
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if termination was successful, False otherwise
|
|
118
|
+
"""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def get_status(self, session_id: str) -> Optional[AgentStatus]:
|
|
123
|
+
"""
|
|
124
|
+
Get the current status of a task.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
session_id: The session ID to query
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The current AgentStatus, or None if session not found
|
|
131
|
+
"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def list_active(self) -> Dict[str, AgentStatus]:
|
|
136
|
+
"""
|
|
137
|
+
List all active (pending or running) tasks.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary mapping session_id to AgentStatus
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Get scheduler statistics.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dictionary containing scheduler metrics
|
|
151
|
+
"""
|
|
152
|
+
pass
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Engine Adapters for Monoco Scheduler.
|
|
3
|
+
|
|
4
|
+
This module provides a unified interface for different AI agent execution engines,
|
|
5
|
+
allowing the Worker to seamlessly switch between Gemini, Claude, and future engines.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EngineAdapter(ABC):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for agent engine adapters.
|
|
15
|
+
|
|
16
|
+
Each adapter is responsible for:
|
|
17
|
+
1. Constructing the correct CLI command for its engine
|
|
18
|
+
2. Handling engine-specific error scenarios
|
|
19
|
+
3. Providing metadata about the engine's capabilities
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def build_command(self, prompt: str) -> List[str]:
|
|
24
|
+
"""
|
|
25
|
+
Build the CLI command to execute the agent with the given prompt.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
prompt: The instruction/context to send to the agent
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of command arguments (e.g., ["gemini", "-y", "prompt text"])
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
"""Return the canonical name of this engine."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def supports_yolo_mode(self) -> bool:
|
|
43
|
+
"""Whether this engine supports auto-approval mode."""
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GeminiAdapter(EngineAdapter):
|
|
48
|
+
"""
|
|
49
|
+
Adapter for Google Gemini CLI.
|
|
50
|
+
|
|
51
|
+
Command format: gemini -p <prompt> -y
|
|
52
|
+
The -y flag enables "YOLO mode" (auto-approval of actions).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def build_command(self, prompt: str) -> List[str]:
|
|
56
|
+
# Based on Gemini CLI help: -p <prompt> for non-interactive
|
|
57
|
+
return ["gemini", "-p", prompt, "-y"]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def name(self) -> str:
|
|
61
|
+
return "gemini"
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def supports_yolo_mode(self) -> bool:
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ClaudeAdapter(EngineAdapter):
|
|
69
|
+
"""
|
|
70
|
+
Adapter for Anthropic Claude CLI.
|
|
71
|
+
|
|
72
|
+
Command format: claude -p <prompt>
|
|
73
|
+
The -p/--print flag enables non-interactive mode.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def build_command(self, prompt: str) -> List[str]:
|
|
77
|
+
# Based on Claude CLI help: -p <prompt> is NOT standard, usually -p means print/non-interactive.
|
|
78
|
+
# But for one-shot execution, we do passing prompt as argument with -p flag.
|
|
79
|
+
return ["claude", "-p", prompt]
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def name(self) -> str:
|
|
83
|
+
return "claude"
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def supports_yolo_mode(self) -> bool:
|
|
87
|
+
# Claude uses -p for non-interactive mode, similar concept
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class QwenAdapter(EngineAdapter):
|
|
92
|
+
"""
|
|
93
|
+
Adapter for Qwen Code CLI.
|
|
94
|
+
|
|
95
|
+
Command format: qwen -p <prompt> -y
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def build_command(self, prompt: str) -> List[str]:
|
|
99
|
+
# Assuming Qwen follows similar patterns (based on user feedback)
|
|
100
|
+
return ["qwen", "-p", prompt, "-y"]
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def name(self) -> str:
|
|
104
|
+
return "qwen"
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def supports_yolo_mode(self) -> bool:
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class KimiAdapter(EngineAdapter):
|
|
112
|
+
"""
|
|
113
|
+
Adapter for Kimi CLI (Moonshot AI).
|
|
114
|
+
|
|
115
|
+
Command format: kimi -p <prompt> --print
|
|
116
|
+
Note: --print implicitly adds --yolo.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def build_command(self, prompt: str) -> List[str]:
|
|
120
|
+
# Based on Kimi CLI help: -p, --prompt TEXT.
|
|
121
|
+
# Also using --print for non-interactive mode (which enables yolo).
|
|
122
|
+
return ["kimi", "-p", prompt, "--print"]
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def name(self) -> str:
|
|
126
|
+
return "kimi"
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def supports_yolo_mode(self) -> bool:
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class EngineFactory:
|
|
134
|
+
"""
|
|
135
|
+
Factory for creating engine adapter instances.
|
|
136
|
+
|
|
137
|
+
Usage:
|
|
138
|
+
adapter = EngineFactory.create("gemini")
|
|
139
|
+
command = adapter.build_command("Write a test")
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
_adapters = {
|
|
143
|
+
"gemini": GeminiAdapter,
|
|
144
|
+
"claude": ClaudeAdapter,
|
|
145
|
+
"qwen": QwenAdapter,
|
|
146
|
+
"kimi": KimiAdapter,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def create(cls, engine_name: str) -> EngineAdapter:
|
|
151
|
+
"""
|
|
152
|
+
Create an adapter instance for the specified engine.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
engine_name: Name of the engine (e.g., "gemini", "claude")
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
An instance of the appropriate EngineAdapter
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValueError: If the engine is not supported
|
|
162
|
+
"""
|
|
163
|
+
adapter_class = cls._adapters.get(engine_name.lower())
|
|
164
|
+
if not adapter_class:
|
|
165
|
+
supported = ", ".join(cls._adapters.keys())
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Unsupported engine: '{engine_name}'. "
|
|
168
|
+
f"Supported engines: {supported}"
|
|
169
|
+
)
|
|
170
|
+
return adapter_class()
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def supported_engines(cls) -> List[str]:
|
|
174
|
+
"""Return a list of all supported engine names."""
|
|
175
|
+
return list(cls._adapters.keys())
|