monoco-toolkit 0.3.10__py3-none-any.whl → 0.3.11__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/__main__.py +8 -0
- monoco/core/artifacts/__init__.py +16 -0
- monoco/core/artifacts/manager.py +575 -0
- monoco/core/artifacts/models.py +161 -0
- monoco/core/config.py +31 -4
- monoco/core/git.py +23 -0
- monoco/core/ingestion/__init__.py +20 -0
- monoco/core/ingestion/discovery.py +248 -0
- monoco/core/ingestion/watcher.py +343 -0
- monoco/core/ingestion/worker.py +436 -0
- monoco/core/loader.py +633 -0
- monoco/core/registry.py +34 -25
- monoco/core/skills.py +119 -80
- monoco/daemon/app.py +77 -1
- monoco/daemon/commands.py +10 -0
- monoco/daemon/mailroom_service.py +196 -0
- monoco/daemon/models.py +1 -0
- monoco/daemon/scheduler.py +236 -0
- monoco/daemon/services.py +185 -0
- monoco/daemon/triggers.py +55 -0
- monoco/features/agent/adapter.py +17 -7
- monoco/features/agent/apoptosis.py +4 -4
- monoco/features/agent/manager.py +41 -5
- monoco/{core/resources/en/skills/monoco_core → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/resources/{roles/role-engineer.yaml → zh/roles/monoco_role_engineer.yaml} +3 -3
- monoco/features/agent/resources/{roles/role-manager.yaml → zh/roles/monoco_role_manager.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-planner.yaml → zh/roles/monoco_role_planner.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-reviewer.yaml → zh/roles/monoco_role_reviewer.yaml} +8 -8
- monoco/{core/resources/zh/skills/monoco_core → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/session.py +59 -11
- monoco/features/artifact/__init__.py +0 -0
- monoco/features/artifact/adapter.py +33 -0
- monoco/features/artifact/resources/zh/AGENTS.md +14 -0
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +278 -0
- monoco/features/glossary/adapter.py +18 -7
- monoco/features/glossary/resources/en/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/glossary/resources/zh/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/hooks/__init__.py +11 -0
- monoco/features/hooks/adapter.py +67 -0
- monoco/features/hooks/commands.py +309 -0
- monoco/features/hooks/core.py +441 -0
- monoco/features/hooks/resources/ADDING_HOOKS.md +234 -0
- monoco/features/i18n/adapter.py +18 -5
- monoco/features/i18n/core.py +482 -17
- monoco/features/i18n/resources/en/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/en/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/issue/adapter.py +19 -6
- monoco/features/issue/commands.py +281 -7
- monoco/features/issue/core.py +227 -13
- monoco/features/issue/engine/machine.py +114 -4
- monoco/features/issue/linter.py +60 -5
- monoco/features/issue/models.py +2 -2
- monoco/features/issue/resources/en/AGENTS.md +109 -0
- monoco/features/issue/resources/en/skills/{monoco_issue → monoco_atom_issue}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/resources/hooks/post-checkout.sh +39 -0
- monoco/features/issue/resources/hooks/pre-commit.sh +41 -0
- monoco/features/issue/resources/hooks/pre-push.sh +35 -0
- monoco/features/issue/resources/zh/AGENTS.md +109 -0
- monoco/features/issue/resources/zh/skills/{monoco_issue → monoco_atom_issue_lifecycle}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/validator.py +101 -1
- monoco/features/memo/adapter.py +21 -8
- monoco/features/memo/cli.py +103 -10
- monoco/features/memo/core.py +178 -92
- monoco/features/memo/models.py +53 -0
- monoco/features/memo/resources/en/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/en/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/spike/adapter.py +18 -5
- monoco/features/spike/resources/en/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/en/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/main.py +38 -1
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.11.dist-info}/METADATA +7 -1
- monoco_toolkit-0.3.11.dist-info/RECORD +181 -0
- monoco_toolkit-0.3.10.dist-info/RECORD +0 -156
- /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
- /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.11.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.11.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.11.dist-info}/licenses/LICENSE +0 -0
monoco/core/registry.py
CHANGED
|
@@ -1,45 +1,54 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
2
|
from monoco.core.feature import MonocoFeature
|
|
3
|
+
from monoco.core.loader import FeatureLoader, FeatureRegistry as LoaderFeatureRegistry
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class FeatureRegistry:
|
|
6
|
-
|
|
7
|
+
"""
|
|
8
|
+
Feature registry that wraps the new unified FeatureLoader.
|
|
9
|
+
|
|
10
|
+
This class provides backward compatibility while delegating to the
|
|
11
|
+
new FeatureLoader for dynamic discovery and lifecycle management.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
_loader: Optional[FeatureLoader] = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _get_loader(cls) -> FeatureLoader:
|
|
18
|
+
"""Get or create the default feature loader."""
|
|
19
|
+
if cls._loader is None:
|
|
20
|
+
cls._loader = FeatureLoader()
|
|
21
|
+
# Discover and load all features
|
|
22
|
+
cls._loader.discover()
|
|
23
|
+
cls._loader.load_all()
|
|
24
|
+
return cls._loader
|
|
7
25
|
|
|
8
26
|
@classmethod
|
|
9
27
|
def register(cls, feature: MonocoFeature):
|
|
10
28
|
"""Register a feature instance."""
|
|
11
|
-
|
|
29
|
+
loader = cls._get_loader()
|
|
30
|
+
loader.registry.register(feature) # type: ignore
|
|
12
31
|
|
|
13
32
|
@classmethod
|
|
14
33
|
def get_features(cls) -> List[MonocoFeature]:
|
|
15
34
|
"""Get all registered features."""
|
|
16
|
-
|
|
35
|
+
loader = cls._get_loader()
|
|
36
|
+
return loader.registry.get_all() # type: ignore
|
|
17
37
|
|
|
18
38
|
@classmethod
|
|
19
|
-
def get_feature(cls, name: str) -> MonocoFeature:
|
|
39
|
+
def get_feature(cls, name: str) -> Optional[MonocoFeature]:
|
|
20
40
|
"""Get a specific feature by name."""
|
|
21
|
-
|
|
41
|
+
loader = cls._get_loader()
|
|
42
|
+
return loader.registry.get(name) # type: ignore
|
|
22
43
|
|
|
23
44
|
@classmethod
|
|
24
45
|
def load_defaults(cls):
|
|
25
46
|
"""
|
|
26
|
-
Load default core features.
|
|
27
|
-
TODO: In the future, this could be dynamic via entry points.
|
|
28
|
-
"""
|
|
29
|
-
# Import here to avoid circular dependencies at module level
|
|
30
|
-
from monoco.features.issue.adapter import IssueFeature
|
|
31
|
-
from monoco.features.spike.adapter import SpikeFeature
|
|
32
|
-
from monoco.features.i18n.adapter import I18nFeature
|
|
33
|
-
from monoco.features.memo.adapter import MemoFeature
|
|
34
|
-
|
|
35
|
-
cls.register(IssueFeature())
|
|
36
|
-
cls.register(SpikeFeature())
|
|
37
|
-
cls.register(I18nFeature())
|
|
38
|
-
cls.register(MemoFeature())
|
|
39
|
-
|
|
47
|
+
Load default core features using the unified FeatureLoader.
|
|
40
48
|
|
|
41
|
-
from monoco
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
This method discovers and loads all features from monoco/features/
|
|
50
|
+
automatically, replacing the manual registration approach.
|
|
51
|
+
"""
|
|
52
|
+
loader = cls._get_loader()
|
|
53
|
+
# Features are already discovered and loaded in _get_loader
|
|
54
|
+
# This method is kept for backward compatibility
|
monoco/core/skills.py
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
Skill Manager for Monoco Toolkit.
|
|
3
3
|
|
|
4
4
|
This module provides centralized management and distribution of Agent Skills
|
|
5
|
-
following the
|
|
6
|
-
- Atom Skills: Atomic capabilities
|
|
7
|
-
- Workflow Skills: Orchestration of atoms
|
|
8
|
-
- Role Skills: Configuration layer
|
|
5
|
+
following the standardized architecture:
|
|
6
|
+
- Atom Skills: monoco_atom_{name} - Atomic capabilities
|
|
7
|
+
- Workflow Skills: monoco_workflow_{name} - Orchestration of atoms
|
|
8
|
+
- Role Skills: monoco_role_{name} - Configuration layer
|
|
9
9
|
|
|
10
10
|
Key Responsibilities:
|
|
11
|
-
1. Discover skills from
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
15
|
-
|
|
11
|
+
1. Discover skills from features (monoco/features/{feature}/resources/)
|
|
12
|
+
2. Validate skill structure and metadata
|
|
13
|
+
3. Distribute skills to target agent framework directories
|
|
14
|
+
4. Support i18n for skill content
|
|
15
|
+
|
|
16
|
+
Architecture Principle:
|
|
17
|
+
- Core is framework-only, no skills
|
|
18
|
+
- All skills are defined in Features (value delivery atoms)
|
|
19
|
+
- All skills follow naming convention: monoco_{type}_{name}
|
|
16
20
|
"""
|
|
17
21
|
|
|
18
22
|
import shutil
|
|
@@ -165,22 +169,17 @@ class Skill:
|
|
|
165
169
|
|
|
166
170
|
class SkillManager:
|
|
167
171
|
"""
|
|
168
|
-
Central manager for Monoco skills
|
|
172
|
+
Central manager for Monoco skills.
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
- Atom Skills: resources/atoms/*.yaml
|
|
172
|
-
- Workflow Skills: resources/workflows/*.yaml
|
|
173
|
-
- Role Skills: resources/roles
|
|
174
|
+
Architecture:
|
|
175
|
+
- Atom Skills: resources/{lang}/skills/monoco_atom_*/SKILL.md or resources/atoms/*.yaml
|
|
176
|
+
- Workflow Skills: resources/{lang}/skills/monoco_workflow_*/SKILL.md or resources/workflows/*.yaml
|
|
177
|
+
- Role Skills: resources/{lang}/roles/monoco_role_*.yaml
|
|
174
178
|
|
|
175
|
-
|
|
176
|
-
- Standard Skills: resources/{lang}/skills/{name}/SKILL.md
|
|
177
|
-
- Flow Skills: resources/{lang}/skills/flow_*/SKILL.md
|
|
179
|
+
All skills follow the naming convention: monoco_{type}_{name}
|
|
178
180
|
"""
|
|
179
181
|
|
|
180
|
-
#
|
|
181
|
-
FLOW_SKILL_PREFIX = "monoco_flow_"
|
|
182
|
-
|
|
183
|
-
# Prefix for three-level architecture skills
|
|
182
|
+
# Prefix for standardized skill naming
|
|
184
183
|
ATOM_PREFIX = "monoco_atom_"
|
|
185
184
|
WORKFLOW_PREFIX = "monoco_workflow_"
|
|
186
185
|
ROLE_PREFIX = "monoco_role_"
|
|
@@ -189,37 +188,24 @@ class SkillManager:
|
|
|
189
188
|
self,
|
|
190
189
|
root: Path,
|
|
191
190
|
features: Optional[List] = None,
|
|
192
|
-
flow_skill_prefix: str = FLOW_SKILL_PREFIX,
|
|
193
191
|
):
|
|
194
192
|
self.root = root
|
|
195
193
|
self.features = features or []
|
|
196
|
-
self.flow_skill_prefix = flow_skill_prefix
|
|
197
194
|
|
|
198
|
-
#
|
|
195
|
+
# Skills discovered from resources/{lang}/skills/monoco_*/SKILL.md
|
|
199
196
|
self.skills: Dict[str, Skill] = {}
|
|
200
197
|
|
|
201
|
-
#
|
|
198
|
+
# Three-level architecture skills
|
|
202
199
|
self._skill_loaders: Dict[str, SkillLoader] = {}
|
|
203
200
|
self._atoms: Dict[str, AtomSkillMetadata] = {}
|
|
204
201
|
self._workflows: Dict[str, WorkflowSkillMetadata] = {}
|
|
205
202
|
self._roles: Dict[str, RoleSkillMetadata] = {}
|
|
206
203
|
|
|
207
|
-
#
|
|
204
|
+
# Discover skills from features only (core is framework-only, no skills)
|
|
208
205
|
if self.features:
|
|
209
206
|
self._discover_skills_from_features()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# Load new three-level skills
|
|
213
|
-
self._discover_three_level_skills()
|
|
214
|
-
|
|
215
|
-
def _discover_core_skills(self) -> None:
|
|
216
|
-
"""Discover skills from monoco/core/resources/{lang}/skills/."""
|
|
217
|
-
core_resources_dir = self.root / "monoco" / "core" / "resources"
|
|
218
|
-
|
|
219
|
-
if not core_resources_dir.exists():
|
|
220
|
-
return
|
|
207
|
+
self._discover_three_level_skills()
|
|
221
208
|
|
|
222
|
-
self._discover_skills_in_resources(core_resources_dir, "monoco_core")
|
|
223
209
|
|
|
224
210
|
def _discover_skills_from_features(self) -> None:
|
|
225
211
|
"""Discover skills from Feature resources."""
|
|
@@ -250,7 +236,7 @@ class SkillManager:
|
|
|
250
236
|
if not resources_dir.exists():
|
|
251
237
|
return
|
|
252
238
|
|
|
253
|
-
|
|
239
|
+
skill_folders: Set[Path] = set()
|
|
254
240
|
|
|
255
241
|
for lang_dir in resources_dir.iterdir():
|
|
256
242
|
if not lang_dir.is_dir() or len(lang_dir.name) != 2:
|
|
@@ -262,9 +248,11 @@ class SkillManager:
|
|
|
262
248
|
|
|
263
249
|
for skill_subdir in skills_dir.iterdir():
|
|
264
250
|
if skill_subdir.is_dir() and (skill_subdir / "SKILL.md").exists():
|
|
265
|
-
|
|
251
|
+
# print(f"DEBUG: Found skill folder {skill_subdir.name} in feature {feature_name}")
|
|
252
|
+
skill_folders.add(skill_subdir)
|
|
266
253
|
|
|
267
|
-
for
|
|
254
|
+
for skill_dir in skill_folders:
|
|
255
|
+
skill_name = skill_dir.name
|
|
268
256
|
skill = Skill(
|
|
269
257
|
root_dir=self.root,
|
|
270
258
|
skill_name=skill_name,
|
|
@@ -277,40 +265,96 @@ class SkillManager:
|
|
|
277
265
|
)
|
|
278
266
|
continue
|
|
279
267
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
name = name[5:]
|
|
285
|
-
skill_key = f"{self.flow_skill_prefix}{name}"
|
|
268
|
+
# Naming Logic: All skills must follow monoco_{type}_{name} convention
|
|
269
|
+
# The skill_key is the folder name (which should match the metadata name)
|
|
270
|
+
if skill_name.startswith("monoco_"):
|
|
271
|
+
skill_key = skill_name
|
|
286
272
|
else:
|
|
287
|
-
|
|
273
|
+
# Non-compliant skills are skipped (should not happen after standardization)
|
|
274
|
+
console.print(
|
|
275
|
+
f"[yellow]Warning: Skill {skill_name} does not follow monoco_{{type}}_{{name}} naming, skipping[/yellow]"
|
|
276
|
+
)
|
|
277
|
+
continue
|
|
288
278
|
|
|
289
279
|
skill.name = skill_key
|
|
290
280
|
self.skills[skill_key] = skill
|
|
291
281
|
|
|
292
282
|
def _discover_three_level_skills(self) -> None:
|
|
293
|
-
"""Discover skills from the new three-level architecture
|
|
294
|
-
|
|
295
|
-
agent_resources_dir = self.root / "monoco" / "features" / "agent" / "resources"
|
|
296
|
-
|
|
297
|
-
if not agent_resources_dir.exists():
|
|
298
|
-
return
|
|
299
|
-
|
|
300
|
-
loader = SkillLoader(agent_resources_dir)
|
|
301
|
-
loader.load_all()
|
|
302
|
-
|
|
303
|
-
self._skill_loaders["agent"] = loader
|
|
283
|
+
"""Discover skills from the new three-level architecture in resources/{atoms,workflows,roles}/."""
|
|
284
|
+
from monoco.core.feature import MonocoFeature
|
|
304
285
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
286
|
+
for feature in self.features:
|
|
287
|
+
if not isinstance(feature, MonocoFeature):
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
module_parts = feature.__class__.__module__.split(".")
|
|
291
|
+
if (
|
|
292
|
+
len(module_parts) >= 3
|
|
293
|
+
and module_parts[0] == "monoco"
|
|
294
|
+
and module_parts[1] == "features"
|
|
295
|
+
):
|
|
296
|
+
feature_name = module_parts[2]
|
|
297
|
+
resources_dir = self.root / "monoco" / "features" / feature_name / "resources"
|
|
298
|
+
|
|
299
|
+
if not resources_dir.exists():
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
# Discover atoms from resources/atoms/*.yaml
|
|
303
|
+
atoms_dir = resources_dir / "atoms"
|
|
304
|
+
if atoms_dir.exists():
|
|
305
|
+
for atom_file in atoms_dir.glob("*.yaml"):
|
|
306
|
+
try:
|
|
307
|
+
data = yaml.safe_load(atom_file.read_text())
|
|
308
|
+
atom = AtomSkillMetadata(**data)
|
|
309
|
+
|
|
310
|
+
# Ensure name follows monoco_atom_ prefix
|
|
311
|
+
atom_key = atom.name
|
|
312
|
+
if not atom_key.startswith(self.ATOM_PREFIX):
|
|
313
|
+
atom_key = f"{self.ATOM_PREFIX}{atom_key}"
|
|
314
|
+
|
|
315
|
+
self._atoms[atom_key] = atom
|
|
316
|
+
except Exception as e:
|
|
317
|
+
console.print(f"[red]Failed to load atom skill {atom_file}: {e}[/red]")
|
|
318
|
+
|
|
319
|
+
# Discover workflows from resources/workflows/*.yaml
|
|
320
|
+
workflows_dir = resources_dir / "workflows"
|
|
321
|
+
if workflows_dir.exists():
|
|
322
|
+
for workflow_file in workflows_dir.glob("*.yaml"):
|
|
323
|
+
try:
|
|
324
|
+
data = yaml.safe_load(workflow_file.read_text())
|
|
325
|
+
workflow = WorkflowSkillMetadata(**data)
|
|
326
|
+
|
|
327
|
+
# Ensure name follows monoco_workflow_ prefix
|
|
328
|
+
workflow_key = workflow.name
|
|
329
|
+
if not workflow_key.startswith(self.WORKFLOW_PREFIX):
|
|
330
|
+
workflow_key = f"{self.WORKFLOW_PREFIX}{workflow_key}"
|
|
331
|
+
|
|
332
|
+
self._workflows[workflow_key] = workflow
|
|
333
|
+
except Exception as e:
|
|
334
|
+
console.print(f"[red]Failed to load workflow skill {workflow_file}: {e}[/red]")
|
|
335
|
+
|
|
336
|
+
# Discover roles from resources/{lang}/roles/*.yaml
|
|
337
|
+
for lang_dir in resources_dir.iterdir():
|
|
338
|
+
if not lang_dir.is_dir() or len(lang_dir.name) != 2:
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
roles_dir = lang_dir / "roles"
|
|
342
|
+
if not roles_dir.exists():
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
for role_file in roles_dir.glob("*.yaml"):
|
|
346
|
+
try:
|
|
347
|
+
data = yaml.safe_load(role_file.read_text())
|
|
348
|
+
role = RoleSkillMetadata(**data)
|
|
349
|
+
|
|
350
|
+
# Ensure name follows monoco_role_ prefix
|
|
351
|
+
role_key = role.name
|
|
352
|
+
if not role_key.startswith(self.ROLE_PREFIX):
|
|
353
|
+
role_key = f"{self.ROLE_PREFIX}{role_key}"
|
|
354
|
+
|
|
355
|
+
self._roles[role_key] = role
|
|
356
|
+
except Exception as e:
|
|
357
|
+
console.print(f"[red]Failed to load role skill {role_file}: {e}[/red]")
|
|
314
358
|
|
|
315
359
|
# ========================================================================
|
|
316
360
|
# Legacy Skill API (backward compatible)
|
|
@@ -688,19 +732,14 @@ class SkillManager:
|
|
|
688
732
|
"""Get list of available flow skill commands."""
|
|
689
733
|
commands = []
|
|
690
734
|
|
|
691
|
-
#
|
|
692
|
-
for skill in self.
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
name = skill.name
|
|
698
|
-
if name.startswith(self.flow_skill_prefix):
|
|
699
|
-
role = name[len(self.flow_skill_prefix):]
|
|
700
|
-
if role:
|
|
701
|
-
commands.append(f"/flow:{role}")
|
|
735
|
+
# Workflow/Flow skills with role attribute from legacy skills
|
|
736
|
+
for skill in self.skills.values():
|
|
737
|
+
if skill.get_type() in ["flow", "workflow"]:
|
|
738
|
+
role = skill.get_role()
|
|
739
|
+
if role:
|
|
740
|
+
commands.append(f"/flow:{role}")
|
|
702
741
|
|
|
703
|
-
#
|
|
742
|
+
# Role skills from three-level architecture
|
|
704
743
|
for role_name in self._roles.keys():
|
|
705
744
|
short_name = role_name.replace(self.ROLE_PREFIX, "")
|
|
706
745
|
commands.append(f"/flow:{short_name}")
|
monoco/daemon/app.py
CHANGED
|
@@ -9,6 +9,8 @@ from typing import Optional, List, Dict
|
|
|
9
9
|
from monoco.daemon.services import Broadcaster, ProjectManager
|
|
10
10
|
from monoco.core.git import GitMonitor
|
|
11
11
|
from monoco.core.config import get_config, ConfigMonitor, ConfigScope, get_config_path
|
|
12
|
+
from monoco.daemon.scheduler import SchedulerService
|
|
13
|
+
from monoco.daemon.mailroom_service import MailroomService
|
|
12
14
|
|
|
13
15
|
# Configure logging
|
|
14
16
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -34,6 +36,8 @@ broadcaster = Broadcaster()
|
|
|
34
36
|
git_monitor: GitMonitor | None = None
|
|
35
37
|
config_monitors: List[ConfigMonitor] = []
|
|
36
38
|
project_manager: ProjectManager | None = None
|
|
39
|
+
scheduler_service: SchedulerService | None = None
|
|
40
|
+
mailroom_service: MailroomService | None = None
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
@asynccontextmanager
|
|
@@ -41,7 +45,7 @@ async def lifespan(app: FastAPI):
|
|
|
41
45
|
# Startup
|
|
42
46
|
logger.info("Starting Monoco Daemon services...")
|
|
43
47
|
|
|
44
|
-
global project_manager, git_monitor, config_monitors
|
|
48
|
+
global project_manager, git_monitor, config_monitors, scheduler_service, mailroom_service
|
|
45
49
|
# Use MONOCO_SERVER_ROOT if set, otherwise CWD
|
|
46
50
|
env_root = os.getenv("MONOCO_SERVER_ROOT")
|
|
47
51
|
workspace_root = Path(env_root) if env_root else Path.cwd()
|
|
@@ -72,6 +76,17 @@ async def lifespan(app: FastAPI):
|
|
|
72
76
|
]
|
|
73
77
|
|
|
74
78
|
await project_manager.start_all()
|
|
79
|
+
# Start Scheduler
|
|
80
|
+
scheduler_service = SchedulerService(project_manager)
|
|
81
|
+
await scheduler_service.start()
|
|
82
|
+
|
|
83
|
+
# Start Mailroom Service
|
|
84
|
+
mailroom_service = MailroomService(
|
|
85
|
+
workspace_root=workspace_root,
|
|
86
|
+
broadcaster=broadcaster,
|
|
87
|
+
)
|
|
88
|
+
await mailroom_service.start()
|
|
89
|
+
|
|
75
90
|
git_task = asyncio.create_task(git_monitor.start())
|
|
76
91
|
config_tasks = [asyncio.create_task(m.start()) for m in config_monitors]
|
|
77
92
|
|
|
@@ -84,6 +99,10 @@ async def lifespan(app: FastAPI):
|
|
|
84
99
|
m.stop()
|
|
85
100
|
if project_manager:
|
|
86
101
|
project_manager.stop_all()
|
|
102
|
+
if scheduler_service:
|
|
103
|
+
scheduler_service.stop()
|
|
104
|
+
if mailroom_service:
|
|
105
|
+
await mailroom_service.stop()
|
|
87
106
|
|
|
88
107
|
await git_task
|
|
89
108
|
await asyncio.gather(*config_tasks)
|
|
@@ -301,6 +320,21 @@ async def create_issue_endpoint(payload: CreateIssueRequest):
|
|
|
301
320
|
related=payload.related,
|
|
302
321
|
subdir=payload.subdir,
|
|
303
322
|
)
|
|
323
|
+
|
|
324
|
+
# Link memos to the newly created issue
|
|
325
|
+
if payload.from_memos:
|
|
326
|
+
from monoco.features.memo.core import load_memos, update_memo
|
|
327
|
+
|
|
328
|
+
existing_memos = {m.uid: m for m in load_memos(project.issues_root)}
|
|
329
|
+
|
|
330
|
+
for memo_id in payload.from_memos:
|
|
331
|
+
if memo_id in existing_memos:
|
|
332
|
+
# Only update if not already linked to this issue (idempotency)
|
|
333
|
+
memo = existing_memos[memo_id]
|
|
334
|
+
if memo.ref != issue.id:
|
|
335
|
+
update_memo(project.issues_root, memo_id, {"status": "tracked", "ref": issue.id})
|
|
336
|
+
# Non-blocking: ignore missing memos (just log warning)
|
|
337
|
+
|
|
304
338
|
return issue
|
|
305
339
|
except Exception as e:
|
|
306
340
|
raise HTTPException(status_code=400, detail=str(e))
|
|
@@ -489,3 +523,45 @@ async def update_workspace_state(state: WorkspaceState):
|
|
|
489
523
|
raise HTTPException(
|
|
490
524
|
status_code=500, detail=f"Failed to persist state: {str(e)}"
|
|
491
525
|
)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
# --- Mailroom API Endpoints ---
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@app.get("/api/v1/mailroom/status")
|
|
532
|
+
async def get_mailroom_status():
|
|
533
|
+
"""
|
|
534
|
+
Get Mailroom service status, capabilities, and statistics.
|
|
535
|
+
"""
|
|
536
|
+
if not mailroom_service:
|
|
537
|
+
raise HTTPException(status_code=503, detail="Mailroom service not initialized")
|
|
538
|
+
|
|
539
|
+
return mailroom_service.get_status()
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@app.post("/api/v1/mailroom/discover")
|
|
543
|
+
async def trigger_mailroom_discovery():
|
|
544
|
+
"""
|
|
545
|
+
Trigger environment discovery for conversion tools.
|
|
546
|
+
"""
|
|
547
|
+
if not mailroom_service:
|
|
548
|
+
raise HTTPException(status_code=503, detail="Mailroom service not initialized")
|
|
549
|
+
|
|
550
|
+
discovery = mailroom_service.get_discovery()
|
|
551
|
+
tools = discovery.discover(force=True)
|
|
552
|
+
|
|
553
|
+
total_tools = sum(len(t) for t in tools.values())
|
|
554
|
+
capabilities = discovery.get_capabilities_summary()
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
"discovered": total_tools,
|
|
558
|
+
"capabilities": capabilities,
|
|
559
|
+
"tools": [
|
|
560
|
+
{
|
|
561
|
+
"name": tool.name,
|
|
562
|
+
"type": tool.tool_type.value,
|
|
563
|
+
"version": tool.version,
|
|
564
|
+
}
|
|
565
|
+
for tool in discovery.get_all_tools()
|
|
566
|
+
],
|
|
567
|
+
}
|
monoco/daemon/commands.py
CHANGED
|
@@ -15,6 +15,9 @@ def serve(
|
|
|
15
15
|
False, "--reload", "-r", help="Enable auto-reload for dev"
|
|
16
16
|
),
|
|
17
17
|
root: Optional[str] = typer.Option(None, "--root", help="Workspace root directory"),
|
|
18
|
+
max_agents: Optional[int] = typer.Option(
|
|
19
|
+
None, "--max-agents", help="Override global maximum concurrent agents (default: 3)"
|
|
20
|
+
),
|
|
18
21
|
):
|
|
19
22
|
"""
|
|
20
23
|
Start the Monoco Daemon server.
|
|
@@ -26,6 +29,13 @@ def serve(
|
|
|
26
29
|
print_output(
|
|
27
30
|
f"Workspace Root: {os.environ['MONOCO_SERVER_ROOT']}", title="Monoco Serve"
|
|
28
31
|
)
|
|
32
|
+
|
|
33
|
+
# Set max agents override if provided
|
|
34
|
+
if max_agents is not None:
|
|
35
|
+
os.environ["MONOCO_MAX_AGENTS"] = str(max_agents)
|
|
36
|
+
print_output(
|
|
37
|
+
f"Max Agents: {max_agents}", title="Monoco Serve"
|
|
38
|
+
)
|
|
29
39
|
|
|
30
40
|
print_output(
|
|
31
41
|
f"Starting Monoco Daemon on http://{host}:{port}", title="Monoco Serve"
|