monoco-toolkit 0.3.1__py3-none-any.whl → 0.3.3__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 +35 -0
- monoco/core/integrations.py +0 -6
- monoco/core/resources/en/AGENTS.md +25 -0
- monoco/core/resources/en/SKILL.md +32 -1
- monoco/core/resources/zh/AGENTS.md +25 -0
- monoco/core/resources/zh/SKILL.md +32 -0
- monoco/core/sync.py +6 -19
- monoco/features/i18n/core.py +31 -11
- monoco/features/issue/commands.py +24 -1
- monoco/features/issue/core.py +90 -39
- monoco/features/issue/domain/models.py +1 -0
- monoco/features/issue/domain_commands.py +47 -0
- monoco/features/issue/domain_service.py +69 -0
- monoco/features/issue/linter.py +119 -11
- monoco/features/issue/validator.py +47 -0
- monoco/features/scheduler/__init__.py +19 -0
- monoco/features/scheduler/cli.py +204 -0
- monoco/features/scheduler/config.py +32 -0
- monoco/features/scheduler/defaults.py +54 -0
- monoco/features/scheduler/manager.py +49 -0
- monoco/features/scheduler/models.py +24 -0
- monoco/features/scheduler/reliability.py +99 -0
- monoco/features/scheduler/session.py +87 -0
- monoco/features/scheduler/worker.py +129 -0
- monoco/main.py +4 -0
- {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/METADATA +1 -1
- {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/RECORD +30 -24
- monoco/core/agent/__init__.py +0 -3
- monoco/core/agent/action.py +0 -168
- monoco/core/agent/adapters.py +0 -133
- monoco/core/agent/protocol.py +0 -32
- monoco/core/agent/state.py +0 -106
- {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RoleTemplate(BaseModel):
|
|
6
|
+
name: str = Field(
|
|
7
|
+
..., description="Unique identifier for the role (e.g., 'crafter')"
|
|
8
|
+
)
|
|
9
|
+
description: str = Field(..., description="Human-readable description of the role")
|
|
10
|
+
trigger: str = Field(
|
|
11
|
+
..., description="Event that triggers this agent (e.g., 'issue.created')"
|
|
12
|
+
)
|
|
13
|
+
goal: str = Field(..., description="The primary goal/output of this agent")
|
|
14
|
+
tools: List[str] = Field(default_factory=list, description="List of allowed tools")
|
|
15
|
+
system_prompt: str = Field(
|
|
16
|
+
..., description="The system prompt template for this agent"
|
|
17
|
+
)
|
|
18
|
+
engine: str = Field(
|
|
19
|
+
default="gemini", description="CLI agent engine (gemini/claude)"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SchedulerConfig(BaseModel):
|
|
24
|
+
roles: List[RoleTemplate] = Field(default_factory=list)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from .manager import SessionManager
|
|
2
|
+
from .session import RuntimeSession
|
|
3
|
+
from .defaults import DEFAULT_ROLES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApoptosisManager:
|
|
7
|
+
"""
|
|
8
|
+
Handles the 'Apoptosis' (Programmed Cell Death) lifecycle for agents.
|
|
9
|
+
Ensures that failing agents are killed, analyzed, and the environment is reset.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, session_manager: SessionManager):
|
|
13
|
+
self.session_manager = session_manager
|
|
14
|
+
# Find coroner role
|
|
15
|
+
self.coroner_role = next(
|
|
16
|
+
(r for r in DEFAULT_ROLES if r.name == "coroner"), None
|
|
17
|
+
)
|
|
18
|
+
if not self.coroner_role:
|
|
19
|
+
raise ValueError("Coroner role not defined!")
|
|
20
|
+
|
|
21
|
+
def check_health(self, session: RuntimeSession) -> bool:
|
|
22
|
+
"""
|
|
23
|
+
Check if a session is healthy.
|
|
24
|
+
In a real implementation, this would check heartbeat, CPU usage, or token limits.
|
|
25
|
+
"""
|
|
26
|
+
# Placeholder logic: Random failure or external flag?
|
|
27
|
+
# For now, always healthy unless explicitly marked 'crashed' (which we can simulate)
|
|
28
|
+
if hasattr(session, "simulate_crash") and session.simulate_crash:
|
|
29
|
+
return False
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
def trigger_apoptosis(self, session_id: str):
|
|
33
|
+
"""
|
|
34
|
+
Execute the full death and rebirth cycle.
|
|
35
|
+
"""
|
|
36
|
+
session = self.session_manager.get_session(session_id)
|
|
37
|
+
if not session:
|
|
38
|
+
print(f"Session {session_id} not found for apoptosis.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
print(f"💀 [Apoptosis] Starting lifecycle for Session {session_id}")
|
|
42
|
+
|
|
43
|
+
# 1. Kill
|
|
44
|
+
self._kill(session)
|
|
45
|
+
|
|
46
|
+
# 2. Autopsy
|
|
47
|
+
try:
|
|
48
|
+
self._perform_autopsy(session)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"⚠️ Autopsy failed: {e}")
|
|
51
|
+
|
|
52
|
+
# 3. Reset
|
|
53
|
+
self._reset_environment(session)
|
|
54
|
+
|
|
55
|
+
print(
|
|
56
|
+
f"✅ [Apoptosis] Task {session.model.issue_id} has been reset and analyzed."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _kill(self, session: RuntimeSession):
|
|
60
|
+
print(f"🔪 Killing worker process for {session.model.id}...")
|
|
61
|
+
session.terminate()
|
|
62
|
+
session.model.status = "crashed"
|
|
63
|
+
|
|
64
|
+
def _perform_autopsy(self, victim_session: RuntimeSession):
|
|
65
|
+
print(
|
|
66
|
+
f"🔍 Performing autopsy on {victim_session.model.id} via Coroner agent..."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Start a Coroner session
|
|
70
|
+
coroner_session = self.session_manager.create_session(
|
|
71
|
+
victim_session.model.issue_id, self.coroner_role
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Context for the coroner
|
|
75
|
+
context = {
|
|
76
|
+
"description": f"The previous agent session ({victim_session.model.id}) for role '{victim_session.model.role_name}' crashed. Please analyze the environment and the Issue {victim_session.model.issue_id}, then write a ## Post-mortem section in the issue file."
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
coroner_session.start(context=context)
|
|
80
|
+
print("📄 Coroner agent finished analysis.")
|
|
81
|
+
|
|
82
|
+
def _reset_environment(self, session: RuntimeSession):
|
|
83
|
+
print("🧹 Resetting environment (simulated git reset --hard)...")
|
|
84
|
+
# In real impl:
|
|
85
|
+
# import subprocess
|
|
86
|
+
# subprocess.run(["git", "reset", "--hard"], check=True)
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def _retry(self, session: RuntimeSession):
|
|
90
|
+
print("🔄 Reincarnating session...")
|
|
91
|
+
# Create a new session with the same role and issue
|
|
92
|
+
new_session = self.session_manager.create_session(
|
|
93
|
+
session.model.issue_id,
|
|
94
|
+
# We need to find the original role object.
|
|
95
|
+
# Simplified: assuming we can find it by name or pass it.
|
|
96
|
+
# For now, just placeholder.
|
|
97
|
+
session.worker.role,
|
|
98
|
+
)
|
|
99
|
+
new_session.start()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from .worker import Worker
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Session(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
Represents a runtime session of a worker.
|
|
10
|
+
Persisted state of the session.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
id: str = Field(..., description="Unique session ID (likely UUID)")
|
|
14
|
+
issue_id: str = Field(..., description="The Issue ID this session is working on")
|
|
15
|
+
role_name: str = Field(..., description="Name of the role employed")
|
|
16
|
+
status: str = Field(
|
|
17
|
+
default="pending", description="pending, running, suspended, terminated"
|
|
18
|
+
)
|
|
19
|
+
branch_name: str = Field(
|
|
20
|
+
..., description="Git branch name associated with this session"
|
|
21
|
+
)
|
|
22
|
+
created_at: datetime = Field(default_factory=datetime.now)
|
|
23
|
+
updated_at: datetime = Field(default_factory=datetime.now)
|
|
24
|
+
# History could be a list of logs or pointers to git commits
|
|
25
|
+
# For now, let's keep it simple. The git log IS the history.
|
|
26
|
+
|
|
27
|
+
class Config:
|
|
28
|
+
arbitrary_types_allowed = True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RuntimeSession:
|
|
32
|
+
"""
|
|
33
|
+
The in-memory wrapper around the Session model and the active Worker.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, session_model: Session, worker: Worker):
|
|
37
|
+
self.model = session_model
|
|
38
|
+
self.worker = worker
|
|
39
|
+
|
|
40
|
+
def start(self, context: Optional[dict] = None):
|
|
41
|
+
print(
|
|
42
|
+
f"Session {self.model.id}: Starting worker on branch {self.model.branch_name}"
|
|
43
|
+
)
|
|
44
|
+
# In real impl, checking out branch happening here
|
|
45
|
+
self.model.status = "running"
|
|
46
|
+
self.model.updated_at = datetime.now()
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
self.worker.start(context)
|
|
50
|
+
# Async mode: we assume it started running.
|
|
51
|
+
# Use poll or refresh_status to check later.
|
|
52
|
+
self.model.status = "running"
|
|
53
|
+
except Exception:
|
|
54
|
+
self.model.status = "failed"
|
|
55
|
+
raise
|
|
56
|
+
finally:
|
|
57
|
+
self.model.updated_at = datetime.now()
|
|
58
|
+
|
|
59
|
+
def refresh_status(self) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Polls the worker and updates the session model status.
|
|
62
|
+
"""
|
|
63
|
+
worker_status = self.worker.poll()
|
|
64
|
+
self.model.status = worker_status
|
|
65
|
+
self.model.updated_at = datetime.now()
|
|
66
|
+
return worker_status
|
|
67
|
+
|
|
68
|
+
def suspend(self):
|
|
69
|
+
print(f"Session {self.model.id}: Suspending worker")
|
|
70
|
+
self.worker.stop()
|
|
71
|
+
self.model.status = "suspended"
|
|
72
|
+
self.model.updated_at = datetime.now()
|
|
73
|
+
# In real impl, ensure git commit of current state?
|
|
74
|
+
|
|
75
|
+
def resume(self):
|
|
76
|
+
print(f"Session {self.model.id}: Resuming worker")
|
|
77
|
+
self.worker.start() # In real impl, might need to re-init process
|
|
78
|
+
|
|
79
|
+
# Async mode: assume running
|
|
80
|
+
self.model.status = "running"
|
|
81
|
+
self.model.updated_at = datetime.now()
|
|
82
|
+
|
|
83
|
+
def terminate(self):
|
|
84
|
+
print(f"Session {self.model.id}: Terminating")
|
|
85
|
+
self.worker.stop()
|
|
86
|
+
self.model.status = "terminated"
|
|
87
|
+
self.model.updated_at = datetime.now()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .models import RoleTemplate
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Worker:
|
|
6
|
+
"""
|
|
7
|
+
Represents an active or pending agent session assigned to a specific role and issue.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, role: RoleTemplate, issue_id: str):
|
|
11
|
+
self.role = role
|
|
12
|
+
self.issue_id = issue_id
|
|
13
|
+
self.status = "pending" # pending, running, suspended, terminated
|
|
14
|
+
self.process_id: Optional[int] = None
|
|
15
|
+
self._process = None
|
|
16
|
+
|
|
17
|
+
def start(self, context: Optional[dict] = None):
|
|
18
|
+
"""
|
|
19
|
+
Start the worker session asynchronously.
|
|
20
|
+
"""
|
|
21
|
+
# Allow restart if not currently running
|
|
22
|
+
if self.status == "running":
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
print(f"Starting worker {self.role.name} for issue {self.issue_id}")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
self._execute_work(context)
|
|
29
|
+
self.status = "running"
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"Worker failed to start: {e}")
|
|
32
|
+
self.status = "failed"
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
def _execute_work(self, context: Optional[dict] = None):
|
|
36
|
+
import subprocess
|
|
37
|
+
import sys
|
|
38
|
+
|
|
39
|
+
# Prepare the prompt
|
|
40
|
+
if self.role.name == "drafter" and context:
|
|
41
|
+
issue_type = context.get("type", "feature")
|
|
42
|
+
description = context.get("description", "No description")
|
|
43
|
+
prompt = (
|
|
44
|
+
f"You are a Drafter in the Monoco project.\n\n"
|
|
45
|
+
f"Task: Create a new {issue_type} issue based on this request: {description}\n\n"
|
|
46
|
+
"Constraints:\n"
|
|
47
|
+
"1. Use 'monoco issue create' to generate the file.\n"
|
|
48
|
+
"2. Use 'monoco issue update' or direct file editing to enrich Objective and Tasks.\n"
|
|
49
|
+
"3. IMPORTANT: Once the issue file is created and filled with high-quality content, EXIT search or interactive mode immediately.\n"
|
|
50
|
+
"4. Do not perform any other development tasks."
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
prompt = (
|
|
54
|
+
f"{self.role.system_prompt}\n\n"
|
|
55
|
+
f"Issue context: {self.issue_id}\n"
|
|
56
|
+
f"Goal: {self.role.goal}\n"
|
|
57
|
+
)
|
|
58
|
+
if context and "description" in context:
|
|
59
|
+
prompt += f"Specific Task: {context['description']}"
|
|
60
|
+
|
|
61
|
+
engine = self.role.engine
|
|
62
|
+
|
|
63
|
+
print(f"[{self.role.name}] Engine: {engine}")
|
|
64
|
+
print(f"[{self.role.name}] Goal: {self.role.goal}")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Execute CLI agent with YOLO mode
|
|
68
|
+
engine_args = (
|
|
69
|
+
[engine, "-y", prompt] if engine == "gemini" else [engine, prompt]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self._process = subprocess.Popen(
|
|
73
|
+
engine_args, stdout=sys.stdout, stderr=sys.stderr, text=True
|
|
74
|
+
)
|
|
75
|
+
self.process_id = self._process.pid
|
|
76
|
+
|
|
77
|
+
# DO NOT WAIT HERE.
|
|
78
|
+
# The scheduler/monitoring loop is responsible for checking status.
|
|
79
|
+
|
|
80
|
+
except FileNotFoundError:
|
|
81
|
+
raise RuntimeError(
|
|
82
|
+
f"Agent engine '{engine}' not found. Please ensure it is installed and in PATH."
|
|
83
|
+
)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"[{self.role.name}] Process Error: {e}")
|
|
86
|
+
raise
|
|
87
|
+
|
|
88
|
+
def poll(self) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Check process status. Returns current worker status.
|
|
91
|
+
Updates self.status if process has finished.
|
|
92
|
+
"""
|
|
93
|
+
if not self._process:
|
|
94
|
+
return self.status
|
|
95
|
+
|
|
96
|
+
returncode = self._process.poll()
|
|
97
|
+
if returncode is None:
|
|
98
|
+
return "running"
|
|
99
|
+
|
|
100
|
+
if returncode == 0:
|
|
101
|
+
self.status = "completed"
|
|
102
|
+
else:
|
|
103
|
+
self.status = "failed"
|
|
104
|
+
|
|
105
|
+
return self.status
|
|
106
|
+
|
|
107
|
+
def wait(self):
|
|
108
|
+
"""
|
|
109
|
+
Block until process finishes.
|
|
110
|
+
"""
|
|
111
|
+
if self._process:
|
|
112
|
+
self._process.wait()
|
|
113
|
+
self.poll() # Update status
|
|
114
|
+
|
|
115
|
+
def stop(self):
|
|
116
|
+
"""
|
|
117
|
+
Stop the worker session.
|
|
118
|
+
"""
|
|
119
|
+
if self.status == "terminated":
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
print(f"Stopping worker {self.role.name} for issue {self.issue_id}")
|
|
123
|
+
self.status = "terminated"
|
|
124
|
+
self.process_id = None
|
|
125
|
+
|
|
126
|
+
def __repr__(self):
|
|
127
|
+
return (
|
|
128
|
+
f"<Worker role={self.role.name} issue={self.issue_id} status={self.status}>"
|
|
129
|
+
)
|
monoco/main.py
CHANGED
|
@@ -166,6 +166,10 @@ app.add_typer(config_cmd.app, name="config", help="Manage configuration")
|
|
|
166
166
|
app.add_typer(project_cmd.app, name="project", help="Manage projects")
|
|
167
167
|
app.add_typer(workspace_cmd.app, name="workspace", help="Manage workspace")
|
|
168
168
|
|
|
169
|
+
from monoco.features.scheduler import cli as scheduler_cmd
|
|
170
|
+
|
|
171
|
+
app.add_typer(scheduler_cmd.app, name="agent", help="Manage agent sessions")
|
|
172
|
+
|
|
169
173
|
|
|
170
174
|
from monoco.daemon.commands import serve
|
|
171
175
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: monoco-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Agent Native Toolkit for Monoco - Task Management & Kanban for AI Agents
|
|
5
5
|
Project-URL: Homepage, https://monoco.io
|
|
6
6
|
Project-URL: Repository, https://github.com/IndenScale/Monoco
|
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
monoco/main.py,sha256=
|
|
1
|
+
monoco/main.py,sha256=YGQ-A3UlD12cyvJscFfV_BFpeHxlm_LJn_WVIaLrINc,5484
|
|
2
2
|
monoco/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
monoco/cli/project.py,sha256=FqLaDD3hiWxa0_TKzxEF7PYH9RPsvmLyjO3NYVckgGs,2737
|
|
4
4
|
monoco/cli/workspace.py,sha256=1TVVS835XyirLDvBGQXSblIaYVhe2Pk9EpORDvcktyk,1538
|
|
5
5
|
monoco/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
monoco/core/config.py,sha256=
|
|
6
|
+
monoco/core/config.py,sha256=M9F5De2FUgaIxFwqZBoJUXsMsph31rNh-4U-k_9rUHA,13543
|
|
7
7
|
monoco/core/execution.py,sha256=7522s7uVWiNdMKtWlIb8JHzbAcJgLVuxUFp_6VmhWNQ,1801
|
|
8
8
|
monoco/core/feature.py,sha256=bcK0C1CaQgNLcdMF0RuniaaQRCYSA-0JdOpQz9W_1xM,1968
|
|
9
9
|
monoco/core/git.py,sha256=d3A8L30nlEj0F1pwSHk_cOoRYi1dMCiZc7KD70ViMyw,8335
|
|
10
10
|
monoco/core/hooks.py,sha256=QY4c74LIYDtcjtWYax1PrK5CtVNKYTfVVVBOj-SCulo,1826
|
|
11
11
|
monoco/core/injection.py,sha256=zs9RpXaBfwP-fbwOU_X2nm-AH94tQN9tVWXNtV4tCYM,6887
|
|
12
|
-
monoco/core/integrations.py,sha256=
|
|
12
|
+
monoco/core/integrations.py,sha256=ifSz7I87DXHF-eHLyXRN3pY1-Kh8IjvMyIrG9iogGpw,7744
|
|
13
13
|
monoco/core/lsp.py,sha256=GOrHzubkMWS3iBpP4H2fTFW-0JXa6D2YlU0ZEhp5zmY,2018
|
|
14
14
|
monoco/core/output.py,sha256=ZlUydZ65FIP5a32_eESIIvMrhcSwghIqZ3N23OSx6Ss,3878
|
|
15
15
|
monoco/core/registry.py,sha256=1m9wvkcqWqrTcARt-jUGHH1BEYMvp0NJGRkq-uduJeg,1167
|
|
16
16
|
monoco/core/setup.py,sha256=YUIhzqZwjEeH7zGZm9SPA-OhgTISJtA6ByNFCGBznUU,11487
|
|
17
17
|
monoco/core/skills.py,sha256=KeVN09KIcRiksjJ91AggJbN_EvjRQfMGqd_hUQfUjes,16168
|
|
18
18
|
monoco/core/state.py,sha256=dfoTH1Sj_TSjtTEXuv4w0ZOp_Fd30XNymVSZo30Xshg,1820
|
|
19
|
-
monoco/core/sync.py,sha256=
|
|
19
|
+
monoco/core/sync.py,sha256=1p37pTfGBanogzcLhdTfMTxDqq79B5iiwiPS4fiuQaA,8378
|
|
20
20
|
monoco/core/telemetry.py,sha256=GQDbtgrZwAL1ZpjgbJZuawbTyH6J0NjMXMi4ogq-Ang,2915
|
|
21
21
|
monoco/core/workspace.py,sha256=H_PHD5A0HZFq841u1JtLoFjkXdQg9D6x6I7QcFtJge4,3000
|
|
22
|
-
monoco/core/
|
|
23
|
-
monoco/core/
|
|
24
|
-
monoco/core/
|
|
25
|
-
monoco/core/
|
|
26
|
-
monoco/core/agent/state.py,sha256=Z8qqY7loms5nt3524_TrEMmlpnurxf0aHfiCT1FgvDI,3385
|
|
27
|
-
monoco/core/resources/en/AGENTS.md,sha256=3TpCLNC1s_lIbDfJJBRmmROMoy9sZXu8BOag8M9NXI0,327
|
|
28
|
-
monoco/core/resources/en/SKILL.md,sha256=1PrBqCgjDxT1LMSeu11BzlMhfboo_UlgZxdzBm7Tp9c,2161
|
|
29
|
-
monoco/core/resources/zh/AGENTS.md,sha256=pGQOLt8mcRygJotd5oC8l9604hikQoUiS99QsHCe-UM,298
|
|
30
|
-
monoco/core/resources/zh/SKILL.md,sha256=8py8tD7s23cw2srKFnTjLO_miyuGXb3NLyEbk5gchqA,1886
|
|
22
|
+
monoco/core/resources/en/AGENTS.md,sha256=vf9z43UU-LPwYKPWCrtw8TpWrmkeZ6zfMyHP4Q9JqdQ,1178
|
|
23
|
+
monoco/core/resources/en/SKILL.md,sha256=wBXNpPqATOxO8TGiOM8S091gMNZ8du_WhQubO6TfNyg,3504
|
|
24
|
+
monoco/core/resources/zh/AGENTS.md,sha256=KBNZSCPBIals6jDdvG5wJgjZuswi_1nKljggJSMtfy8,1156
|
|
25
|
+
monoco/core/resources/zh/SKILL.md,sha256=1D8_FqzDVqowZWk4Qkyx4me53NTd8qihP20gaCH6gJY,3153
|
|
31
26
|
monoco/daemon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
27
|
monoco/daemon/app.py,sha256=tAtLxzw_aRzU0r6Bk9lhshaCyqZRkQKUJmg5FDi7-1c,15431
|
|
33
28
|
monoco/daemon/commands.py,sha256=2AGsqDZ0edg34KuAGZrTvKlJZ1fhxNXgus7vGn2km4c,1115
|
|
@@ -40,22 +35,24 @@ monoco/features/config/commands.py,sha256=i6_fKhSGLeO8RCbWrYKxelFgaHWcPiQERS2uxU
|
|
|
40
35
|
monoco/features/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
36
|
monoco/features/i18n/adapter.py,sha256=-ifDKQ7UH8tDoOdB_fSKoIrNRJROIPNuzZor2FPJVfo,932
|
|
42
37
|
monoco/features/i18n/commands.py,sha256=NJ2lZRRjiTW-3UbqxhEKURdWSDxQPkfnBAUgnwoVCL8,8416
|
|
43
|
-
monoco/features/i18n/core.py,sha256=
|
|
38
|
+
monoco/features/i18n/core.py,sha256=bnEV9D7KgxbC5I3l-1RN1-H8hek1loDL6KFXzmdq0pQ,9658
|
|
44
39
|
monoco/features/i18n/resources/en/AGENTS.md,sha256=zzuF7BW6x8Mj6LZZmeM6wTbG5CSCDMShf4rwtN7XITg,197
|
|
45
40
|
monoco/features/i18n/resources/en/SKILL.md,sha256=Z8fAAqeMvpLDw1D_9AzZIykS5-HLVM9nnlRZLWBTPqM,2203
|
|
46
41
|
monoco/features/i18n/resources/zh/AGENTS.md,sha256=lKkwLLCADRH7UDq9no4eQY2sRfJrb64JoZ_HNved8vA,175
|
|
47
42
|
monoco/features/i18n/resources/zh/SKILL.md,sha256=Wynk7zVBW7CO0_6AEQNUvlD_3eMW_7EPnzEn9QUaiDs,1884
|
|
48
43
|
monoco/features/issue/adapter.py,sha256=4dzKg4-0XH63uORoh8qcolvKxJR6McBDIYxYEcZJJkA,1204
|
|
49
|
-
monoco/features/issue/commands.py,sha256=
|
|
50
|
-
monoco/features/issue/core.py,sha256=
|
|
51
|
-
monoco/features/issue/
|
|
44
|
+
monoco/features/issue/commands.py,sha256=V5EVdmin-JPoqdV9-vrbKcZ7HXWHZt-zq_mM1kqFVu0,38163
|
|
45
|
+
monoco/features/issue/core.py,sha256=kRyUbYqEBno0Zoogq7dMIcQv5M0QIRSlptvzp0pfImE,51302
|
|
46
|
+
monoco/features/issue/domain_commands.py,sha256=eatSF_uZp4nGpVr2PIgb00MWfEBm0OnyAd4JvUJEAAA,1535
|
|
47
|
+
monoco/features/issue/domain_service.py,sha256=bEs_WXOWmotgIR-lGwyWekF4nonvjsgrK1YG3pyVfwk,2564
|
|
48
|
+
monoco/features/issue/linter.py,sha256=lBXWj3T9OK4ysU33Mr-gzO7wcxXxl4Mu_IMgv4ePwjQ,26502
|
|
52
49
|
monoco/features/issue/migration.py,sha256=i0xlxZjrpmuHGHOAIN4iu31EwwVIvZn7yjveS-kU22c,4896
|
|
53
50
|
monoco/features/issue/models.py,sha256=7oIMxvUEfe00n7wni9bZgKU2e9404flvArixbLQ95Dg,5902
|
|
54
51
|
monoco/features/issue/monitor.py,sha256=vZN0TbR3V5fHKHRGkIhimO6UwWcwYjDHQs2qzjEG174,3549
|
|
55
|
-
monoco/features/issue/validator.py,sha256=
|
|
52
|
+
monoco/features/issue/validator.py,sha256=8N921_3B2Cd3hFjRRMFPwCZh9Pbc6CsGXFRt28X9KLQ,26798
|
|
56
53
|
monoco/features/issue/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
54
|
monoco/features/issue/domain/lifecycle.py,sha256=oIuLYTVy1RRHGngAzbNU4kTWOHMBhjuw_TuYCwNqMJw,4991
|
|
58
|
-
monoco/features/issue/domain/models.py,sha256=
|
|
55
|
+
monoco/features/issue/domain/models.py,sha256=2vuSAGaFrbAEkQapwkGG_T8cHlsmARN71fTA44PIVSM,5850
|
|
59
56
|
monoco/features/issue/domain/parser.py,sha256=axfB9BjY-m4DQqYwTQBm_38plOidZaDqFtsIejW_kYI,9095
|
|
60
57
|
monoco/features/issue/domain/workspace.py,sha256=nyTEFNmpLPuX7nssMCBiFI1XBhY9zbqG877Ppp_s0Fg,3658
|
|
61
58
|
monoco/features/issue/engine/__init__.py,sha256=-SBvzjlZJz0O-IB1c95d9pbhr3lydJNLq2vhnfkZ8PQ,747
|
|
@@ -68,6 +65,15 @@ monoco/features/issue/resources/en/AGENTS.md,sha256=7lMJdLhokispHVTrViIyMaz5TOJ8
|
|
|
68
65
|
monoco/features/issue/resources/en/SKILL.md,sha256=M5khTJdraQenX0tf7RHOVcqyKHC285qwDzWdsCJ7XPI,4032
|
|
69
66
|
monoco/features/issue/resources/zh/AGENTS.md,sha256=veb22lU0qindYcsNsuGLqzpBEjExCPDOkX5Q39OjZz0,1113
|
|
70
67
|
monoco/features/issue/resources/zh/SKILL.md,sha256=XFE33cCCos0U1IVNkMhU7fGuqVqfxo_FNKdp1aScaoM,5055
|
|
68
|
+
monoco/features/scheduler/__init__.py,sha256=hvE9A14qFTeTYbWlK9iMRdXIFLMBuG8724GEZzFTHB8,483
|
|
69
|
+
monoco/features/scheduler/cli.py,sha256=peRfALi_4etXpyn9SKl98lNJhUdNWlgX1GVo0xXSlLI,5859
|
|
70
|
+
monoco/features/scheduler/config.py,sha256=4kC9ll-bFd8jldzxF29D94inn4HR2DkDEO-4u0LqjXA,1175
|
|
71
|
+
monoco/features/scheduler/defaults.py,sha256=4Ne3RpF7gZ46BmHtF0hC8wYkuCIkDSuKB7hGnHhGXms,2208
|
|
72
|
+
monoco/features/scheduler/manager.py,sha256=PmNKqfktgrdpUwE-jPE_WX3IvzZat0JKdEcmzse08-0,1680
|
|
73
|
+
monoco/features/scheduler/models.py,sha256=rb8rx8t_TD5ilUC6xU3jvK0sud7YUZE_iEdes9Np8R0,877
|
|
74
|
+
monoco/features/scheduler/reliability.py,sha256=AGs87DPkAa0IMYtQ5x7dpAOeTB4NTRh37FJsiqZY9Y4,3624
|
|
75
|
+
monoco/features/scheduler/session.py,sha256=V1xFXA65BaERhBwRYIecaye2TjDtFvAzuIy2gZ68ag8,2954
|
|
76
|
+
monoco/features/scheduler/worker.py,sha256=4eFyC3118ZuUnmiFVe3nSTUsIOn-N2uPXpCP_1FSTRg,4238
|
|
71
77
|
monoco/features/skills/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
78
|
monoco/features/skills/core.py,sha256=BXVvMot8E457-pj9U19lPHypBVhdLQRsx0zEAXJxpGY,3436
|
|
73
79
|
monoco/features/spike/adapter.py,sha256=ZIpw-xNVoA2IIP18dbR-dmQGbQmK5ua_P-uGXuDjCeI,1021
|
|
@@ -77,8 +83,8 @@ monoco/features/spike/resources/en/AGENTS.md,sha256=NG3CMnlDk_0J8hnRUcueAM9lgIQr
|
|
|
77
83
|
monoco/features/spike/resources/en/SKILL.md,sha256=qKDcVh0D3pDRvfNLh1Bzo4oQU3obpl4tqdlzxeiWYMk,1911
|
|
78
84
|
monoco/features/spike/resources/zh/AGENTS.md,sha256=5RHNl7fc3RdYYTFH483ojJl_arGPKkyYziOuGgFbqqg,290
|
|
79
85
|
monoco/features/spike/resources/zh/SKILL.md,sha256=Q82e9lCQOAYIwBs5rGnvlVUDq7bp0pz8yvO10KTWFYQ,1710
|
|
80
|
-
monoco_toolkit-0.3.
|
|
81
|
-
monoco_toolkit-0.3.
|
|
82
|
-
monoco_toolkit-0.3.
|
|
83
|
-
monoco_toolkit-0.3.
|
|
84
|
-
monoco_toolkit-0.3.
|
|
86
|
+
monoco_toolkit-0.3.3.dist-info/METADATA,sha256=GXPy1vr5MxL9afZc7r-vvUUOofmjiShBZe3qA9X9MHc,4866
|
|
87
|
+
monoco_toolkit-0.3.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
88
|
+
monoco_toolkit-0.3.3.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
|
|
89
|
+
monoco_toolkit-0.3.3.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
|
|
90
|
+
monoco_toolkit-0.3.3.dist-info/RECORD,,
|
monoco/core/agent/__init__.py
DELETED
monoco/core/agent/action.py
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import yaml
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Dict, List, Optional, Any
|
|
5
|
-
from pydantic import BaseModel
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ActionContext(BaseModel):
|
|
9
|
-
"""Context information for matching actions."""
|
|
10
|
-
|
|
11
|
-
id: Optional[str] = None
|
|
12
|
-
type: Optional[str] = None
|
|
13
|
-
stage: Optional[str] = None
|
|
14
|
-
status: Optional[str] = None
|
|
15
|
-
file_path: Optional[str] = None
|
|
16
|
-
project_id: Optional[str] = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ActionWhen(BaseModel):
|
|
20
|
-
"""Conditions under which an action should be displayed/active."""
|
|
21
|
-
|
|
22
|
-
idMatch: Optional[str] = None
|
|
23
|
-
typeMatch: Optional[str] = None
|
|
24
|
-
stageMatch: Optional[str] = None
|
|
25
|
-
statusMatch: Optional[str] = None
|
|
26
|
-
fileMatch: Optional[str] = None
|
|
27
|
-
|
|
28
|
-
def matches(self, context: ActionContext) -> bool:
|
|
29
|
-
"""Evaluate if the context matches these criteria."""
|
|
30
|
-
if self.idMatch and context.id and not re.match(self.idMatch, context.id):
|
|
31
|
-
return False
|
|
32
|
-
if (
|
|
33
|
-
self.typeMatch
|
|
34
|
-
and context.type
|
|
35
|
-
and not re.match(self.typeMatch, context.type)
|
|
36
|
-
):
|
|
37
|
-
return False
|
|
38
|
-
if (
|
|
39
|
-
self.stageMatch
|
|
40
|
-
and context.stage
|
|
41
|
-
and not re.match(self.stageMatch, context.stage)
|
|
42
|
-
):
|
|
43
|
-
return False
|
|
44
|
-
if (
|
|
45
|
-
self.statusMatch
|
|
46
|
-
and context.status
|
|
47
|
-
and not re.match(self.statusMatch, context.status)
|
|
48
|
-
):
|
|
49
|
-
return False
|
|
50
|
-
if (
|
|
51
|
-
self.fileMatch
|
|
52
|
-
and context.file_path
|
|
53
|
-
and not re.match(self.fileMatch, context.file_path)
|
|
54
|
-
):
|
|
55
|
-
return False
|
|
56
|
-
return True
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class PromptyAction(BaseModel):
|
|
60
|
-
name: str
|
|
61
|
-
description: str
|
|
62
|
-
version: Optional[str] = "1.0.0"
|
|
63
|
-
authors: List[str] = []
|
|
64
|
-
model: Dict[str, Any] = {}
|
|
65
|
-
inputs: Dict[str, Any] = {}
|
|
66
|
-
outputs: Dict[str, Any] = {}
|
|
67
|
-
template: str
|
|
68
|
-
when: Optional[ActionWhen] = None
|
|
69
|
-
|
|
70
|
-
# Monoco specific metadata
|
|
71
|
-
path: Optional[str] = None
|
|
72
|
-
provider: Optional[str] = None # Derived from model.api or explicitly set
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class ActionRegistry:
|
|
76
|
-
def __init__(self, project_root: Optional[Path] = None):
|
|
77
|
-
self.project_root = project_root
|
|
78
|
-
self._actions: List[PromptyAction] = []
|
|
79
|
-
|
|
80
|
-
def scan(self) -> List[PromptyAction]:
|
|
81
|
-
"""Scan user global and project local directories for .prompty files."""
|
|
82
|
-
self._actions = []
|
|
83
|
-
|
|
84
|
-
# 1. User Global: ~/.monoco/actions/
|
|
85
|
-
user_dir = Path.home() / ".monoco" / "actions"
|
|
86
|
-
self._scan_dir(user_dir)
|
|
87
|
-
|
|
88
|
-
# 2. Project Local: {project_root}/.monoco/actions/
|
|
89
|
-
if self.project_root:
|
|
90
|
-
project_dir = self.project_root / ".monoco" / "actions"
|
|
91
|
-
self._scan_dir(project_dir)
|
|
92
|
-
|
|
93
|
-
return self._actions
|
|
94
|
-
|
|
95
|
-
def _scan_dir(self, directory: Path):
|
|
96
|
-
if not directory.exists():
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
for prompty_file in directory.glob("*.prompty"):
|
|
100
|
-
try:
|
|
101
|
-
action = self._load_action(prompty_file)
|
|
102
|
-
if action:
|
|
103
|
-
self._actions.append(action)
|
|
104
|
-
except Exception as e:
|
|
105
|
-
print(f"Failed to load action {prompty_file}: {e}")
|
|
106
|
-
|
|
107
|
-
def _load_action(self, file_path: Path) -> Optional[PromptyAction]:
|
|
108
|
-
content = file_path.read_text(encoding="utf-8")
|
|
109
|
-
|
|
110
|
-
# Prompty Parser (Standard YAML Frontmatter + Body)
|
|
111
|
-
# We look for the first --- and the second ---
|
|
112
|
-
parts = re.split(r"^---\s*$", content, maxsplit=2, flags=re.MULTILINE)
|
|
113
|
-
|
|
114
|
-
if len(parts) < 3:
|
|
115
|
-
return None
|
|
116
|
-
|
|
117
|
-
frontmatter_raw = parts[1]
|
|
118
|
-
body = parts[2].strip()
|
|
119
|
-
|
|
120
|
-
try:
|
|
121
|
-
meta = yaml.safe_load(frontmatter_raw)
|
|
122
|
-
if not meta or "name" not in meta:
|
|
123
|
-
# Use filename as fallback name if missing? Prompty usually requires name.
|
|
124
|
-
if not meta:
|
|
125
|
-
meta = {}
|
|
126
|
-
meta["name"] = meta.get("name", file_path.stem)
|
|
127
|
-
|
|
128
|
-
# Map Prompty 'when' if present
|
|
129
|
-
when_data = meta.get("when")
|
|
130
|
-
when = ActionWhen(**when_data) if when_data else None
|
|
131
|
-
|
|
132
|
-
action = PromptyAction(
|
|
133
|
-
name=meta["name"],
|
|
134
|
-
description=meta.get("description", ""),
|
|
135
|
-
version=meta.get("version"),
|
|
136
|
-
authors=meta.get("authors", []),
|
|
137
|
-
model=meta.get("model", {}),
|
|
138
|
-
inputs=meta.get("inputs", {}),
|
|
139
|
-
outputs=meta.get("outputs", {}),
|
|
140
|
-
template=body,
|
|
141
|
-
when=when,
|
|
142
|
-
path=str(file_path.absolute()),
|
|
143
|
-
provider=meta.get("provider") or meta.get("model", {}).get("api"),
|
|
144
|
-
)
|
|
145
|
-
return action
|
|
146
|
-
|
|
147
|
-
except Exception as e:
|
|
148
|
-
print(f"Invalid Prompty in {file_path}: {e}")
|
|
149
|
-
return None
|
|
150
|
-
|
|
151
|
-
def list_available(
|
|
152
|
-
self, context: Optional[ActionContext] = None
|
|
153
|
-
) -> List[PromptyAction]:
|
|
154
|
-
if not self._actions:
|
|
155
|
-
self.scan()
|
|
156
|
-
|
|
157
|
-
if not context:
|
|
158
|
-
return self._actions
|
|
159
|
-
|
|
160
|
-
return [a for a in self._actions if not a.when or a.when.matches(context)]
|
|
161
|
-
|
|
162
|
-
def get(self, name: str) -> Optional[PromptyAction]:
|
|
163
|
-
if not self._actions:
|
|
164
|
-
self.scan()
|
|
165
|
-
for a in self._actions:
|
|
166
|
-
if a.name == name:
|
|
167
|
-
return a
|
|
168
|
-
return None
|