monoco-toolkit 0.3.9__py3-none-any.whl → 0.3.10__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/config.py +7 -0
- monoco/core/hooks/builtin/git_cleanup.py +1 -1
- monoco/core/injection.py +63 -29
- monoco/core/integrations.py +2 -2
- monoco/core/output.py +5 -5
- monoco/core/registry.py +7 -1
- monoco/core/resource/__init__.py +5 -0
- monoco/core/resource/finder.py +98 -0
- monoco/core/resource/manager.py +91 -0
- monoco/core/resource/models.py +35 -0
- monoco/core/resources/en/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
- monoco/core/resources/zh/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
- monoco/core/skill_framework.py +292 -0
- monoco/core/skills.py +471 -371
- monoco/core/sync.py +73 -1
- monoco/core/workflow_converter.py +420 -0
- monoco/features/agent/__init__.py +2 -2
- monoco/features/agent/adapter.py +31 -0
- monoco/features/agent/apoptosis.py +44 -0
- monoco/features/agent/cli.py +101 -144
- monoco/features/agent/config.py +35 -21
- monoco/features/agent/defaults.py +6 -49
- monoco/features/agent/engines.py +32 -6
- monoco/features/agent/manager.py +6 -1
- monoco/features/agent/models.py +2 -2
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
- monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
- monoco/features/agent/resources/en/skills/flow_engineer/SKILL.md +94 -0
- monoco/features/agent/resources/en/skills/flow_manager/SKILL.md +93 -0
- monoco/features/agent/resources/en/skills/flow_planner/SKILL.md +85 -0
- monoco/features/agent/resources/en/skills/flow_reviewer/SKILL.md +114 -0
- monoco/features/agent/resources/roles/role-engineer.yaml +49 -0
- monoco/features/agent/resources/roles/role-manager.yaml +46 -0
- monoco/features/agent/resources/roles/role-planner.yaml +46 -0
- monoco/features/agent/resources/roles/role-reviewer.yaml +47 -0
- monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
- monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
- monoco/features/agent/resources/zh/skills/flow_planner/SKILL.md +259 -0
- monoco/features/agent/resources/zh/skills/flow_reviewer/SKILL.md +137 -0
- monoco/features/agent/worker.py +38 -2
- monoco/features/glossary/__init__.py +0 -0
- monoco/features/glossary/adapter.py +31 -0
- monoco/features/glossary/config.py +5 -0
- monoco/features/glossary/resources/en/AGENTS.md +29 -0
- monoco/features/glossary/resources/en/skills/monoco_glossary/SKILL.md +35 -0
- monoco/features/glossary/resources/zh/AGENTS.md +29 -0
- monoco/features/glossary/resources/zh/skills/monoco_glossary/SKILL.md +35 -0
- monoco/features/i18n/resources/en/skills/i18n_scan_workflow/SKILL.md +105 -0
- monoco/features/i18n/resources/en/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
- monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
- monoco/features/issue/core.py +45 -6
- monoco/features/issue/engine/machine.py +5 -2
- monoco/features/issue/models.py +1 -0
- monoco/features/issue/resources/en/skills/issue_create_workflow/SKILL.md +167 -0
- monoco/features/issue/resources/en/skills/issue_develop_workflow/SKILL.md +224 -0
- monoco/features/issue/resources/en/skills/issue_lifecycle_workflow/SKILL.md +159 -0
- monoco/features/issue/resources/en/skills/issue_refine_workflow/SKILL.md +203 -0
- monoco/features/issue/resources/en/{SKILL.md → skills/monoco_issue/SKILL.md} +2 -0
- monoco/features/issue/resources/zh/skills/issue_create_workflow/SKILL.md +167 -0
- monoco/features/issue/resources/zh/skills/issue_develop_workflow/SKILL.md +224 -0
- monoco/features/issue/resources/zh/skills/issue_refine_workflow/SKILL.md +203 -0
- monoco/features/issue/resources/zh/{SKILL.md → skills/monoco_issue/SKILL.md} +2 -0
- monoco/features/memo/resources/en/skills/monoco_memo/SKILL.md +77 -0
- monoco/features/memo/resources/en/skills/note_processing_workflow/SKILL.md +140 -0
- monoco/features/memo/resources/zh/{SKILL.md → skills/monoco_memo/SKILL.md} +2 -0
- monoco/features/spike/resources/en/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
- monoco/features/spike/resources/en/skills/research_workflow/SKILL.md +121 -0
- monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
- monoco_toolkit-0.3.10.dist-info/METADATA +124 -0
- monoco_toolkit-0.3.10.dist-info/RECORD +156 -0
- monoco/features/agent/reliability.py +0 -106
- monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +0 -114
- monoco_toolkit-0.3.9.dist-info/METADATA +0 -127
- monoco_toolkit-0.3.9.dist-info/RECORD +0 -115
- /monoco/features/agent/resources/{skills → zh/skills}/flow_engineer/SKILL.md +0 -0
- /monoco/features/agent/resources/{skills → zh/skills}/flow_manager/SKILL.md +0 -0
- /monoco/features/i18n/resources/{skills → zh/skills}/i18n_scan_workflow/SKILL.md +0 -0
- /monoco/features/issue/resources/{skills → zh/skills}/issue_lifecycle_workflow/SKILL.md +0 -0
- /monoco/features/memo/resources/{skills → zh/skills}/note_processing_workflow/SKILL.md +0 -0
- /monoco/features/spike/resources/{skills → zh/skills}/research_workflow/SKILL.md +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.10.dist-info}/licenses/LICENSE +0 -0
monoco/features/agent/cli.py
CHANGED
|
@@ -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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
106
|
-
"
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
|
|
119
|
+
Usage:
|
|
120
|
+
monoco agent run "Check memos"
|
|
121
|
+
monoco agent run -i FEAT-101 "Implement feature"
|
|
120
122
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
144
|
-
|
|
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(
|
|
147
|
+
selected_role = roles.get(role)
|
|
149
148
|
|
|
150
149
|
if not selected_role:
|
|
151
|
-
print_error(f"Role '{
|
|
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 '{
|
|
156
|
-
title="Agent
|
|
207
|
+
f"Starting Agent Session for '{display_target}' as {role} (via {selected_role.engine})...",
|
|
208
|
+
title="Agent Framework",
|
|
157
209
|
)
|
|
158
210
|
|
|
159
|
-
#
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
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
|
|
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
|
"""
|
monoco/features/agent/config.py
CHANGED
|
@@ -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
|
|
12
|
-
Level 2: Global (~/.monoco/roles
|
|
13
|
-
Level 3: Project (./.monoco/roles
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
self.
|
|
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
|
-
|
|
35
|
-
self.
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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="
|
|
6
|
-
description="
|
|
7
|
-
trigger="task.
|
|
8
|
-
goal="
|
|
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
|
]
|
monoco/features/agent/engines.py
CHANGED
|
@@ -48,12 +48,13 @@ class GeminiAdapter(EngineAdapter):
|
|
|
48
48
|
"""
|
|
49
49
|
Adapter for Google Gemini CLI.
|
|
50
50
|
|
|
51
|
-
Command format: gemini -
|
|
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
|
-
|
|
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
|
|
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 -
|
|
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
|
-
|
|
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
|
monoco/features/agent/manager.py
CHANGED
|
@@ -60,7 +60,12 @@ class SessionManager:
|
|
|
60
60
|
branch_name=branch_name,
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
# Get timeout from config
|
|
64
|
+
timeout = 900
|
|
65
|
+
if self.config and hasattr(self.config, "agent"):
|
|
66
|
+
timeout = self.config.agent.timeout_seconds
|
|
67
|
+
|
|
68
|
+
worker = Worker(role, issue_id, timeout=timeout)
|
|
64
69
|
runtime = RuntimeSession(
|
|
65
70
|
session_model,
|
|
66
71
|
worker,
|
monoco/features/agent/models.py
CHANGED
|
@@ -19,9 +19,9 @@ class RoleTemplate(BaseModel):
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class
|
|
22
|
+
class AgentRoleConfig(BaseModel):
|
|
23
23
|
roles: List[RoleTemplate] = Field(default_factory=list)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
# Backward compatibility alias
|
|
27
|
-
SchedulerConfig =
|
|
27
|
+
SchedulerConfig = AgentRoleConfig
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atom-code-dev
|
|
3
|
+
type: atom
|
|
4
|
+
domain: code
|
|
5
|
+
description: 代码开发的原子操作 - 调研、实现、测试、文档
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
author: Monoco Toolkit
|
|
8
|
+
|
|
9
|
+
# 系统级合规规则
|
|
10
|
+
compliance_rules:
|
|
11
|
+
- rule: "遵循项目代码规范"
|
|
12
|
+
severity: warning
|
|
13
|
+
|
|
14
|
+
- rule: "优先编写测试"
|
|
15
|
+
severity: warning
|
|
16
|
+
mindset: "TDD"
|
|
17
|
+
|
|
18
|
+
- rule: "保持代码简单直观"
|
|
19
|
+
severity: info
|
|
20
|
+
mindset: "KISS"
|
|
21
|
+
|
|
22
|
+
# 原子操作定义
|
|
23
|
+
operations:
|
|
24
|
+
- name: investigate
|
|
25
|
+
description: 理解需求与上下文,识别相关代码和依赖
|
|
26
|
+
reminder: "理解需求后再开始编码,识别相关文件和依赖 Issue"
|
|
27
|
+
checkpoints:
|
|
28
|
+
- "阅读并理解 Issue 描述"
|
|
29
|
+
- "识别相关代码文件"
|
|
30
|
+
- "检查依赖 Issue 状态"
|
|
31
|
+
- "评估技术可行性"
|
|
32
|
+
output: "技术方案草稿、风险清单"
|
|
33
|
+
|
|
34
|
+
- name: implement
|
|
35
|
+
description: 实现代码变更
|
|
36
|
+
reminder: "遵循项目代码规范,小步提交,处理边界情况"
|
|
37
|
+
checkpoints:
|
|
38
|
+
- "遵循项目代码规范"
|
|
39
|
+
- "编写/更新必要的文档"
|
|
40
|
+
- "处理边界情况"
|
|
41
|
+
- "保持代码可审查性(单次提交 < 400 行)"
|
|
42
|
+
|
|
43
|
+
- name: test
|
|
44
|
+
description: 运行测试确保代码质量
|
|
45
|
+
reminder: "所有测试必须通过,检查测试覆盖率"
|
|
46
|
+
checkpoints:
|
|
47
|
+
- "编写/更新单元测试"
|
|
48
|
+
- "运行测试套件 (pytest, cargo test, etc.)"
|
|
49
|
+
- "修复失败的测试"
|
|
50
|
+
- "检查测试覆盖率"
|
|
51
|
+
compliance_rules:
|
|
52
|
+
- rule: "所有单元测试必须通过后才能提交"
|
|
53
|
+
severity: error
|
|
54
|
+
|
|
55
|
+
- name: document
|
|
56
|
+
description: 更新文档
|
|
57
|
+
reminder: "代码变更必须同步更新文档"
|
|
58
|
+
checkpoints:
|
|
59
|
+
- "更新代码注释"
|
|
60
|
+
- "更新相关文档"
|
|
61
|
+
- "更新 CHANGELOG(如适用)"
|