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.
Files changed (120) hide show
  1. monoco/core/automation/__init__.py +0 -11
  2. monoco/core/automation/handlers.py +108 -26
  3. monoco/core/config.py +28 -10
  4. monoco/core/daemon/__init__.py +5 -0
  5. monoco/core/daemon/pid.py +290 -0
  6. monoco/core/injection.py +86 -8
  7. monoco/core/integrations.py +0 -24
  8. monoco/core/router/__init__.py +1 -39
  9. monoco/core/router/action.py +3 -142
  10. monoco/core/scheduler/events.py +28 -2
  11. monoco/core/setup.py +9 -0
  12. monoco/core/sync.py +199 -4
  13. monoco/core/watcher/__init__.py +6 -0
  14. monoco/core/watcher/base.py +18 -1
  15. monoco/core/watcher/im.py +460 -0
  16. monoco/core/watcher/memo.py +40 -48
  17. monoco/daemon/app.py +3 -60
  18. monoco/daemon/commands.py +459 -25
  19. monoco/daemon/scheduler.py +1 -16
  20. monoco/daemon/services.py +15 -0
  21. monoco/features/agent/resources/en/AGENTS.md +14 -14
  22. monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
  23. monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
  24. monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
  25. monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
  26. monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
  27. monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
  28. monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
  29. monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
  30. monoco/features/hooks/__init__.py +61 -6
  31. monoco/features/hooks/commands.py +281 -271
  32. monoco/features/hooks/dispatchers/__init__.py +23 -0
  33. monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
  34. monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
  35. monoco/features/hooks/manager.py +357 -0
  36. monoco/features/hooks/models.py +262 -0
  37. monoco/features/hooks/parser.py +322 -0
  38. monoco/features/hooks/universal_interceptor.py +503 -0
  39. monoco/features/im/__init__.py +67 -0
  40. monoco/features/im/core.py +782 -0
  41. monoco/features/im/models.py +311 -0
  42. monoco/features/issue/commands.py +65 -50
  43. monoco/features/issue/core.py +199 -99
  44. monoco/features/issue/domain_commands.py +0 -19
  45. monoco/features/issue/resources/en/AGENTS.md +17 -122
  46. monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
  47. monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
  48. monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
  49. monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
  50. monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
  51. monoco/features/issue/resources/zh/AGENTS.md +18 -123
  52. monoco/features/memo/cli.py +15 -64
  53. monoco/features/memo/core.py +6 -34
  54. monoco/features/memo/models.py +24 -15
  55. monoco/features/memo/resources/en/AGENTS.md +31 -0
  56. monoco/features/memo/resources/zh/AGENTS.md +28 -5
  57. monoco/main.py +5 -3
  58. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
  59. monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
  60. monoco/core/automation/config.py +0 -338
  61. monoco/core/execution.py +0 -67
  62. monoco/core/executor/__init__.py +0 -38
  63. monoco/core/executor/agent_action.py +0 -254
  64. monoco/core/executor/git_action.py +0 -303
  65. monoco/core/executor/im_action.py +0 -309
  66. monoco/core/executor/pytest_action.py +0 -218
  67. monoco/core/router/router.py +0 -392
  68. monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
  69. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
  70. monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
  71. monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
  72. monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
  73. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  74. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
  75. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
  76. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
  77. monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
  78. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
  79. monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
  80. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
  81. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
  82. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
  83. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
  84. monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
  85. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  86. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
  87. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
  88. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
  89. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
  90. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
  91. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
  92. monoco/features/hooks/adapter.py +0 -67
  93. monoco/features/hooks/core.py +0 -441
  94. monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
  95. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  96. monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
  97. monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  98. monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
  99. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  100. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  101. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  102. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  103. monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
  104. monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
  105. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  106. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  107. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  108. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  109. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
  110. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  111. monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
  112. monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  113. monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
  114. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
  115. monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
  116. monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
  117. monoco_toolkit-0.3.12.dist-info/RECORD +0 -202
  118. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
  119. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
  120. {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
- def __init__(self, target_file: Path):
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
- return original + "\n\n" + managed_block_str.strip()
167
+ result += original + "\n\n" + managed_block_str.strip()
110
168
  elif original:
111
- return original + "\n" + managed_block_str.strip()
169
+ result += original + "\n" + managed_block_str.strip()
112
170
  else:
113
- return managed_block_str.strip() + "\n"
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 = pre_block
138
- if result:
139
- result += "\n\n"
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
@@ -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",
@@ -1,55 +1,17 @@
1
1
  """
2
2
  Router Module - Layer 2 of the Event Automation Framework.
3
3
 
4
- This module provides event routing capabilities that map events to actions.
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
  ]
@@ -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 related classes for the execution layer.
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, Callable, Dict, List, Optional, Union
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 the ActionRouter when events match their conditions.
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()
@@ -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 inspect.iscoroutinefunction(handler):
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", {})