monoco-toolkit 0.3.12__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 +0 -11
- monoco/core/automation/handlers.py +108 -26
- monoco/core/config.py +28 -10
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +1 -39
- monoco/core/router/action.py +3 -142
- monoco/core/scheduler/events.py +28 -2
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +6 -0
- monoco/core/watcher/base.py +18 -1
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/memo.py +40 -48
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/scheduler.py +1 -16
- monoco/daemon/services.py +15 -0
- 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/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 +65 -50
- monoco/features/issue/core.py +199 -99
- 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/main.py +5 -3
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/automation/config.py +0 -338
- monoco/core/execution.py +0 -67
- monoco/core/executor/__init__.py +0 -38
- monoco/core/executor/agent_action.py +0 -254
- monoco/core/executor/git_action.py +0 -303
- monoco/core/executor/im_action.py +0 -309
- monoco/core/executor/pytest_action.py +0 -218
- monoco/core/router/router.py +0 -392
- 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/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.12.dist-info/RECORD +0 -202
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
monoco/core/automation/config.py
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Automation Configuration - YAML/JSON configuration for triggers.
|
|
3
|
-
|
|
4
|
-
Part of the Event Automation Framework.
|
|
5
|
-
Provides configuration schema and loading for automation triggers.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
from dataclasses import dataclass, field as dataclass_field
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any, Dict, List, Optional, Union
|
|
15
|
-
|
|
16
|
-
import yaml
|
|
17
|
-
|
|
18
|
-
from monoco.core.scheduler import AgentEventType
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclass
|
|
24
|
-
class ActionConfig:
|
|
25
|
-
"""Configuration for an action."""
|
|
26
|
-
type: str
|
|
27
|
-
params: Dict[str, Any] = dataclass_field(default_factory=dict)
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def from_dict(cls, data: Dict[str, Any]) -> "ActionConfig":
|
|
31
|
-
"""Create from dict."""
|
|
32
|
-
return cls(
|
|
33
|
-
type=data.get("type", ""),
|
|
34
|
-
params=data.get("params", {}),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@dataclass
|
|
39
|
-
class TriggerConfig:
|
|
40
|
-
"""
|
|
41
|
-
Configuration for a trigger.
|
|
42
|
-
|
|
43
|
-
Attributes:
|
|
44
|
-
name: Unique trigger name
|
|
45
|
-
watcher: Watcher type (IssueWatcher, MemoWatcher, etc.)
|
|
46
|
-
event_type: Event type to listen for
|
|
47
|
-
condition: Optional condition expression
|
|
48
|
-
field: Optional field to watch (for field-level triggers)
|
|
49
|
-
actions: List of actions to execute
|
|
50
|
-
enabled: Whether trigger is enabled
|
|
51
|
-
"""
|
|
52
|
-
name: str
|
|
53
|
-
watcher: str
|
|
54
|
-
event_type: Optional[str] = None
|
|
55
|
-
condition: Optional[str] = None
|
|
56
|
-
field: Optional[str] = None
|
|
57
|
-
actions: List[ActionConfig] = dataclass_field(default_factory=list)
|
|
58
|
-
enabled: bool = True
|
|
59
|
-
priority: int = 0
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def from_dict(cls, data: Dict[str, Any]) -> "TriggerConfig":
|
|
63
|
-
"""Create from dict."""
|
|
64
|
-
actions = [
|
|
65
|
-
ActionConfig.from_dict(a) if isinstance(a, dict) else ActionConfig(type=a)
|
|
66
|
-
for a in data.get("actions", [])
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
return cls(
|
|
70
|
-
name=data.get("name", "unnamed"),
|
|
71
|
-
watcher=data.get("watcher", ""),
|
|
72
|
-
event_type=data.get("event_type"),
|
|
73
|
-
condition=data.get("condition"),
|
|
74
|
-
field=data.get("field"),
|
|
75
|
-
actions=actions,
|
|
76
|
-
enabled=data.get("enabled", True),
|
|
77
|
-
priority=data.get("priority", 0),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
def to_agent_event_type(self) -> Optional[AgentEventType]:
|
|
81
|
-
"""Convert event_type string to AgentEventType."""
|
|
82
|
-
if not self.event_type:
|
|
83
|
-
return None
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
return AgentEventType(self.event_type)
|
|
87
|
-
except ValueError:
|
|
88
|
-
# Try to map common patterns
|
|
89
|
-
mapping = {
|
|
90
|
-
"issue.created": AgentEventType.ISSUE_CREATED,
|
|
91
|
-
"issue.updated": AgentEventType.ISSUE_UPDATED,
|
|
92
|
-
"issue.stage_changed": AgentEventType.ISSUE_STAGE_CHANGED,
|
|
93
|
-
"issue.status_changed": AgentEventType.ISSUE_STATUS_CHANGED,
|
|
94
|
-
"memo.created": AgentEventType.MEMO_CREATED,
|
|
95
|
-
"memo.threshold": AgentEventType.MEMO_THRESHOLD,
|
|
96
|
-
"session.completed": AgentEventType.SESSION_COMPLETED,
|
|
97
|
-
"session.failed": AgentEventType.SESSION_FAILED,
|
|
98
|
-
"pr.created": AgentEventType.PR_CREATED,
|
|
99
|
-
}
|
|
100
|
-
return mapping.get(self.event_type)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@dataclass
|
|
104
|
-
class AutomationConfig:
|
|
105
|
-
"""
|
|
106
|
-
Complete automation configuration.
|
|
107
|
-
|
|
108
|
-
Attributes:
|
|
109
|
-
version: Configuration version
|
|
110
|
-
triggers: List of trigger configurations
|
|
111
|
-
settings: Global settings
|
|
112
|
-
"""
|
|
113
|
-
version: str = "1.0"
|
|
114
|
-
triggers: List[TriggerConfig] = dataclass_field(default_factory=list)
|
|
115
|
-
settings: Dict[str, Any] = dataclass_field(default_factory=dict)
|
|
116
|
-
|
|
117
|
-
@classmethod
|
|
118
|
-
def from_dict(cls, data: Dict[str, Any]) -> "AutomationConfig":
|
|
119
|
-
"""Create from dict."""
|
|
120
|
-
triggers = [
|
|
121
|
-
TriggerConfig.from_dict(t)
|
|
122
|
-
for t in data.get("triggers", [])
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
return cls(
|
|
126
|
-
version=data.get("version", "1.0"),
|
|
127
|
-
triggers=triggers,
|
|
128
|
-
settings=data.get("settings", {}),
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
@classmethod
|
|
132
|
-
def from_yaml(cls, yaml_content: str) -> "AutomationConfig":
|
|
133
|
-
"""Load from YAML string."""
|
|
134
|
-
data = yaml.safe_load(yaml_content)
|
|
135
|
-
return cls.from_dict(data or {})
|
|
136
|
-
|
|
137
|
-
@classmethod
|
|
138
|
-
def from_json(cls, json_content: str) -> "AutomationConfig":
|
|
139
|
-
"""Load from JSON string."""
|
|
140
|
-
data = json.loads(json_content)
|
|
141
|
-
return cls.from_dict(data)
|
|
142
|
-
|
|
143
|
-
def to_yaml(self) -> str:
|
|
144
|
-
"""Export to YAML string."""
|
|
145
|
-
data = {
|
|
146
|
-
"version": self.version,
|
|
147
|
-
"triggers": [
|
|
148
|
-
{
|
|
149
|
-
"name": t.name,
|
|
150
|
-
"watcher": t.watcher,
|
|
151
|
-
"event_type": t.event_type,
|
|
152
|
-
"condition": t.condition,
|
|
153
|
-
"field": t.field,
|
|
154
|
-
"actions": [
|
|
155
|
-
{"type": a.type, "params": a.params}
|
|
156
|
-
for a in t.actions
|
|
157
|
-
],
|
|
158
|
-
"enabled": t.enabled,
|
|
159
|
-
"priority": t.priority,
|
|
160
|
-
}
|
|
161
|
-
for t in self.triggers
|
|
162
|
-
],
|
|
163
|
-
"settings": self.settings,
|
|
164
|
-
}
|
|
165
|
-
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
166
|
-
|
|
167
|
-
def to_json(self) -> str:
|
|
168
|
-
"""Export to JSON string."""
|
|
169
|
-
data = {
|
|
170
|
-
"version": self.version,
|
|
171
|
-
"triggers": [
|
|
172
|
-
{
|
|
173
|
-
"name": t.name,
|
|
174
|
-
"watcher": t.watcher,
|
|
175
|
-
"event_type": t.event_type,
|
|
176
|
-
"condition": t.condition,
|
|
177
|
-
"field": t.field,
|
|
178
|
-
"actions": [
|
|
179
|
-
{"type": a.type, "params": a.params}
|
|
180
|
-
for a in t.actions
|
|
181
|
-
],
|
|
182
|
-
"enabled": t.enabled,
|
|
183
|
-
"priority": t.priority,
|
|
184
|
-
}
|
|
185
|
-
for t in self.triggers
|
|
186
|
-
],
|
|
187
|
-
"settings": self.settings,
|
|
188
|
-
}
|
|
189
|
-
return json.dumps(data, indent=2)
|
|
190
|
-
|
|
191
|
-
def get_enabled_triggers(self) -> List[TriggerConfig]:
|
|
192
|
-
"""Get all enabled triggers."""
|
|
193
|
-
return [t for t in self.triggers if t.enabled]
|
|
194
|
-
|
|
195
|
-
def get_trigger(self, name: str) -> Optional[TriggerConfig]:
|
|
196
|
-
"""Get trigger by name."""
|
|
197
|
-
for trigger in self.triggers:
|
|
198
|
-
if trigger.name == name:
|
|
199
|
-
return trigger
|
|
200
|
-
return None
|
|
201
|
-
|
|
202
|
-
def add_trigger(self, trigger: TriggerConfig) -> None:
|
|
203
|
-
"""Add a trigger."""
|
|
204
|
-
# Remove existing trigger with same name
|
|
205
|
-
self.triggers = [t for t in self.triggers if t.name != trigger.name]
|
|
206
|
-
self.triggers.append(trigger)
|
|
207
|
-
|
|
208
|
-
def remove_trigger(self, name: str) -> bool:
|
|
209
|
-
"""Remove a trigger by name."""
|
|
210
|
-
original_count = len(self.triggers)
|
|
211
|
-
self.triggers = [t for t in self.triggers if t.name != name]
|
|
212
|
-
return len(self.triggers) < original_count
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def load_automation_config(
|
|
216
|
-
path: Union[str, Path],
|
|
217
|
-
create_default: bool = False,
|
|
218
|
-
) -> AutomationConfig:
|
|
219
|
-
"""
|
|
220
|
-
Load automation configuration from file.
|
|
221
|
-
|
|
222
|
-
Supports .yaml, .yml, and .json files.
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
path: Path to configuration file
|
|
226
|
-
create_default: If True and file doesn't exist, create default config
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
AutomationConfig instance
|
|
230
|
-
"""
|
|
231
|
-
path = Path(path)
|
|
232
|
-
|
|
233
|
-
if not path.exists():
|
|
234
|
-
if create_default:
|
|
235
|
-
default_config = create_default_config()
|
|
236
|
-
path.write_text(default_config.to_yaml())
|
|
237
|
-
logger.info(f"Created default automation config at {path}")
|
|
238
|
-
return default_config
|
|
239
|
-
else:
|
|
240
|
-
logger.warning(f"Config file not found: {path}")
|
|
241
|
-
return AutomationConfig()
|
|
242
|
-
|
|
243
|
-
content = path.read_text(encoding="utf-8")
|
|
244
|
-
|
|
245
|
-
if path.suffix in (".yaml", ".yml"):
|
|
246
|
-
return AutomationConfig.from_yaml(content)
|
|
247
|
-
elif path.suffix == ".json":
|
|
248
|
-
return AutomationConfig.from_json(content)
|
|
249
|
-
else:
|
|
250
|
-
# Try YAML first, then JSON
|
|
251
|
-
try:
|
|
252
|
-
return AutomationConfig.from_yaml(content)
|
|
253
|
-
except yaml.YAMLError:
|
|
254
|
-
return AutomationConfig.from_json(content)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def create_default_config() -> AutomationConfig:
|
|
258
|
-
"""Create a default automation configuration."""
|
|
259
|
-
return AutomationConfig(
|
|
260
|
-
version="1.0",
|
|
261
|
-
triggers=[
|
|
262
|
-
TriggerConfig(
|
|
263
|
-
name="memo_threshold",
|
|
264
|
-
watcher="MemoWatcher",
|
|
265
|
-
event_type="memo.threshold",
|
|
266
|
-
condition="pending_count >= 5",
|
|
267
|
-
actions=[
|
|
268
|
-
ActionConfig(
|
|
269
|
-
type="SpawnAgentAction",
|
|
270
|
-
params={"role": "Architect"},
|
|
271
|
-
),
|
|
272
|
-
],
|
|
273
|
-
),
|
|
274
|
-
TriggerConfig(
|
|
275
|
-
name="issue_doing",
|
|
276
|
-
watcher="IssueWatcher",
|
|
277
|
-
event_type="issue.stage_changed",
|
|
278
|
-
field="stage",
|
|
279
|
-
condition="value == 'doing'",
|
|
280
|
-
actions=[
|
|
281
|
-
ActionConfig(
|
|
282
|
-
type="SpawnAgentAction",
|
|
283
|
-
params={"role": "Engineer"},
|
|
284
|
-
),
|
|
285
|
-
],
|
|
286
|
-
),
|
|
287
|
-
TriggerConfig(
|
|
288
|
-
name="issue_completed",
|
|
289
|
-
watcher="IssueWatcher",
|
|
290
|
-
event_type="issue.stage_changed",
|
|
291
|
-
field="stage",
|
|
292
|
-
condition="value == 'done'",
|
|
293
|
-
actions=[
|
|
294
|
-
ActionConfig(
|
|
295
|
-
type="SendIMAction",
|
|
296
|
-
params={
|
|
297
|
-
"channel": "console",
|
|
298
|
-
"message_template": "Issue {issue_id} completed!",
|
|
299
|
-
},
|
|
300
|
-
),
|
|
301
|
-
],
|
|
302
|
-
),
|
|
303
|
-
],
|
|
304
|
-
settings={
|
|
305
|
-
"default_poll_interval": 5.0,
|
|
306
|
-
"max_concurrent_actions": 10,
|
|
307
|
-
"action_timeout": 300,
|
|
308
|
-
},
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def save_automation_config(
|
|
313
|
-
config: AutomationConfig,
|
|
314
|
-
path: Union[str, Path],
|
|
315
|
-
format: str = "yaml",
|
|
316
|
-
) -> None:
|
|
317
|
-
"""
|
|
318
|
-
Save automation configuration to file.
|
|
319
|
-
|
|
320
|
-
Args:
|
|
321
|
-
config: Configuration to save
|
|
322
|
-
path: Path to save to
|
|
323
|
-
format: "yaml" or "json"
|
|
324
|
-
"""
|
|
325
|
-
path = Path(path)
|
|
326
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
327
|
-
|
|
328
|
-
if format == "yaml":
|
|
329
|
-
content = config.to_yaml()
|
|
330
|
-
if path.suffix not in (".yaml", ".yml"):
|
|
331
|
-
path = path.with_suffix(".yaml")
|
|
332
|
-
else:
|
|
333
|
-
content = config.to_json()
|
|
334
|
-
if path.suffix != ".json":
|
|
335
|
-
path = path.with_suffix(".json")
|
|
336
|
-
|
|
337
|
-
path.write_text(content, encoding="utf-8")
|
|
338
|
-
logger.info(f"Saved automation config to {path}")
|
monoco/core/execution.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import List, Optional
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ExecutionProfile(BaseModel):
|
|
7
|
-
name: str
|
|
8
|
-
source: str # "Global" or "Project"
|
|
9
|
-
path: str
|
|
10
|
-
content: Optional[str] = None
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def scan_execution_profiles(
|
|
14
|
-
project_root: Optional[Path] = None,
|
|
15
|
-
) -> List[ExecutionProfile]:
|
|
16
|
-
"""
|
|
17
|
-
Scan for execution profiles (SOPs) in global and project scopes.
|
|
18
|
-
"""
|
|
19
|
-
profiles = []
|
|
20
|
-
|
|
21
|
-
# 1. Global Scope
|
|
22
|
-
global_path = Path.home() / ".monoco" / "execution"
|
|
23
|
-
if global_path.exists():
|
|
24
|
-
profiles.extend(_scan_dir(global_path, "Global"))
|
|
25
|
-
|
|
26
|
-
# 2. Project Scope
|
|
27
|
-
if project_root:
|
|
28
|
-
project_path = project_root / ".monoco" / "execution"
|
|
29
|
-
if project_path.exists():
|
|
30
|
-
profiles.extend(_scan_dir(project_path, "Project"))
|
|
31
|
-
|
|
32
|
-
return profiles
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _scan_dir(base_path: Path, source: str) -> List[ExecutionProfile]:
|
|
36
|
-
profiles = []
|
|
37
|
-
if not base_path.is_dir():
|
|
38
|
-
return profiles
|
|
39
|
-
|
|
40
|
-
for item in base_path.iterdir():
|
|
41
|
-
if item.is_dir():
|
|
42
|
-
sop_path = item / "SOP.md"
|
|
43
|
-
if sop_path.exists():
|
|
44
|
-
profiles.append(
|
|
45
|
-
ExecutionProfile(
|
|
46
|
-
name=item.name, source=source, path=str(sop_path.absolute())
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
return profiles
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def get_profile_detail(profile_path: str) -> Optional[ExecutionProfile]:
|
|
53
|
-
path = Path(profile_path)
|
|
54
|
-
if not path.exists():
|
|
55
|
-
return None
|
|
56
|
-
|
|
57
|
-
# Determine source (rough heuristic)
|
|
58
|
-
source = "Project"
|
|
59
|
-
if str(path).startswith(str(Path.home() / ".monoco")):
|
|
60
|
-
source = "Global"
|
|
61
|
-
|
|
62
|
-
return ExecutionProfile(
|
|
63
|
-
name=path.parent.name,
|
|
64
|
-
source=source,
|
|
65
|
-
path=str(path.absolute()),
|
|
66
|
-
content=path.read_text(encoding="utf-8"),
|
|
67
|
-
)
|
monoco/core/executor/__init__.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Executor Module - Layer 3 of the Event Automation Framework.
|
|
3
|
-
|
|
4
|
-
This module provides concrete Action implementations for various execution types.
|
|
5
|
-
It is part of the three-layer architecture:
|
|
6
|
-
- Layer 1: File Watcher
|
|
7
|
-
- Layer 2: Action Router
|
|
8
|
-
- Layer 3: Action Executor (this module)
|
|
9
|
-
|
|
10
|
-
Available Actions:
|
|
11
|
-
- SpawnAgentAction: Spawn agent sessions
|
|
12
|
-
- RunPytestAction: Execute pytest tests
|
|
13
|
-
- GitCommitAction: Perform git commits
|
|
14
|
-
- GitPushAction: Push to remote
|
|
15
|
-
- SendIMAction: Send notifications
|
|
16
|
-
|
|
17
|
-
Example Usage:
|
|
18
|
-
>>> from monoco.core.executor import SpawnAgentAction
|
|
19
|
-
>>> from monoco.core.scheduler import AgentEventType
|
|
20
|
-
>>> from monoco.core.router import ActionRouter
|
|
21
|
-
>>>
|
|
22
|
-
>>> action = SpawnAgentAction(role="Engineer")
|
|
23
|
-
>>> router = ActionRouter()
|
|
24
|
-
>>> router.register(AgentEventType.ISSUE_STAGE_CHANGED, action)
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
from .agent_action import SpawnAgentAction
|
|
28
|
-
from .pytest_action import RunPytestAction
|
|
29
|
-
from .git_action import GitCommitAction, GitPushAction
|
|
30
|
-
from .im_action import SendIMAction
|
|
31
|
-
|
|
32
|
-
__all__ = [
|
|
33
|
-
"SpawnAgentAction",
|
|
34
|
-
"RunPytestAction",
|
|
35
|
-
"GitCommitAction",
|
|
36
|
-
"GitPushAction",
|
|
37
|
-
"SendIMAction",
|
|
38
|
-
]
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
SpawnAgentAction - Action for spawning agent sessions.
|
|
3
|
-
|
|
4
|
-
Part of Layer 3 (Action Executor) in the event automation framework.
|
|
5
|
-
Uses AgentScheduler to spawn and manage agent sessions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
from typing import Any, Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
from monoco.core.scheduler import AgentEvent, AgentEventType, AgentScheduler, AgentTask
|
|
14
|
-
from monoco.core.router import Action, ActionResult
|
|
15
|
-
from monoco.features.agent.models import RoleTemplate
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class SpawnAgentAction(Action):
|
|
21
|
-
"""
|
|
22
|
-
Action that spawns an agent session.
|
|
23
|
-
|
|
24
|
-
This action creates and starts a new agent session based on the event.
|
|
25
|
-
It supports different roles (Architect, Engineer, Reviewer, etc.)
|
|
26
|
-
and integrates with the AgentScheduler for lifecycle management.
|
|
27
|
-
|
|
28
|
-
Example:
|
|
29
|
-
>>> action = SpawnAgentAction(
|
|
30
|
-
... role="Engineer",
|
|
31
|
-
... scheduler=scheduler,
|
|
32
|
-
... )
|
|
33
|
-
>>> result = await action(event)
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
ROLE_TEMPLATES = {
|
|
37
|
-
"Architect": {
|
|
38
|
-
"description": "System Architect",
|
|
39
|
-
"trigger": "memo.accumulation",
|
|
40
|
-
"goal": "Process memo inbox and create issues.",
|
|
41
|
-
"system_prompt": "You are the Architect. Process the Memo inbox.",
|
|
42
|
-
"engine": "gemini",
|
|
43
|
-
},
|
|
44
|
-
"Engineer": {
|
|
45
|
-
"description": "Software Engineer",
|
|
46
|
-
"trigger": "issue.stage_changed",
|
|
47
|
-
"goal": "Implement feature requirements.",
|
|
48
|
-
"system_prompt": "You are a Software Engineer. Read the issue and implement requirements.",
|
|
49
|
-
"engine": "gemini",
|
|
50
|
-
},
|
|
51
|
-
"Reviewer": {
|
|
52
|
-
"description": "Code Reviewer",
|
|
53
|
-
"trigger": "pr.created",
|
|
54
|
-
"goal": "Review code changes thoroughly.",
|
|
55
|
-
"system_prompt": "You are a Code Reviewer. Review the code changes thoroughly.",
|
|
56
|
-
"engine": "gemini",
|
|
57
|
-
},
|
|
58
|
-
"Coroner": {
|
|
59
|
-
"description": "Session Autopsy",
|
|
60
|
-
"trigger": "session.failed",
|
|
61
|
-
"goal": "Analyze failed session and create incident report.",
|
|
62
|
-
"system_prompt": "You are the Coroner. Analyze the failed session.",
|
|
63
|
-
"engine": "gemini",
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
def __init__(
|
|
68
|
-
self,
|
|
69
|
-
role: str,
|
|
70
|
-
scheduler: AgentScheduler,
|
|
71
|
-
config: Optional[Dict[str, Any]] = None,
|
|
72
|
-
custom_role_template: Optional[Dict[str, str]] = None,
|
|
73
|
-
):
|
|
74
|
-
super().__init__(config)
|
|
75
|
-
self.role = role
|
|
76
|
-
self.scheduler = scheduler
|
|
77
|
-
self.custom_role_template = custom_role_template
|
|
78
|
-
self._spawned_sessions: List[str] = []
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def name(self) -> str:
|
|
82
|
-
return f"SpawnAgentAction({self.role})"
|
|
83
|
-
|
|
84
|
-
async def can_execute(self, event: AgentEvent) -> bool:
|
|
85
|
-
"""
|
|
86
|
-
Check if we should spawn an agent.
|
|
87
|
-
|
|
88
|
-
Conditions:
|
|
89
|
-
- Scheduler has capacity for new tasks
|
|
90
|
-
"""
|
|
91
|
-
# Check scheduler capacity
|
|
92
|
-
stats = self.scheduler.get_stats()
|
|
93
|
-
active_tasks = stats.get("active_tasks", 0)
|
|
94
|
-
max_concurrent = stats.get("max_concurrent", 5)
|
|
95
|
-
|
|
96
|
-
if active_tasks >= max_concurrent:
|
|
97
|
-
logger.warning(f"Scheduler at capacity ({active_tasks}/{max_concurrent}), skipping")
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
|
-
return True
|
|
101
|
-
|
|
102
|
-
async def execute(self, event: AgentEvent) -> ActionResult:
|
|
103
|
-
"""Spawn an agent session."""
|
|
104
|
-
issue_id = event.payload.get("issue_id", "unknown")
|
|
105
|
-
issue_title = event.payload.get("issue_title", "Unknown")
|
|
106
|
-
|
|
107
|
-
logger.info(f"Spawning {self.role} agent for {issue_id}")
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
# Create AgentTask
|
|
111
|
-
task = AgentTask(
|
|
112
|
-
task_id=f"{self.role.lower()}-{issue_id}-{event.timestamp.timestamp()}",
|
|
113
|
-
role_name=self.role,
|
|
114
|
-
issue_id=issue_id,
|
|
115
|
-
prompt=self._build_prompt(issue_id, issue_title),
|
|
116
|
-
engine=self._get_engine(),
|
|
117
|
-
timeout=self.config.get("timeout", 1800),
|
|
118
|
-
metadata={
|
|
119
|
-
"trigger": event.type.value,
|
|
120
|
-
"issue_title": issue_title,
|
|
121
|
-
},
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
# Schedule task
|
|
125
|
-
session_id = await self.scheduler.schedule(task)
|
|
126
|
-
|
|
127
|
-
# Track spawned session
|
|
128
|
-
self._spawned_sessions.append(session_id)
|
|
129
|
-
|
|
130
|
-
logger.info(f"{self.role} scheduled: session={session_id}")
|
|
131
|
-
|
|
132
|
-
return ActionResult.success_result(
|
|
133
|
-
output={
|
|
134
|
-
"session_id": session_id,
|
|
135
|
-
"issue_id": issue_id,
|
|
136
|
-
"role": self.role,
|
|
137
|
-
},
|
|
138
|
-
metadata={
|
|
139
|
-
"task_id": task.task_id,
|
|
140
|
-
},
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
except Exception as e:
|
|
144
|
-
logger.error(f"Failed to spawn {self.role} for {issue_id}: {e}")
|
|
145
|
-
return ActionResult.failure_result(
|
|
146
|
-
error=str(e),
|
|
147
|
-
metadata={
|
|
148
|
-
"issue_id": issue_id,
|
|
149
|
-
"role": self.role,
|
|
150
|
-
},
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
def _build_prompt(self, issue_id: str, issue_title: str) -> str:
|
|
154
|
-
"""Build the prompt for the agent."""
|
|
155
|
-
template = self.ROLE_TEMPLATES.get(self.role, self.ROLE_TEMPLATES["Engineer"])
|
|
156
|
-
|
|
157
|
-
return f"""You are a {template['description']}.
|
|
158
|
-
|
|
159
|
-
Issue: {issue_id} - {issue_title}
|
|
160
|
-
|
|
161
|
-
Goal: {template['goal']}
|
|
162
|
-
|
|
163
|
-
{template['system_prompt']}
|
|
164
|
-
"""
|
|
165
|
-
|
|
166
|
-
def _get_engine(self) -> str:
|
|
167
|
-
"""Get the engine for this role."""
|
|
168
|
-
if self.custom_role_template:
|
|
169
|
-
return self.custom_role_template.get("engine", "gemini")
|
|
170
|
-
template = self.ROLE_TEMPLATES.get(self.role, self.ROLE_TEMPLATES["Engineer"])
|
|
171
|
-
return template["engine"]
|
|
172
|
-
|
|
173
|
-
def get_spawned_sessions(self) -> List[str]:
|
|
174
|
-
"""Get list of session IDs spawned by this action."""
|
|
175
|
-
return self._spawned_sessions.copy()
|
|
176
|
-
|
|
177
|
-
def get_stats(self) -> Dict[str, Any]:
|
|
178
|
-
"""Get action statistics."""
|
|
179
|
-
stats = super().get_stats()
|
|
180
|
-
stats.update({
|
|
181
|
-
"role": self.role,
|
|
182
|
-
"spawned_sessions": len(self._spawned_sessions),
|
|
183
|
-
})
|
|
184
|
-
return stats
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class SpawnArchitectAction(SpawnAgentAction):
|
|
188
|
-
"""Convenience action for spawning Architect agents."""
|
|
189
|
-
|
|
190
|
-
def __init__(
|
|
191
|
-
self,
|
|
192
|
-
scheduler: AgentScheduler,
|
|
193
|
-
config: Optional[Dict[str, Any]] = None,
|
|
194
|
-
):
|
|
195
|
-
super().__init__(
|
|
196
|
-
role="Architect",
|
|
197
|
-
scheduler=scheduler,
|
|
198
|
-
config=config,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
async def can_execute(self, event: AgentEvent) -> bool:
|
|
202
|
-
"""Only execute for MEMO_THRESHOLD events."""
|
|
203
|
-
if event.type != AgentEventType.MEMO_THRESHOLD:
|
|
204
|
-
return False
|
|
205
|
-
return await super().can_execute(event)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
class SpawnEngineerAction(SpawnAgentAction):
|
|
209
|
-
"""Convenience action for spawning Engineer agents."""
|
|
210
|
-
|
|
211
|
-
def __init__(
|
|
212
|
-
self,
|
|
213
|
-
scheduler: AgentScheduler,
|
|
214
|
-
config: Optional[Dict[str, Any]] = None,
|
|
215
|
-
):
|
|
216
|
-
super().__init__(
|
|
217
|
-
role="Engineer",
|
|
218
|
-
scheduler=scheduler,
|
|
219
|
-
config=config,
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
async def can_execute(self, event: AgentEvent) -> bool:
|
|
223
|
-
"""Only execute for ISSUE_STAGE_CHANGED to 'doing' events."""
|
|
224
|
-
if event.type != AgentEventType.ISSUE_STAGE_CHANGED:
|
|
225
|
-
return False
|
|
226
|
-
|
|
227
|
-
new_stage = event.payload.get("new_stage")
|
|
228
|
-
issue_status = event.payload.get("issue_status")
|
|
229
|
-
|
|
230
|
-
if new_stage != "doing" or issue_status != "open":
|
|
231
|
-
return False
|
|
232
|
-
|
|
233
|
-
return await super().can_execute(event)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
class SpawnReviewerAction(SpawnAgentAction):
|
|
237
|
-
"""Convenience action for spawning Reviewer agents."""
|
|
238
|
-
|
|
239
|
-
def __init__(
|
|
240
|
-
self,
|
|
241
|
-
scheduler: AgentScheduler,
|
|
242
|
-
config: Optional[Dict[str, Any]] = None,
|
|
243
|
-
):
|
|
244
|
-
super().__init__(
|
|
245
|
-
role="Reviewer",
|
|
246
|
-
scheduler=scheduler,
|
|
247
|
-
config=config,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
async def can_execute(self, event: AgentEvent) -> bool:
|
|
251
|
-
"""Only execute for PR_CREATED events."""
|
|
252
|
-
if event.type != AgentEventType.PR_CREATED:
|
|
253
|
-
return False
|
|
254
|
-
return await super().can_execute(event)
|