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/injection.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Dict
|
|
3
|
+
from typing import Dict, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class PromptInjector:
|
|
@@ -13,8 +13,52 @@ class PromptInjector:
|
|
|
13
13
|
MANAGED_START = "<!-- MONOCO_GENERATED_START -->"
|
|
14
14
|
MANAGED_END = "<!-- MONOCO_GENERATED_END -->"
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
FILE_HEADER_COMMENT = """<!--
|
|
17
|
+
⚠️ IMPORTANT: This file is partially managed by Monoco.
|
|
18
|
+
- Content between MONOCO_GENERATED_START and MONOCO_GENERATED_END is auto-generated.
|
|
19
|
+
- Do NOT manually edit the managed block.
|
|
20
|
+
- Do NOT add content after MONOCO_GENERATED_END (use separate files instead).
|
|
21
|
+
-->
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, target_file: Path, verbose: bool = True):
|
|
17
26
|
self.target_file = target_file
|
|
27
|
+
self.verbose = verbose
|
|
28
|
+
|
|
29
|
+
def _detect_external_content(self, content: str) -> Optional[str]:
|
|
30
|
+
"""
|
|
31
|
+
Detects content outside the managed block.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
The external content string if found, None otherwise.
|
|
35
|
+
"""
|
|
36
|
+
if not content or self.MANAGED_END not in content:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Split by MANAGED_END and check if there's non-empty content after
|
|
40
|
+
parts = content.split(self.MANAGED_END)
|
|
41
|
+
if len(parts) < 2:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
post_content = parts[-1].strip()
|
|
45
|
+
# Check if there's meaningful content (not just whitespace or newlines)
|
|
46
|
+
if post_content and len(post_content) > 10: # Threshold to avoid false positives
|
|
47
|
+
return post_content
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _warn_external_content(self, external_content: str):
|
|
51
|
+
"""Outputs warning about external content."""
|
|
52
|
+
if not self.verbose:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# Truncate long content for warning message
|
|
56
|
+
preview = external_content[:200].replace("\n", " ")
|
|
57
|
+
if len(external_content) > 200:
|
|
58
|
+
preview += "..."
|
|
59
|
+
|
|
60
|
+
print(f"⚠️ Warning: Manual content detected after Managed Block in {self.target_file}")
|
|
61
|
+
print(f" Consider moving to a separate file. Found content starting with: {preview}")
|
|
18
62
|
|
|
19
63
|
def inject(self, prompts: Dict[str, str]) -> bool:
|
|
20
64
|
"""
|
|
@@ -30,6 +74,11 @@ class PromptInjector:
|
|
|
30
74
|
if self.target_file.exists():
|
|
31
75
|
current_content = self.target_file.read_text(encoding="utf-8")
|
|
32
76
|
|
|
77
|
+
# Check for external content and warn
|
|
78
|
+
external_content = self._detect_external_content(current_content)
|
|
79
|
+
if external_content:
|
|
80
|
+
self._warn_external_content(external_content)
|
|
81
|
+
|
|
33
82
|
new_content = self._merge_content(current_content, prompts)
|
|
34
83
|
|
|
35
84
|
if new_content != current_content:
|
|
@@ -76,12 +125,18 @@ class PromptInjector:
|
|
|
76
125
|
managed_block_str = "\n".join(managed_block).strip() + "\n"
|
|
77
126
|
managed_block_str = f"{self.MANAGED_START}\n{managed_block_str}\n{self.MANAGED_END}\n"
|
|
78
127
|
|
|
128
|
+
# 2. Add file header comment if not present
|
|
129
|
+
has_header = original.strip().startswith("<!--") and "IMPORTANT: This file is partially managed by Monoco" in original
|
|
130
|
+
|
|
79
131
|
# 2. Find and replace/append in the original content
|
|
80
132
|
# Check for delimiters first
|
|
81
133
|
if self.MANAGED_START in original and self.MANAGED_END in original:
|
|
82
134
|
try:
|
|
83
135
|
pre = original.split(self.MANAGED_START)[0]
|
|
84
136
|
post = original.split(self.MANAGED_END)[1]
|
|
137
|
+
# Add header comment if not present
|
|
138
|
+
if not has_header and not pre.strip().startswith("<!--"):
|
|
139
|
+
pre = self.FILE_HEADER_COMMENT + pre
|
|
85
140
|
# Reconstruct
|
|
86
141
|
return pre + managed_block_str.strip() + post
|
|
87
142
|
except IndexError:
|
|
@@ -105,12 +160,16 @@ class PromptInjector:
|
|
|
105
160
|
|
|
106
161
|
if start_idx == -1:
|
|
107
162
|
# Block not found, append to end
|
|
163
|
+
result = ""
|
|
164
|
+
if not has_header:
|
|
165
|
+
result = self.FILE_HEADER_COMMENT
|
|
108
166
|
if original and not original.endswith("\n"):
|
|
109
|
-
|
|
167
|
+
result += original + "\n\n" + managed_block_str.strip()
|
|
110
168
|
elif original:
|
|
111
|
-
|
|
169
|
+
result += original + "\n" + managed_block_str.strip()
|
|
112
170
|
else:
|
|
113
|
-
|
|
171
|
+
result += managed_block_str.strip() + "\n"
|
|
172
|
+
return result
|
|
114
173
|
|
|
115
174
|
# Find end: Look for next header of level 1 or 2 (siblings or parents)
|
|
116
175
|
header_level_match = re.match(r"^(#+)\s", self.MANAGED_HEADER)
|
|
@@ -134,9 +193,13 @@ class PromptInjector:
|
|
|
134
193
|
pre_block = "\n".join(lines[:start_idx])
|
|
135
194
|
post_block = "\n".join(lines[end_idx:])
|
|
136
195
|
|
|
137
|
-
result =
|
|
138
|
-
if
|
|
139
|
-
|
|
196
|
+
result = ""
|
|
197
|
+
# Add header comment if not present
|
|
198
|
+
if not has_header and not pre_block.strip().startswith("<!--"):
|
|
199
|
+
result = self.FILE_HEADER_COMMENT
|
|
200
|
+
|
|
201
|
+
if pre_block:
|
|
202
|
+
result += pre_block + "\n\n"
|
|
140
203
|
|
|
141
204
|
result += managed_block_str
|
|
142
205
|
|
|
@@ -218,6 +281,21 @@ class PromptInjector:
|
|
|
218
281
|
pre_block = lines[:start_idx]
|
|
219
282
|
post_block = lines[end_idx:]
|
|
220
283
|
|
|
284
|
+
# Check if pre_block contains only the file header comment
|
|
285
|
+
pre_text = "\n".join(pre_block)
|
|
286
|
+
if pre_text.strip() and "This file is partially managed by Monoco" in pre_text:
|
|
287
|
+
# Check if pre_block is just the header comment
|
|
288
|
+
is_only_header = all(
|
|
289
|
+
line.strip().startswith("<!--") or
|
|
290
|
+
line.strip().startswith("⚠️ IMPORTANT") or
|
|
291
|
+
line.strip().startswith("-") or
|
|
292
|
+
line.strip().startswith("-->") or
|
|
293
|
+
not line.strip()
|
|
294
|
+
for line in pre_block
|
|
295
|
+
)
|
|
296
|
+
if is_only_header and not post_block:
|
|
297
|
+
pre_block = []
|
|
298
|
+
|
|
221
299
|
# If we removed everything, the file might become empty or just newlines
|
|
222
300
|
|
|
223
301
|
new_lines = pre_block + post_block
|
monoco/core/integrations.py
CHANGED
|
@@ -94,14 +94,6 @@ class AgentProviderHealth(BaseModel):
|
|
|
94
94
|
|
|
95
95
|
# Default Integration Registry
|
|
96
96
|
DEFAULT_INTEGRATIONS: Dict[str, AgentIntegration] = {
|
|
97
|
-
"cursor": AgentIntegration(
|
|
98
|
-
key="cursor",
|
|
99
|
-
name="Cursor",
|
|
100
|
-
system_prompt_file=".cursorrules",
|
|
101
|
-
skill_root_dir=".cursor/skills/",
|
|
102
|
-
bin_name="cursor",
|
|
103
|
-
version_cmd="--version",
|
|
104
|
-
),
|
|
105
97
|
"claude": AgentIntegration(
|
|
106
98
|
key="claude",
|
|
107
99
|
name="Claude Code",
|
|
@@ -118,22 +110,6 @@ DEFAULT_INTEGRATIONS: Dict[str, AgentIntegration] = {
|
|
|
118
110
|
bin_name="gemini",
|
|
119
111
|
version_cmd="--version",
|
|
120
112
|
),
|
|
121
|
-
"qwen": AgentIntegration(
|
|
122
|
-
key="qwen",
|
|
123
|
-
name="Qwen Code",
|
|
124
|
-
system_prompt_file="QWEN.md",
|
|
125
|
-
skill_root_dir=".qwen/skills/",
|
|
126
|
-
bin_name="qwen",
|
|
127
|
-
version_cmd="--version",
|
|
128
|
-
),
|
|
129
|
-
"kimi": AgentIntegration(
|
|
130
|
-
key="kimi",
|
|
131
|
-
name="Kimi CLI",
|
|
132
|
-
system_prompt_file="AGENTS.md",
|
|
133
|
-
skill_root_dir=".agent/skills/",
|
|
134
|
-
bin_name="kimi",
|
|
135
|
-
version_cmd="--version",
|
|
136
|
-
),
|
|
137
113
|
"agent": AgentIntegration(
|
|
138
114
|
key="agent",
|
|
139
115
|
name="Generic Agent",
|
monoco/core/router/__init__.py
CHANGED
|
@@ -1,55 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Router Module - Layer 2 of the Event Automation Framework.
|
|
3
3
|
|
|
4
|
-
This module provides
|
|
5
|
-
It is part of the three-layer architecture:
|
|
6
|
-
- Layer 1: File Watcher
|
|
7
|
-
- Layer 2: Action Router (this module)
|
|
8
|
-
- Layer 3: Action Executor
|
|
9
|
-
|
|
10
|
-
Example Usage:
|
|
11
|
-
>>> from monoco.core.router import ActionRouter, Action, ActionResult
|
|
12
|
-
>>> from monoco.core.scheduler import AgentEventType
|
|
13
|
-
>>>
|
|
14
|
-
>>> router = ActionRouter()
|
|
15
|
-
>>>
|
|
16
|
-
>>> # Register action for issue creation
|
|
17
|
-
>>> router.register(AgentEventType.ISSUE_CREATED, my_action)
|
|
18
|
-
>>>
|
|
19
|
-
>>> # Register with condition
|
|
20
|
-
>>> router.register(
|
|
21
|
-
... AgentEventType.ISSUE_STAGE_CHANGED,
|
|
22
|
-
... engineer_action,
|
|
23
|
-
... condition=lambda e: e.payload.get("new_stage") == "doing"
|
|
24
|
-
... )
|
|
25
|
-
>>>
|
|
26
|
-
>>> await router.start()
|
|
4
|
+
This module provides Action ABC and ActionResult for handlers.
|
|
27
5
|
"""
|
|
28
6
|
|
|
29
7
|
from .action import (
|
|
30
8
|
Action,
|
|
31
|
-
ActionChain,
|
|
32
|
-
ActionRegistry,
|
|
33
9
|
ActionResult,
|
|
34
10
|
ActionStatus,
|
|
35
|
-
ConditionalAction,
|
|
36
|
-
)
|
|
37
|
-
from .router import (
|
|
38
|
-
ActionRouter,
|
|
39
|
-
ConditionalRouter,
|
|
40
|
-
RoutingRule,
|
|
41
11
|
)
|
|
42
12
|
|
|
43
13
|
__all__ = [
|
|
44
|
-
# Actions
|
|
45
14
|
"Action",
|
|
46
|
-
"ActionChain",
|
|
47
|
-
"ActionRegistry",
|
|
48
15
|
"ActionResult",
|
|
49
16
|
"ActionStatus",
|
|
50
|
-
"ConditionalAction",
|
|
51
|
-
# Router
|
|
52
|
-
"ActionRouter",
|
|
53
|
-
"ConditionalRouter",
|
|
54
|
-
"RoutingRule",
|
|
55
17
|
]
|
monoco/core/router/action.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Action Abstractions - Layer 2 & 3 of the Event Automation Framework.
|
|
3
3
|
|
|
4
|
-
This module defines the Action ABC and
|
|
5
|
-
Actions are the units of work that respond to events.
|
|
4
|
+
This module defines the Action ABC and ActionResult for handler return types.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -12,7 +11,7 @@ from abc import ABC, abstractmethod
|
|
|
12
11
|
from dataclasses import dataclass, field
|
|
13
12
|
from datetime import datetime
|
|
14
13
|
from enum import Enum
|
|
15
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
16
15
|
|
|
17
16
|
from monoco.core.scheduler import AgentEvent, AgentEventType
|
|
18
17
|
|
|
@@ -101,7 +100,7 @@ class Action(ABC):
|
|
|
101
100
|
Abstract base class for Actions (Layer 3).
|
|
102
101
|
|
|
103
102
|
Actions are the units of work that respond to events.
|
|
104
|
-
They are executed by
|
|
103
|
+
They are executed by handlers when events match their conditions.
|
|
105
104
|
|
|
106
105
|
Responsibilities:
|
|
107
106
|
- Define execution conditions (can_execute)
|
|
@@ -201,141 +200,3 @@ class Action(ABC):
|
|
|
201
200
|
"execution_count": self._execution_count,
|
|
202
201
|
"last_execution": self._last_execution.isoformat() if self._last_execution else None,
|
|
203
202
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class ConditionalAction(Action):
|
|
207
|
-
"""
|
|
208
|
-
Action with configurable conditions.
|
|
209
|
-
|
|
210
|
-
Allows defining conditions declaratively without subclassing.
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
def __init__(
|
|
214
|
-
self,
|
|
215
|
-
name: str,
|
|
216
|
-
execute_fn: Callable[[AgentEvent], Any],
|
|
217
|
-
condition_fn: Optional[Callable[[AgentEvent], bool]] = None,
|
|
218
|
-
config: Optional[Dict[str, Any]] = None,
|
|
219
|
-
):
|
|
220
|
-
super().__init__(config)
|
|
221
|
-
self._name = name
|
|
222
|
-
self._execute_fn = execute_fn
|
|
223
|
-
self._condition_fn = condition_fn
|
|
224
|
-
|
|
225
|
-
@property
|
|
226
|
-
def name(self) -> str:
|
|
227
|
-
return self._name
|
|
228
|
-
|
|
229
|
-
async def can_execute(self, event: AgentEvent) -> bool:
|
|
230
|
-
if self._condition_fn is None:
|
|
231
|
-
return True
|
|
232
|
-
|
|
233
|
-
if hasattr(self._condition_fn, '__await__'):
|
|
234
|
-
return await self._condition_fn(event)
|
|
235
|
-
return self._condition_fn(event)
|
|
236
|
-
|
|
237
|
-
async def execute(self, event: AgentEvent) -> ActionResult:
|
|
238
|
-
try:
|
|
239
|
-
if hasattr(self._execute_fn, '__await__'):
|
|
240
|
-
result = await self._execute_fn(event)
|
|
241
|
-
else:
|
|
242
|
-
result = self._execute_fn(event)
|
|
243
|
-
|
|
244
|
-
if isinstance(result, ActionResult):
|
|
245
|
-
return result
|
|
246
|
-
return ActionResult.success_result(output=result)
|
|
247
|
-
|
|
248
|
-
except Exception as e:
|
|
249
|
-
return ActionResult.failure_result(error=str(e))
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
class ActionChain:
|
|
253
|
-
"""
|
|
254
|
-
Chain of actions that execute sequentially.
|
|
255
|
-
|
|
256
|
-
Each action in the chain receives the output of the previous action.
|
|
257
|
-
If any action fails, the chain stops.
|
|
258
|
-
"""
|
|
259
|
-
|
|
260
|
-
def __init__(self, name: str, actions: Optional[List[Action]] = None):
|
|
261
|
-
self.name = name
|
|
262
|
-
self.actions = actions or []
|
|
263
|
-
self._results: List[ActionResult] = []
|
|
264
|
-
|
|
265
|
-
def add(self, action: Action) -> "ActionChain":
|
|
266
|
-
"""Add an action to the chain."""
|
|
267
|
-
self.actions.append(action)
|
|
268
|
-
return self
|
|
269
|
-
|
|
270
|
-
async def execute(self, event: AgentEvent) -> List[ActionResult]:
|
|
271
|
-
"""
|
|
272
|
-
Execute all actions in the chain.
|
|
273
|
-
|
|
274
|
-
Args:
|
|
275
|
-
event: The triggering event
|
|
276
|
-
|
|
277
|
-
Returns:
|
|
278
|
-
List of ActionResults for each action
|
|
279
|
-
"""
|
|
280
|
-
self._results = []
|
|
281
|
-
context = {"event": event, "chain_name": self.name}
|
|
282
|
-
|
|
283
|
-
for action in self.actions:
|
|
284
|
-
# Check if previous action failed
|
|
285
|
-
if self._results and not self._results[-1].success:
|
|
286
|
-
self._results.append(ActionResult.skipped_result(
|
|
287
|
-
reason="Previous action in chain failed"
|
|
288
|
-
))
|
|
289
|
-
continue
|
|
290
|
-
|
|
291
|
-
# Execute action with context
|
|
292
|
-
result = await action(event)
|
|
293
|
-
result.metadata["chain_context"] = context.copy()
|
|
294
|
-
self._results.append(result)
|
|
295
|
-
|
|
296
|
-
# Update context with output
|
|
297
|
-
if result.success and result.output is not None:
|
|
298
|
-
context[f"{action.name}_output"] = result.output
|
|
299
|
-
|
|
300
|
-
return self._results
|
|
301
|
-
|
|
302
|
-
def get_stats(self) -> Dict[str, Any]:
|
|
303
|
-
"""Get chain statistics."""
|
|
304
|
-
return {
|
|
305
|
-
"name": self.name,
|
|
306
|
-
"actions": len(self.actions),
|
|
307
|
-
"action_names": [a.name for a in self.actions],
|
|
308
|
-
"last_results": len(self._results),
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class ActionRegistry:
|
|
313
|
-
"""
|
|
314
|
-
Registry for managing actions.
|
|
315
|
-
|
|
316
|
-
Provides action lookup and management.
|
|
317
|
-
"""
|
|
318
|
-
|
|
319
|
-
def __init__(self):
|
|
320
|
-
self._actions: Dict[str, Action] = {}
|
|
321
|
-
|
|
322
|
-
def register(self, action: Action) -> None:
|
|
323
|
-
"""Register an action."""
|
|
324
|
-
self._actions[action.name] = action
|
|
325
|
-
logger.debug(f"Registered action: {action.name}")
|
|
326
|
-
|
|
327
|
-
def unregister(self, name: str) -> Optional[Action]:
|
|
328
|
-
"""Unregister an action."""
|
|
329
|
-
return self._actions.pop(name, None)
|
|
330
|
-
|
|
331
|
-
def get(self, name: str) -> Optional[Action]:
|
|
332
|
-
"""Get an action by name."""
|
|
333
|
-
return self._actions.get(name)
|
|
334
|
-
|
|
335
|
-
def list_actions(self) -> List[str]:
|
|
336
|
-
"""List all registered action names."""
|
|
337
|
-
return list(self._actions.keys())
|
|
338
|
-
|
|
339
|
-
def clear(self) -> None:
|
|
340
|
-
"""Clear all registered actions."""
|
|
341
|
-
self._actions.clear()
|
monoco/core/scheduler/events.py
CHANGED
|
@@ -9,10 +9,27 @@ import asyncio
|
|
|
9
9
|
import inspect
|
|
10
10
|
import logging
|
|
11
11
|
from enum import Enum, auto
|
|
12
|
-
from typing import Dict, List, Callable, Any, Optional
|
|
12
|
+
from typing import Dict, List, Callable, Any, Optional, Union
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
def _is_async_handler(handler: Callable) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Check if a handler is async (coroutine function or has async __call__).
|
|
20
|
+
|
|
21
|
+
Handles both:
|
|
22
|
+
- Regular async functions: async def func(): ...
|
|
23
|
+
- Callable objects with async __call__: class Handler: async def __call__(self, ...): ...
|
|
24
|
+
"""
|
|
25
|
+
# Direct check for coroutine function
|
|
26
|
+
if inspect.iscoroutinefunction(handler):
|
|
27
|
+
return True
|
|
28
|
+
# Check for callable object with async __call__ method
|
|
29
|
+
if hasattr(handler, "__call__") and inspect.iscoroutinefunction(handler.__call__):
|
|
30
|
+
return True
|
|
31
|
+
return False
|
|
32
|
+
|
|
16
33
|
logger = logging.getLogger("monoco.core.scheduler.events")
|
|
17
34
|
|
|
18
35
|
|
|
@@ -38,6 +55,15 @@ class AgentEventType(Enum):
|
|
|
38
55
|
# PR events (for Reviewer trigger)
|
|
39
56
|
PR_CREATED = "pr.created"
|
|
40
57
|
PR_UPDATED = "pr.updated"
|
|
58
|
+
|
|
59
|
+
# IM events (FEAT-0167)
|
|
60
|
+
IM_MESSAGE_RECEIVED = "im.message.received"
|
|
61
|
+
IM_MESSAGE_REPLIED = "im.message.replied"
|
|
62
|
+
IM_AGENT_TRIGGER = "im.agent.trigger"
|
|
63
|
+
IM_SESSION_STARTED = "im.session.started"
|
|
64
|
+
IM_SESSION_CLOSED = "im.session.closed"
|
|
65
|
+
IM_CHANNEL_CREATED = "im.channel.created"
|
|
66
|
+
IM_CHANNEL_UPDATED = "im.channel.updated"
|
|
41
67
|
|
|
42
68
|
|
|
43
69
|
@dataclass
|
|
@@ -122,7 +148,7 @@ class EventBus:
|
|
|
122
148
|
tasks = []
|
|
123
149
|
for handler in handlers:
|
|
124
150
|
try:
|
|
125
|
-
if
|
|
151
|
+
if _is_async_handler(handler):
|
|
126
152
|
tasks.append(asyncio.create_task(handler(event)))
|
|
127
153
|
else:
|
|
128
154
|
handler(event)
|
monoco/core/setup.py
CHANGED
|
@@ -211,6 +211,8 @@ def init_cli(
|
|
|
211
211
|
project_config_path = project_config_dir / "project.yaml"
|
|
212
212
|
|
|
213
213
|
project_initialized = False
|
|
214
|
+
workspace_config = {}
|
|
215
|
+
project_key = "MON"
|
|
214
216
|
|
|
215
217
|
# Check if we should init project
|
|
216
218
|
if workspace_config_path.exists() or project_config_path.exists():
|
|
@@ -323,6 +325,13 @@ def init_cli(
|
|
|
323
325
|
try:
|
|
324
326
|
from monoco.core.githooks import install_hooks
|
|
325
327
|
|
|
328
|
+
# Check if git initialized, if not, do it
|
|
329
|
+
if not (cwd / ".git").exists():
|
|
330
|
+
console.print("[dim]Git repository not found. Initializing...[/dim]")
|
|
331
|
+
# Set global default branch to main
|
|
332
|
+
subprocess.run(["git", "config", "--global", "init.defaultBranch", "main"], check=False)
|
|
333
|
+
subprocess.run(["git", "init"], cwd=cwd, check=False)
|
|
334
|
+
|
|
326
335
|
# Re-load config to get the just-written hooks (or default ones)
|
|
327
336
|
# Actually we have the dict right here in workspace_config['hooks']
|
|
328
337
|
hooks_config = workspace_config.get("hooks", {})
|