monoco-toolkit 0.3.9__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.
Files changed (132) hide show
  1. monoco/__main__.py +8 -0
  2. monoco/core/artifacts/__init__.py +16 -0
  3. monoco/core/artifacts/manager.py +575 -0
  4. monoco/core/artifacts/models.py +161 -0
  5. monoco/core/config.py +38 -4
  6. monoco/core/git.py +23 -0
  7. monoco/core/hooks/builtin/git_cleanup.py +1 -1
  8. monoco/core/ingestion/__init__.py +20 -0
  9. monoco/core/ingestion/discovery.py +248 -0
  10. monoco/core/ingestion/watcher.py +343 -0
  11. monoco/core/ingestion/worker.py +436 -0
  12. monoco/core/injection.py +63 -29
  13. monoco/core/integrations.py +2 -2
  14. monoco/core/loader.py +633 -0
  15. monoco/core/output.py +5 -5
  16. monoco/core/registry.py +34 -19
  17. monoco/core/resource/__init__.py +5 -0
  18. monoco/core/resource/finder.py +98 -0
  19. monoco/core/resource/manager.py +91 -0
  20. monoco/core/resource/models.py +35 -0
  21. monoco/core/skill_framework.py +292 -0
  22. monoco/core/skills.py +524 -385
  23. monoco/core/sync.py +73 -1
  24. monoco/core/workflow_converter.py +420 -0
  25. monoco/daemon/app.py +77 -1
  26. monoco/daemon/commands.py +10 -0
  27. monoco/daemon/mailroom_service.py +196 -0
  28. monoco/daemon/models.py +1 -0
  29. monoco/daemon/scheduler.py +236 -0
  30. monoco/daemon/services.py +185 -0
  31. monoco/daemon/triggers.py +55 -0
  32. monoco/features/agent/__init__.py +2 -2
  33. monoco/features/agent/adapter.py +41 -0
  34. monoco/features/agent/apoptosis.py +44 -0
  35. monoco/features/agent/cli.py +101 -144
  36. monoco/features/agent/config.py +35 -21
  37. monoco/features/agent/defaults.py +6 -49
  38. monoco/features/agent/engines.py +32 -6
  39. monoco/features/agent/manager.py +47 -6
  40. monoco/features/agent/models.py +2 -2
  41. monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
  42. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
  43. monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
  44. monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
  45. monoco/{core/resources/en → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +3 -1
  46. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +94 -0
  47. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +93 -0
  48. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +85 -0
  49. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +114 -0
  50. monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
  51. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
  52. monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
  53. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +49 -0
  54. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +46 -0
  55. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +46 -0
  56. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +47 -0
  57. monoco/{core/resources/zh → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +3 -1
  58. monoco/features/agent/resources/{skills/flow_engineer → zh/skills/monoco_workflow_agent_engineer}/SKILL.md +2 -2
  59. monoco/features/agent/resources/{skills/flow_manager → zh/skills/monoco_workflow_agent_manager}/SKILL.md +2 -2
  60. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +259 -0
  61. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +137 -0
  62. monoco/features/agent/session.py +59 -11
  63. monoco/features/agent/worker.py +38 -2
  64. monoco/features/artifact/__init__.py +0 -0
  65. monoco/features/artifact/adapter.py +33 -0
  66. monoco/features/artifact/resources/zh/AGENTS.md +14 -0
  67. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +278 -0
  68. monoco/features/glossary/__init__.py +0 -0
  69. monoco/features/glossary/adapter.py +42 -0
  70. monoco/features/glossary/config.py +5 -0
  71. monoco/features/glossary/resources/en/AGENTS.md +29 -0
  72. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +35 -0
  73. monoco/features/glossary/resources/zh/AGENTS.md +29 -0
  74. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +35 -0
  75. monoco/features/hooks/__init__.py +11 -0
  76. monoco/features/hooks/adapter.py +67 -0
  77. monoco/features/hooks/commands.py +309 -0
  78. monoco/features/hooks/core.py +441 -0
  79. monoco/features/hooks/resources/ADDING_HOOKS.md +234 -0
  80. monoco/features/i18n/adapter.py +18 -5
  81. monoco/features/i18n/core.py +482 -17
  82. monoco/features/i18n/resources/en/{SKILL.md → skills/monoco_atom_i18n/SKILL.md} +3 -1
  83. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +105 -0
  84. monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_atom_i18n/SKILL.md} +3 -1
  85. monoco/features/i18n/resources/{skills/i18n_scan_workflow → zh/skills/monoco_workflow_i18n_scan}/SKILL.md +2 -2
  86. monoco/features/issue/adapter.py +19 -6
  87. monoco/features/issue/commands.py +281 -7
  88. monoco/features/issue/core.py +272 -19
  89. monoco/features/issue/engine/machine.py +118 -5
  90. monoco/features/issue/linter.py +60 -5
  91. monoco/features/issue/models.py +3 -2
  92. monoco/features/issue/resources/en/AGENTS.md +109 -0
  93. monoco/features/issue/resources/en/{SKILL.md → skills/monoco_atom_issue/SKILL.md} +3 -1
  94. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +167 -0
  95. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +224 -0
  96. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +159 -0
  97. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +203 -0
  98. monoco/features/issue/resources/hooks/post-checkout.sh +39 -0
  99. monoco/features/issue/resources/hooks/pre-commit.sh +41 -0
  100. monoco/features/issue/resources/hooks/pre-push.sh +35 -0
  101. monoco/features/issue/resources/zh/AGENTS.md +109 -0
  102. monoco/features/issue/resources/zh/{SKILL.md → skills/monoco_atom_issue_lifecycle/SKILL.md} +3 -1
  103. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +167 -0
  104. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +224 -0
  105. monoco/features/issue/resources/{skills/issue_lifecycle_workflow → zh/skills/monoco_workflow_issue_management}/SKILL.md +2 -2
  106. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +203 -0
  107. monoco/features/issue/validator.py +101 -1
  108. monoco/features/memo/adapter.py +21 -8
  109. monoco/features/memo/cli.py +103 -10
  110. monoco/features/memo/core.py +178 -92
  111. monoco/features/memo/models.py +53 -0
  112. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +77 -0
  113. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +140 -0
  114. monoco/features/memo/resources/zh/{SKILL.md → skills/monoco_atom_memo/SKILL.md} +3 -1
  115. monoco/features/memo/resources/{skills/note_processing_workflow → zh/skills/monoco_workflow_note_processing}/SKILL.md +2 -2
  116. monoco/features/spike/adapter.py +18 -5
  117. monoco/features/spike/resources/en/{SKILL.md → skills/monoco_atom_spike/SKILL.md} +3 -1
  118. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +121 -0
  119. monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_atom_spike/SKILL.md} +3 -1
  120. monoco/features/spike/resources/{skills/research_workflow → zh/skills/monoco_workflow_research}/SKILL.md +2 -2
  121. monoco/main.py +38 -1
  122. monoco_toolkit-0.3.11.dist-info/METADATA +130 -0
  123. monoco_toolkit-0.3.11.dist-info/RECORD +181 -0
  124. monoco/features/agent/reliability.py +0 -106
  125. monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +0 -114
  126. monoco_toolkit-0.3.9.dist-info/METADATA +0 -127
  127. monoco_toolkit-0.3.9.dist-info/RECORD +0 -115
  128. /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
  129. /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
  130. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/WHEEL +0 -0
  131. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/entry_points.txt +0 -0
  132. {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,10 @@
1
- from .models import RoleTemplate, AgentConfig, SchedulerConfig
1
+ from .models import RoleTemplate, AgentRoleConfig as AgentConfig, SchedulerConfig
2
2
  from .worker import Worker
3
3
  from .config import load_scheduler_config, load_agent_config
4
4
  from .defaults import DEFAULT_ROLES
5
5
  from .session import Session, RuntimeSession
6
6
  from .manager import SessionManager
7
- from .reliability import ApoptosisManager
7
+ from .apoptosis import ApoptosisManager
8
8
 
9
9
  __all__ = [
10
10
  "RoleTemplate",
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+ from typing import Dict
3
+ from monoco.core.loader import FeatureModule, FeatureMetadata
4
+ from monoco.core.feature import IntegrationData
5
+
6
+
7
+ class AgentFeature(FeatureModule):
8
+ """Agent management feature module with unified lifecycle support."""
9
+
10
+ @property
11
+ def metadata(self) -> FeatureMetadata:
12
+ return FeatureMetadata(
13
+ name="agent",
14
+ version="1.0.0",
15
+ description="Agent session and role management",
16
+ dependencies=["core"],
17
+ priority=20,
18
+ )
19
+
20
+ def _on_mount(self, context: "FeatureContext") -> None: # type: ignore
21
+ """Agent feature doesn't require special initialization."""
22
+ pass
23
+
24
+ def integrate(self, root: Path, config: Dict) -> IntegrationData:
25
+ """Provide integration data for agent environment."""
26
+ # Determine language from config, default to 'en'
27
+ lang = config.get("i18n", {}).get("source_lang", "en")
28
+
29
+ # Resource path: monoco/features/agent/resources/{lang}/AGENTS.md
30
+ base_dir = Path(__file__).parent / "resources"
31
+
32
+ # Try specific language, fallback to 'en'
33
+ prompt_file = base_dir / lang / "AGENTS.md"
34
+ if not prompt_file.exists():
35
+ prompt_file = base_dir / "en" / "AGENTS.md"
36
+
37
+ content = ""
38
+ if prompt_file.exists():
39
+ content = prompt_file.read_text(encoding="utf-8").strip()
40
+
41
+ return IntegrationData(system_prompts={"Agent": content})
@@ -0,0 +1,44 @@
1
+ from .manager import SessionManager
2
+ from .models import RoleTemplate
3
+
4
+ class ApoptosisManager:
5
+ """
6
+ Manages the controlled shutdown (Apoptosis) and investigation (Autopsy)
7
+ of failed agent sessions.
8
+ """
9
+
10
+ def __init__(self, session_manager: SessionManager):
11
+ self.session_manager = session_manager
12
+
13
+ def trigger_apoptosis(self, session_id: str, failure_reason: str = "Unknown") -> None:
14
+ """
15
+ Trigger the apoptosis process for a given session.
16
+ 1. Mark session as crashed.
17
+ 2. Spin up a Coroner agent to diagnose.
18
+ """
19
+ session = self.session_manager.get_session(session_id)
20
+ if not session:
21
+ return
22
+
23
+ # 1. Mark as crashed
24
+ session.model.status = "crashed"
25
+
26
+ # 2. Start Coroner
27
+ self._perform_autopsy(session, failure_reason)
28
+
29
+ def _perform_autopsy(self, victim_session, failure_reason: str):
30
+ coroner_role = RoleTemplate(
31
+ name="Coroner",
32
+ description="Investigates cause of death for failed agents.",
33
+ trigger="system.crash",
34
+ goal=f"Determine why the previous agent failed. Reason: {failure_reason}",
35
+ system_prompt="You are the Coroner. Analyze the logs.",
36
+ engine="gemini"
37
+ )
38
+
39
+ coroner_session = self.session_manager.create_session(
40
+ issue_id=victim_session.model.issue_id,
41
+ role=coroner_role
42
+ )
43
+
44
+ coroner_session.start()
@@ -2,7 +2,7 @@ import typer
2
2
  import time
3
3
  from pathlib import Path
4
4
  from typing import Optional
5
- from monoco.core.output import print_output
5
+ from monoco.core.output import print_output, print_error
6
6
  from monoco.core.config import get_config
7
7
  from monoco.features.agent import SessionManager, load_scheduler_config
8
8
 
@@ -50,16 +50,20 @@ def list_providers():
50
50
  """
51
51
  from monoco.core.integrations import get_all_integrations
52
52
 
53
- settings = get_config()
54
53
  # Ideally we'd pass project-specific integrations here if they existed in config objects
55
54
  integrations = get_all_integrations(enabled_only=False)
56
55
 
57
56
  output = []
58
57
  for key, integration in integrations.items():
58
+ # Perform health check
59
+ health = integration.check_health()
60
+ status_icon = "✅" if health.available else "❌"
61
+
59
62
  output.append(
60
63
  {
61
64
  "key": key,
62
65
  "name": integration.name,
66
+ "status": status_icon,
63
67
  "binary": integration.bin_name or "-",
64
68
  "enabled": integration.enabled,
65
69
  "rules": integration.system_prompt_file,
@@ -95,87 +99,130 @@ def check_providers():
95
99
 
96
100
  @app.command()
97
101
  def run(
98
- target: Optional[str] = typer.Argument(
99
- None, help="Issue ID (e.g. FEAT-101) or a Task Description in quotes."
100
- ),
101
- role: Optional[str] = typer.Option(
102
- None,
103
- help="Specific role to use (Planner/Builder/Reviewer). Default: intelligent selection.",
102
+ prompt: Optional[list[str]] = typer.Argument(None, help="Instructions for the agent (e.g. 'Fix the bug')."),
103
+ issue: Optional[str] = typer.Option(
104
+ None, "--issue", "-i", help="Link to a specific Issue ID (e.g. FEAT-101)."
104
105
  ),
105
- type: str = typer.Option(
106
- "feature", "--type", "-t", help="Issue type for new tasks (feature/chore/fix)."
106
+ role: str = typer.Option(
107
+ "Default", "--role", "-r", help="Specific role to use."
107
108
  ),
108
109
  detach: bool = typer.Option(
109
110
  False, "--detach", "-d", help="Run in background (Daemon)"
110
111
  ),
111
- fail: bool = typer.Option(
112
- False, "--fail", help="Simulate a crash for testing Apoptosis."
112
+ provider: Optional[str] = typer.Option(
113
+ None, "--provider", "-p", help="Override the default engine/provider for this session."
113
114
  ),
114
115
  ):
115
116
  """
116
117
  Start an agent session.
117
118
 
118
- If TARGET is an Issue ID, it starts a session for that issue.
119
- If TARGET is a description, it starts a 'Planner' session to draft a new issue.
119
+ Usage:
120
+ monoco agent run "Check memos"
121
+ monoco agent run -i FEAT-101 "Implement feature"
120
122
  """
121
- from monoco.core.output import print_error
122
-
123
- # Normal run mode - target is required
124
- if not target:
125
- from monoco.core.output import print_error
126
-
127
- print_error("TARGET (Issue ID or Task Description) is required.")
128
- raise typer.Exit(code=1)
129
- raise typer.Exit(code=1)
130
-
131
- # 1. Smart Intent Recognition
132
- import re
133
-
134
- is_id = re.match(r"^[a-zA-Z]+-\d+$", target)
135
-
136
- if is_id:
137
- issue_id = target.upper()
138
- role_name = role or "Builder"
139
- description = None
123
+ settings = get_config()
124
+ project_root = Path(settings.paths.root).resolve()
125
+
126
+ # 1. Resolve Inputs
127
+ full_prompt = " ".join(prompt) if prompt else ""
128
+
129
+ if issue:
130
+ # User explicitly linked an issue
131
+ issue_id = issue.upper()
132
+ description = full_prompt or None
140
133
  else:
141
- # Implicit Draft Mode via run command (target is description)
134
+ # Ad-hoc task check
135
+ import re
136
+ # Heuristic: if prompt looks like an ID and is short, maybe they meant ID?
137
+ # But explicit is better. Let's assume everything in prompt is instructions.
142
138
  issue_id = "NEW_TASK"
143
- role_name = role or "Planner"
144
- description = target
139
+ description = full_prompt
140
+
141
+ if not description and not issue:
142
+ print_error("Please provide either a PROMPT or an --issue ID.")
143
+ raise typer.Exit(code=1)
145
144
 
146
145
  # 2. Load Roles
147
146
  roles = load_scheduler_config(project_root)
148
- selected_role = roles.get(role_name)
147
+ selected_role = roles.get(role)
149
148
 
150
149
  if not selected_role:
151
- print_error(f"Role '{role_name}' not found. Available: {list(roles.keys())}")
150
+ print_error(f"Role '{role}' not found. Available: {list(roles.keys())}")
152
151
  raise typer.Exit(code=1)
153
152
 
153
+ # 3. Provider Override & Fallback Logic
154
+ target_engine = provider or selected_role.engine
155
+ from monoco.core.integrations import get_integration, get_all_integrations
156
+
157
+ integration = get_integration(target_engine)
158
+
159
+ # If integration is found, check health
160
+ is_available = False
161
+ if integration:
162
+ health = integration.check_health()
163
+ is_available = health.available
164
+ if not is_available and provider:
165
+ # If user explicitly requested this provider, fail hard
166
+ print_error(f"Requested provider '{target_engine}' is not available.")
167
+ print_error(f"Error: {health.error}")
168
+ raise typer.Exit(code=1)
169
+
170
+ # Auto-fallback if default provider is unavailable
171
+ if not is_available:
172
+ print_output(f"⚠️ Provider '{target_engine}' is not available. Searching for fallback...", style="yellow")
173
+
174
+ all_integrations = get_all_integrations(enabled_only=True)
175
+ fallback_found = None
176
+
177
+ # Priority list for fallback
178
+ priority = ["cursor", "claude", "gemini", "qwen", "kimi"]
179
+
180
+ # Try priority matches first
181
+ for key in priority:
182
+ if key in all_integrations:
183
+ if all_integrations[key].check_health().available:
184
+ fallback_found = key
185
+ break
186
+
187
+ # Determine strict fallback
188
+ if fallback_found:
189
+ print_output(f"🔄 Falling back to available provider: [bold green]{fallback_found}[/bold green]")
190
+ selected_role.engine = fallback_found
191
+ else:
192
+ # If NO CLI tools available, maybe generic agent?
193
+ if "agent" in all_integrations:
194
+ print_output("🔄 Falling back to Generic Agent (No CLI execution).", style="yellow")
195
+ selected_role.engine = "agent"
196
+ else:
197
+ print_error("❌ No available agent providers found on this system.")
198
+ print_error("Please install Cursor, Claude Code, or Gemini CLI.")
199
+ raise typer.Exit(code=1)
200
+ elif provider:
201
+ # If available and user overrode it
202
+ print_output(f"Overriding provider: {selected_role.engine} -> {provider}")
203
+ selected_role.engine = provider
204
+
205
+ display_target = issue if issue else (full_prompt[:50] + "..." if len(full_prompt) > 50 else full_prompt)
154
206
  print_output(
155
- f"Starting Agent Session for '{target}' as {role_name}...",
156
- title="Agent Scheduler",
207
+ f"Starting Agent Session for '{display_target}' as {role} (via {selected_role.engine})...",
208
+ title="Agent Framework",
157
209
  )
158
210
 
159
- # 3. Initialize Session
211
+ # 4. Initialize Session
160
212
  manager = SessionManager()
161
213
  session = manager.create_session(issue_id, selected_role)
162
214
 
163
- if detach:
164
- print_output(
165
- "Background mode not fully implemented yet. Running in foreground."
166
- )
167
215
 
168
216
  try:
169
217
  # Pass description if it's a new task
170
218
  context = {"description": description} if description else None
219
+ session.start(context=context)
171
220
 
172
- if fail:
173
- from monoco.core.output import rprint
174
-
175
- rprint("[bold yellow]DEBUG: Simulating immediate crash...[/bold yellow]")
176
- session.model.status = "failed"
177
- else:
178
- session.start(context=context)
221
+ if detach:
222
+ print_output(
223
+ f"Session {session.model.id} started in background (detached)."
224
+ )
225
+ return
179
226
 
180
227
  # Monitoring Loop
181
228
  while session.refresh_status() == "running":
@@ -183,12 +230,12 @@ def run(
183
230
 
184
231
  if session.model.status == "failed":
185
232
  print_error(
186
- f"Session {session.model.id} FAILED. Use 'monoco agent session autopsy {session.model.id}' for analysis."
233
+ f"Session {session.model.id} FAILED. Review logs for details."
187
234
  )
188
235
  else:
189
236
  print_output(
190
237
  f"Session finished with status: {session.model.status}",
191
- title="Agent Scheduler",
238
+ title="Agent Framework",
192
239
  )
193
240
 
194
241
  except KeyboardInterrupt:
@@ -211,96 +258,6 @@ def kill_session(session_id: str):
211
258
  print_output(f"Session {session_id} not found.", style="red")
212
259
 
213
260
 
214
- @session_app.command(name="autopsy")
215
- def autopsy_command(target: str):
216
- """Execute Post-Mortem analysis on a failed session or target Issue."""
217
- _run_autopsy(target)
218
-
219
-
220
- def _run_autopsy(target: str):
221
- """Execute Post-Mortem analysis on a failed session or target Issue."""
222
- from .reliability import ApoptosisManager
223
- from monoco.core.output import print_error
224
-
225
- manager = SessionManager()
226
-
227
- print_output(f"Initiating Autopsy for '{target}'...", title="Coroner")
228
-
229
- # Try to find session
230
- session = manager.get_session(target)
231
- if not session:
232
- # Fallback: Treat target as Issue ID and create a dummy failed session context
233
- import re
234
-
235
- if re.match(r"^[a-zA-Z]+-\d+$", target):
236
- print_output(f"Session not in memory. Analyzing Issue {target} directly.")
237
- # We create a transient session just to trigger the coroner
238
- settings = get_config()
239
- project_root = Path(settings.paths.root).resolve()
240
- roles = load_scheduler_config(project_root)
241
- builder_role = roles.get("Builder")
242
-
243
- if not builder_role:
244
- print_error("Builder role not found.")
245
- raise typer.Exit(code=1)
246
-
247
- session = manager.create_session(target.upper(), builder_role)
248
- session.model.status = "failed"
249
- else:
250
- print_error(f"Could not find session or valid Issue ID for '{target}'")
251
- raise typer.Exit(code=1)
252
-
253
- apoptosis = ApoptosisManager(manager)
254
- apoptosis.trigger_apoptosis(session.model.id)
255
-
256
-
257
- def _run_draft(desc: str, type: str, detach: bool):
258
- """Draft a new issue based on a natural language description."""
259
- from monoco.core.output import print_error
260
-
261
- settings = get_config()
262
- project_root = Path(settings.paths.root).resolve()
263
-
264
- # Load Roles
265
- roles = load_scheduler_config(project_root)
266
- # Use 'Planner' as the role for drafting (it handles new tasks)
267
- role_name = "Planner"
268
- selected_role = roles.get(role_name)
269
-
270
- if not selected_role:
271
- print_error(f"Role '{role_name}' not found.")
272
- raise typer.Exit(code=1)
273
-
274
- print_output(
275
- f"Drafting {type} from description: '{desc}'",
276
- title="Agent Drafter",
277
- )
278
-
279
- manager = SessionManager()
280
- # We use a placeholder ID as we don't know the ID yet.
281
- # The agent is expected to create the file, so the ID will be generated then.
282
- session = manager.create_session("NEW_TASK", selected_role)
283
-
284
- context = {"description": desc, "type": type}
285
-
286
- try:
287
- session.start(context=context)
288
-
289
- # Monitoring Loop
290
- while session.refresh_status() == "running":
291
- time.sleep(1)
292
-
293
- if session.model.status == "failed":
294
- print_error("Drafting failed.")
295
- else:
296
- print_output("Drafting completed.", title="Agent Drafter")
297
-
298
- except KeyboardInterrupt:
299
- print("\nStopping...")
300
- session.terminate()
301
- print_output("Drafting cancelled.")
302
-
303
-
304
261
  @session_app.command(name="list")
305
262
  def list_sessions():
306
263
  """
@@ -1,16 +1,16 @@
1
1
  from typing import Dict, Optional
2
2
  import yaml
3
3
  from pathlib import Path
4
- from .models import RoleTemplate, AgentConfig
4
+ from .models import RoleTemplate, AgentRoleConfig as AgentConfig
5
5
  from .defaults import DEFAULT_ROLES
6
6
 
7
7
 
8
8
  class RoleLoader:
9
9
  """
10
10
  Tiered configuration loader for Agent Roles.
11
- Level 1: Builtin Fallback
12
- Level 2: Global (~/.monoco/roles.yaml)
13
- Level 3: Project (./.monoco/roles.yaml)
11
+ Level 1: Builtin Resources (monoco/features/agent/resources/roles/*.yaml)
12
+ Level 2: Global (~/.monoco/roles/*.yaml)
13
+ Level 3: Project (./.monoco/roles/*.yaml)
14
14
  """
15
15
 
16
16
  def __init__(self, project_root: Optional[Path] = None):
@@ -20,23 +20,33 @@ class RoleLoader:
20
20
  self.sources: Dict[str, str] = {} # role_name -> source description
21
21
 
22
22
  def load_all(self) -> Dict[str, RoleTemplate]:
23
- # Level 1: Defaults
23
+ # Level 1: Defaults (Hardcoded)
24
24
  for role in DEFAULT_ROLES:
25
- self.roles[role.name] = role
26
- self.sources[role.name] = "builtin"
25
+ if role.name not in self.roles:
26
+ self.roles[role.name] = role
27
+ self.sources[role.name] = "builtin (default)"
27
28
 
28
29
  # Level 2: Global
29
- global_path = self.user_home / ".monoco" / "roles.yaml"
30
- self._load_from_path(global_path, "global")
30
+ global_monoco = self.user_home / ".monoco"
31
+ self._load_from_file(global_monoco / "roles.yaml", "global")
32
+ self._load_from_dir(global_monoco / "roles", "global")
31
33
 
32
34
  # Level 3: Project
33
35
  if self.project_root:
34
- project_path = self.project_root / ".monoco" / "roles.yaml"
35
- self._load_from_path(project_path, "project")
36
+ project_monoco = self.project_root / ".monoco"
37
+ self._load_from_file(project_monoco / "roles.yaml", "project")
38
+ self._load_from_dir(project_monoco / "roles", "project")
36
39
 
37
40
  return self.roles
38
41
 
39
- def _load_from_path(self, path: Path, source_label: str):
42
+ def _load_from_dir(self, directory: Path, source_label: str):
43
+ if not directory.exists() or not directory.is_dir():
44
+ return
45
+
46
+ for file in directory.glob("*.yaml"):
47
+ self._load_from_file(file, source_label)
48
+
49
+ def _load_from_file(self, path: Path, source_label: str):
40
50
  if not path.exists():
41
51
  return
42
52
 
@@ -44,21 +54,25 @@ class RoleLoader:
44
54
  with open(path, "r") as f:
45
55
  data = yaml.safe_load(f) or {}
46
56
 
47
- if "roles" in data:
48
- # Validate using AgentConfig
57
+ # Case A: Config object with "roles" list
58
+ if "roles" in data and isinstance(data["roles"], list):
49
59
  config = AgentConfig(roles=data["roles"])
50
60
  for role in config.roles:
51
- # Level 3 > Level 2 > Level 1 (名字相同的 Role 进行覆盖/Merge)
52
- # Currently we do total replacement for same-named roles
53
- self.roles[role.name] = role
54
- self.sources[role.name] = str(path)
61
+ self._upsert_role(role, str(path))
62
+
63
+ # Case B: Single Role object
64
+ elif "name" in data and "system_prompt" in data:
65
+ role = RoleTemplate(**data)
66
+ self._upsert_role(role, str(path))
67
+
55
68
  except Exception as e:
56
- # We don't want to crash the whole tool if a config is malformed,
57
- # but we should probably warn.
58
69
  import sys
59
-
60
70
  print(f"Warning: Failed to load roles from {path}: {e}", file=sys.stderr)
61
71
 
72
+ def _upsert_role(self, role: RoleTemplate, source: str):
73
+ self.roles[role.name] = role
74
+ self.sources[role.name] = source
75
+
62
76
 
63
77
  def load_scheduler_config(project_root: Path) -> Dict[str, RoleTemplate]:
64
78
  """
@@ -2,54 +2,11 @@ from .models import RoleTemplate
2
2
 
3
3
  DEFAULT_ROLES = [
4
4
  RoleTemplate(
5
- name="Planner",
6
- description="Responsible for requirement analysis, design documents, and issue drafting.",
7
- trigger="task.received",
8
- goal="Produce a structured design document or issue ticket.",
9
- system_prompt=(
10
- "You are a Planner agent. Your goal is to transform requirements into structured plans.\n"
11
- "Analyze the workspace context and produce detailed issue tickets or design documents."
12
- ),
5
+ name="Default",
6
+ description="A generic, jack-of-all-trades agent used when no specific role is configured.",
7
+ trigger="task.dispatch",
8
+ goal="Complete the assigned task.",
9
+ system_prompt="You are a helpful Agent. Complete the task assigned to you.",
13
10
  engine="gemini",
14
- ),
15
- RoleTemplate(
16
- name="Builder",
17
- description="Responsible for implementing code and tests based on the design.",
18
- trigger="design.approved",
19
- goal="Implement functional code and passing tests.",
20
- system_prompt="You are a Builder agent. Your job is to implement the solution as specified in the issue.",
21
- engine="gemini",
22
- ),
23
- RoleTemplate(
24
- name="Reviewer",
25
- description="Responsible for code quality, architectural consistency, and linting.",
26
- trigger="implementation.submitted",
27
- goal="Provide critical feedback and approve or reject submissions.",
28
- system_prompt="You are a Reviewer agent. Analyze code changes for bugs, style, and correctness.",
29
- engine="gemini",
30
- ),
31
- RoleTemplate(
32
- name="Merger",
33
- description="Responsible for branch management and merging verified changes into trunk.",
34
- trigger="review.passed",
35
- goal="Safely merge feature branches and prune resources.",
36
- system_prompt="You are a Merger agent. Handle the final integration of features and clean up environment.",
37
- engine="gemini",
38
- ),
39
- RoleTemplate(
40
- name="Coroner",
41
- description="Responsible for post-mortem analysis and root cause identification on failure.",
42
- trigger="session.crashed",
43
- goal="Generate a detailed autopsy report.",
44
- system_prompt="You are a Coroner agent. Analyze the failure state and generate a ## Post-mortem report.",
45
- engine="gemini",
46
- ),
47
- RoleTemplate(
48
- name="Manager",
49
- description="The central orchestrator that processes memos and schedules other roles.",
50
- trigger="daily.check / memo.added",
51
- goal="Orchestrate the development workflow and manage team priorities.",
52
- system_prompt="You are the Manager agent. You act as the scheduler and coordinator for the entire agentic system.",
53
- engine="gemini",
54
- ),
11
+ )
55
12
  ]
@@ -48,12 +48,13 @@ class GeminiAdapter(EngineAdapter):
48
48
  """
49
49
  Adapter for Google Gemini CLI.
50
50
 
51
- Command format: gemini -y <prompt>
51
+ Command format: gemini -p <prompt> -y
52
52
  The -y flag enables "YOLO mode" (auto-approval of actions).
53
53
  """
54
54
 
55
55
  def build_command(self, prompt: str) -> List[str]:
56
- return ["gemini", "-y", prompt]
56
+ # Based on Gemini CLI help: -p <prompt> for non-interactive
57
+ return ["gemini", "-p", prompt, "-y"]
57
58
 
58
59
  @property
59
60
  def name(self) -> str:
@@ -69,10 +70,12 @@ class ClaudeAdapter(EngineAdapter):
69
70
  Adapter for Anthropic Claude CLI.
70
71
 
71
72
  Command format: claude -p <prompt>
72
- The -p/--print flag enables non-interactive mode (print response and exit).
73
+ The -p/--print flag enables non-interactive mode.
73
74
  """
74
75
 
75
76
  def build_command(self, prompt: str) -> List[str]:
77
+ # Based on Claude CLI help: -p <prompt> is NOT standard, usually -p means print/non-interactive.
78
+ # But for one-shot execution, we do passing prompt as argument with -p flag.
76
79
  return ["claude", "-p", prompt]
77
80
 
78
81
  @property
@@ -89,12 +92,12 @@ class QwenAdapter(EngineAdapter):
89
92
  """
90
93
  Adapter for Qwen Code CLI.
91
94
 
92
- Command format: qwen -y <prompt>
93
- The -y flag enables "YOLO mode" (auto-approval of actions).
95
+ Command format: qwen -p <prompt> -y
94
96
  """
95
97
 
96
98
  def build_command(self, prompt: str) -> List[str]:
97
- return ["qwen", "-y", prompt]
99
+ # Assuming Qwen follows similar patterns (based on user feedback)
100
+ return ["qwen", "-p", prompt, "-y"]
98
101
 
99
102
  @property
100
103
  def name(self) -> str:
@@ -105,6 +108,28 @@ class QwenAdapter(EngineAdapter):
105
108
  return True
106
109
 
107
110
 
111
+ class KimiAdapter(EngineAdapter):
112
+ """
113
+ Adapter for Kimi CLI (Moonshot AI).
114
+
115
+ Command format: kimi -p <prompt> --print
116
+ Note: --print implicitly adds --yolo.
117
+ """
118
+
119
+ def build_command(self, prompt: str) -> List[str]:
120
+ # Based on Kimi CLI help: -p, --prompt TEXT.
121
+ # Also using --print for non-interactive mode (which enables yolo).
122
+ return ["kimi", "-p", prompt, "--print"]
123
+
124
+ @property
125
+ def name(self) -> str:
126
+ return "kimi"
127
+
128
+ @property
129
+ def supports_yolo_mode(self) -> bool:
130
+ return True
131
+
132
+
108
133
  class EngineFactory:
109
134
  """
110
135
  Factory for creating engine adapter instances.
@@ -118,6 +143,7 @@ class EngineFactory:
118
143
  "gemini": GeminiAdapter,
119
144
  "claude": ClaudeAdapter,
120
145
  "qwen": QwenAdapter,
146
+ "kimi": KimiAdapter,
121
147
  }
122
148
 
123
149
  @classmethod