monoco-toolkit 0.3.6__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/cli/workspace.py +1 -1
- monoco/core/config.py +58 -0
- monoco/core/hooks/__init__.py +19 -0
- monoco/core/hooks/base.py +104 -0
- monoco/core/hooks/builtin/__init__.py +11 -0
- monoco/core/hooks/builtin/git_cleanup.py +266 -0
- monoco/core/hooks/builtin/logging_hook.py +78 -0
- monoco/core/hooks/context.py +131 -0
- monoco/core/hooks/registry.py +222 -0
- monoco/core/injection.py +63 -29
- monoco/core/integrations.py +8 -2
- monoco/core/output.py +5 -5
- monoco/core/registry.py +9 -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/setup.py +1 -1
- monoco/core/skill_framework.py +292 -0
- monoco/core/skills.py +538 -254
- monoco/core/sync.py +73 -1
- monoco/core/workflow_converter.py +420 -0
- monoco/features/{scheduler → agent}/__init__.py +5 -3
- monoco/features/agent/adapter.py +31 -0
- monoco/features/agent/apoptosis.py +44 -0
- monoco/features/agent/cli.py +296 -0
- monoco/features/agent/config.py +96 -0
- monoco/features/agent/defaults.py +12 -0
- monoco/features/{scheduler → agent}/engines.py +32 -6
- monoco/features/agent/flow_skills.py +281 -0
- monoco/features/agent/manager.py +91 -0
- monoco/features/{scheduler → agent}/models.py +6 -3
- 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_engineer/SKILL.md +94 -0
- monoco/features/agent/resources/zh/skills/flow_manager/SKILL.md +88 -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/{scheduler → agent}/session.py +36 -1
- monoco/features/{scheduler → agent}/worker.py +40 -4
- 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/skills/i18n_scan_workflow/SKILL.md +105 -0
- monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
- monoco/features/issue/commands.py +427 -21
- monoco/features/issue/core.py +140 -1
- monoco/features/issue/criticality.py +553 -0
- monoco/features/issue/domain/models.py +28 -2
- monoco/features/issue/engine/machine.py +75 -15
- monoco/features/issue/git_service.py +185 -0
- monoco/features/issue/linter.py +291 -62
- monoco/features/issue/models.py +50 -2
- 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} +50 -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_lifecycle_workflow/SKILL.md +159 -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} +52 -0
- monoco/features/issue/validator.py +185 -65
- monoco/features/memo/__init__.py +2 -1
- monoco/features/memo/adapter.py +32 -0
- monoco/features/memo/cli.py +36 -14
- monoco/features/memo/core.py +59 -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/AGENTS.md +8 -0
- monoco/features/memo/resources/zh/skills/monoco_memo/SKILL.md +77 -0
- monoco/features/memo/resources/zh/skills/note_processing_workflow/SKILL.md +140 -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/features/spike/resources/zh/skills/research_workflow/SKILL.md +121 -0
- monoco/main.py +2 -3
- monoco_toolkit-0.3.10.dist-info/METADATA +124 -0
- monoco_toolkit-0.3.10.dist-info/RECORD +156 -0
- monoco/features/scheduler/cli.py +0 -285
- monoco/features/scheduler/config.py +0 -68
- monoco/features/scheduler/defaults.py +0 -54
- monoco/features/scheduler/manager.py +0 -49
- monoco/features/scheduler/reliability.py +0 -106
- monoco/features/skills/core.py +0 -102
- monoco_toolkit-0.3.6.dist-info/METADATA +0 -127
- monoco_toolkit-0.3.6.dist-info/RECORD +0 -97
- /monoco/core/{hooks.py → githooks.py} +0 -0
- /monoco/features/{skills → glossary}/__init__.py +0 -0
- {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from monoco.core.output import print_output, print_error
|
|
6
|
+
from monoco.core.config import get_config
|
|
7
|
+
from monoco.features.agent import SessionManager, load_scheduler_config
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(name="agent", help="Manage agent sessions and roles")
|
|
10
|
+
session_app = typer.Typer(name="session", help="Manage active agent sessions")
|
|
11
|
+
role_app = typer.Typer(name="role", help="Manage agent roles (CRUD)")
|
|
12
|
+
provider_app = typer.Typer(name="provider", help="Manage agent providers (Engines)")
|
|
13
|
+
|
|
14
|
+
app.add_typer(session_app, name="session")
|
|
15
|
+
app.add_typer(role_app, name="role")
|
|
16
|
+
app.add_typer(provider_app, name="provider")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@role_app.command(name="list")
|
|
20
|
+
def list_roles():
|
|
21
|
+
"""
|
|
22
|
+
List available agent roles and their sources.
|
|
23
|
+
"""
|
|
24
|
+
from monoco.features.agent.config import RoleLoader
|
|
25
|
+
|
|
26
|
+
settings = get_config()
|
|
27
|
+
project_root = Path(settings.paths.root).resolve()
|
|
28
|
+
|
|
29
|
+
loader = RoleLoader(project_root)
|
|
30
|
+
roles = loader.load_all()
|
|
31
|
+
|
|
32
|
+
output = []
|
|
33
|
+
for name, role in roles.items():
|
|
34
|
+
output.append(
|
|
35
|
+
{
|
|
36
|
+
"role": name,
|
|
37
|
+
"engine": role.engine,
|
|
38
|
+
"source": loader.sources.get(name, "unknown"),
|
|
39
|
+
"description": role.description,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
print_output(output, title="Agent Roles")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@provider_app.command(name="list")
|
|
47
|
+
def list_providers():
|
|
48
|
+
"""
|
|
49
|
+
List available agent providers and their status.
|
|
50
|
+
"""
|
|
51
|
+
from monoco.core.integrations import get_all_integrations
|
|
52
|
+
|
|
53
|
+
# Ideally we'd pass project-specific integrations here if they existed in config objects
|
|
54
|
+
integrations = get_all_integrations(enabled_only=False)
|
|
55
|
+
|
|
56
|
+
output = []
|
|
57
|
+
for key, integration in integrations.items():
|
|
58
|
+
# Perform health check
|
|
59
|
+
health = integration.check_health()
|
|
60
|
+
status_icon = "✅" if health.available else "❌"
|
|
61
|
+
|
|
62
|
+
output.append(
|
|
63
|
+
{
|
|
64
|
+
"key": key,
|
|
65
|
+
"name": integration.name,
|
|
66
|
+
"status": status_icon,
|
|
67
|
+
"binary": integration.bin_name or "-",
|
|
68
|
+
"enabled": integration.enabled,
|
|
69
|
+
"rules": integration.system_prompt_file,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
print_output(output, title="Agent Providers")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@provider_app.command(name="check")
|
|
77
|
+
def check_providers():
|
|
78
|
+
"""
|
|
79
|
+
Run health checks on available providers.
|
|
80
|
+
"""
|
|
81
|
+
from monoco.core.integrations import get_all_integrations
|
|
82
|
+
|
|
83
|
+
integrations = get_all_integrations(enabled_only=True)
|
|
84
|
+
|
|
85
|
+
output = []
|
|
86
|
+
for key, integration in integrations.items():
|
|
87
|
+
health = integration.check_health()
|
|
88
|
+
output.append(
|
|
89
|
+
{
|
|
90
|
+
"provider": integration.name,
|
|
91
|
+
"available": "✅" if health.available else "❌",
|
|
92
|
+
"latency": f"{health.latency_ms}ms" if health.latency_ms else "-",
|
|
93
|
+
"error": health.error or "-",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
print_output(output, title="Provider Health Check")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def run(
|
|
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)."
|
|
105
|
+
),
|
|
106
|
+
role: str = typer.Option(
|
|
107
|
+
"Default", "--role", "-r", help="Specific role to use."
|
|
108
|
+
),
|
|
109
|
+
detach: bool = typer.Option(
|
|
110
|
+
False, "--detach", "-d", help="Run in background (Daemon)"
|
|
111
|
+
),
|
|
112
|
+
provider: Optional[str] = typer.Option(
|
|
113
|
+
None, "--provider", "-p", help="Override the default engine/provider for this session."
|
|
114
|
+
),
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Start an agent session.
|
|
118
|
+
|
|
119
|
+
Usage:
|
|
120
|
+
monoco agent run "Check memos"
|
|
121
|
+
monoco agent run -i FEAT-101 "Implement feature"
|
|
122
|
+
"""
|
|
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
|
|
133
|
+
else:
|
|
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.
|
|
138
|
+
issue_id = "NEW_TASK"
|
|
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)
|
|
144
|
+
|
|
145
|
+
# 2. Load Roles
|
|
146
|
+
roles = load_scheduler_config(project_root)
|
|
147
|
+
selected_role = roles.get(role)
|
|
148
|
+
|
|
149
|
+
if not selected_role:
|
|
150
|
+
print_error(f"Role '{role}' not found. Available: {list(roles.keys())}")
|
|
151
|
+
raise typer.Exit(code=1)
|
|
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)
|
|
206
|
+
print_output(
|
|
207
|
+
f"Starting Agent Session for '{display_target}' as {role} (via {selected_role.engine})...",
|
|
208
|
+
title="Agent Framework",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# 4. Initialize Session
|
|
212
|
+
manager = SessionManager()
|
|
213
|
+
session = manager.create_session(issue_id, selected_role)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Pass description if it's a new task
|
|
218
|
+
context = {"description": description} if description else None
|
|
219
|
+
session.start(context=context)
|
|
220
|
+
|
|
221
|
+
if detach:
|
|
222
|
+
print_output(
|
|
223
|
+
f"Session {session.model.id} started in background (detached)."
|
|
224
|
+
)
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
# Monitoring Loop
|
|
228
|
+
while session.refresh_status() == "running":
|
|
229
|
+
time.sleep(1)
|
|
230
|
+
|
|
231
|
+
if session.model.status == "failed":
|
|
232
|
+
print_error(
|
|
233
|
+
f"Session {session.model.id} FAILED. Review logs for details."
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
print_output(
|
|
237
|
+
f"Session finished with status: {session.model.status}",
|
|
238
|
+
title="Agent Framework",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
except KeyboardInterrupt:
|
|
242
|
+
print("\nStopping...")
|
|
243
|
+
session.terminate()
|
|
244
|
+
print_output("Session terminated.")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@session_app.command(name="kill")
|
|
248
|
+
def kill_session(session_id: str):
|
|
249
|
+
"""
|
|
250
|
+
Terminate a specific session.
|
|
251
|
+
"""
|
|
252
|
+
manager = SessionManager()
|
|
253
|
+
session = manager.get_session(session_id)
|
|
254
|
+
if session:
|
|
255
|
+
session.terminate()
|
|
256
|
+
print_output(f"Session {session_id} terminated.")
|
|
257
|
+
else:
|
|
258
|
+
print_output(f"Session {session_id} not found.", style="red")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@session_app.command(name="list")
|
|
262
|
+
def list_sessions():
|
|
263
|
+
"""
|
|
264
|
+
List active agent sessions.
|
|
265
|
+
"""
|
|
266
|
+
manager = SessionManager()
|
|
267
|
+
sessions = manager.list_sessions()
|
|
268
|
+
|
|
269
|
+
output = []
|
|
270
|
+
for s in sessions:
|
|
271
|
+
output.append(
|
|
272
|
+
{
|
|
273
|
+
"id": s.model.id,
|
|
274
|
+
"issue": s.model.issue_id,
|
|
275
|
+
"role": s.model.role_name,
|
|
276
|
+
"status": s.model.status,
|
|
277
|
+
"branch": s.model.branch_name,
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
print_output(
|
|
282
|
+
output
|
|
283
|
+
or "No active sessions found (Note: Persistence not implemented in CLI list yet).",
|
|
284
|
+
title="Active Sessions",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@session_app.command(name="logs")
|
|
289
|
+
def session_logs(session_id: str):
|
|
290
|
+
"""
|
|
291
|
+
Stream logs for a session.
|
|
292
|
+
"""
|
|
293
|
+
print_output(f"Streaming logs for {session_id}...", title="Session Logs")
|
|
294
|
+
# Placeholder
|
|
295
|
+
print("[12:00:00] Session started")
|
|
296
|
+
print("[12:00:01] Worker initialized")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .models import RoleTemplate, AgentRoleConfig as AgentConfig
|
|
5
|
+
from .defaults import DEFAULT_ROLES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RoleLoader:
|
|
9
|
+
"""
|
|
10
|
+
Tiered configuration loader for Agent 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
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, project_root: Optional[Path] = None):
|
|
17
|
+
self.project_root = project_root
|
|
18
|
+
self.user_home = Path.home()
|
|
19
|
+
self.roles: Dict[str, RoleTemplate] = {}
|
|
20
|
+
self.sources: Dict[str, str] = {} # role_name -> source description
|
|
21
|
+
|
|
22
|
+
def load_all(self) -> Dict[str, RoleTemplate]:
|
|
23
|
+
# Level 1: Defaults (Hardcoded)
|
|
24
|
+
for role in DEFAULT_ROLES:
|
|
25
|
+
if role.name not in self.roles:
|
|
26
|
+
self.roles[role.name] = role
|
|
27
|
+
self.sources[role.name] = "builtin (default)"
|
|
28
|
+
|
|
29
|
+
# Level 2: 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")
|
|
33
|
+
|
|
34
|
+
# Level 3: Project
|
|
35
|
+
if self.project_root:
|
|
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")
|
|
39
|
+
|
|
40
|
+
return self.roles
|
|
41
|
+
|
|
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):
|
|
50
|
+
if not path.exists():
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
with open(path, "r") as f:
|
|
55
|
+
data = yaml.safe_load(f) or {}
|
|
56
|
+
|
|
57
|
+
# Case A: Config object with "roles" list
|
|
58
|
+
if "roles" in data and isinstance(data["roles"], list):
|
|
59
|
+
config = AgentConfig(roles=data["roles"])
|
|
60
|
+
for role in config.roles:
|
|
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
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
import sys
|
|
70
|
+
print(f"Warning: Failed to load roles from {path}: {e}", file=sys.stderr)
|
|
71
|
+
|
|
72
|
+
def _upsert_role(self, role: RoleTemplate, source: str):
|
|
73
|
+
self.roles[role.name] = role
|
|
74
|
+
self.sources[role.name] = source
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def load_scheduler_config(project_root: Path) -> Dict[str, RoleTemplate]:
|
|
78
|
+
"""
|
|
79
|
+
Legacy compatibility wrapper for functional access.
|
|
80
|
+
"""
|
|
81
|
+
loader = RoleLoader(project_root)
|
|
82
|
+
return loader.load_all()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_agent_config(project_root: Path) -> Dict[str, RoleTemplate]:
|
|
86
|
+
"""
|
|
87
|
+
Load agent configuration from tiered sources.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
project_root: Path to the project root directory
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary mapping role names to RoleTemplate objects
|
|
94
|
+
"""
|
|
95
|
+
loader = RoleLoader(project_root)
|
|
96
|
+
return loader.load_all()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .models import RoleTemplate
|
|
2
|
+
|
|
3
|
+
DEFAULT_ROLES = [
|
|
4
|
+
RoleTemplate(
|
|
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.",
|
|
10
|
+
engine="gemini",
|
|
11
|
+
)
|
|
12
|
+
]
|
|
@@ -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
|