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.
Files changed (35) hide show
  1. monoco/core/config.py +35 -0
  2. monoco/core/integrations.py +0 -6
  3. monoco/core/resources/en/AGENTS.md +25 -0
  4. monoco/core/resources/en/SKILL.md +32 -1
  5. monoco/core/resources/zh/AGENTS.md +25 -0
  6. monoco/core/resources/zh/SKILL.md +32 -0
  7. monoco/core/sync.py +6 -19
  8. monoco/features/i18n/core.py +31 -11
  9. monoco/features/issue/commands.py +24 -1
  10. monoco/features/issue/core.py +90 -39
  11. monoco/features/issue/domain/models.py +1 -0
  12. monoco/features/issue/domain_commands.py +47 -0
  13. monoco/features/issue/domain_service.py +69 -0
  14. monoco/features/issue/linter.py +119 -11
  15. monoco/features/issue/validator.py +47 -0
  16. monoco/features/scheduler/__init__.py +19 -0
  17. monoco/features/scheduler/cli.py +204 -0
  18. monoco/features/scheduler/config.py +32 -0
  19. monoco/features/scheduler/defaults.py +54 -0
  20. monoco/features/scheduler/manager.py +49 -0
  21. monoco/features/scheduler/models.py +24 -0
  22. monoco/features/scheduler/reliability.py +99 -0
  23. monoco/features/scheduler/session.py +87 -0
  24. monoco/features/scheduler/worker.py +129 -0
  25. monoco/main.py +4 -0
  26. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/METADATA +1 -1
  27. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/RECORD +30 -24
  28. monoco/core/agent/__init__.py +0 -3
  29. monoco/core/agent/action.py +0 -168
  30. monoco/core/agent/adapters.py +0 -133
  31. monoco/core/agent/protocol.py +0 -32
  32. monoco/core/agent/state.py +0 -106
  33. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/WHEEL +0 -0
  34. {monoco_toolkit-0.3.1.dist-info → monoco_toolkit-0.3.3.dist-info}/entry_points.txt +0 -0
  35. {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.1
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=_ZhokFhmYF1NNHubuVr9gZ_HLg6AaZPWMppnUESbpwA,5346
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=DsxkUaHbKK17JuMzcYLVBWnLhhs78HC_gday2fyx6sw,12250
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=GOWwDceEuhU6Q1nX8vvyGQ7s_kzuW0lKK23nEFkzCT0,7912
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=DJXnWM_LxN36VniTUS5axFiAhVXYHbxLbI1uaUvV1JU,8750
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/agent/__init__.py,sha256=iU3wac9iYecA1M2jaYnnPjU9wANdAS7B-jRAuT5gCo4,38
23
- monoco/core/agent/action.py,sha256=qkQGJERhL4N5MSuwDivn8NAw4sMhEa346huGtqNyFTM,5295
24
- monoco/core/agent/adapters.py,sha256=q6obVcXve46mVNBaSCBRFRYs8gImv6Y2gAJVv173Z6w,4376
25
- monoco/core/agent/protocol.py,sha256=eB0OiMjDQu1DOoeVqb_PpHsMyPy2LdNenNyt4vJ9qbQ,798
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=MZPn6dJCj9CYy1gihHPOOgLlIlsbqwz97Dl9KpAnX-w,9188
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=ndBE_3tP4CZK45lqAbsGpeopzvebMZKeIezZchTGIsc,37353
50
- monoco/features/issue/core.py,sha256=WoWZRpw7o9f6ERqqIgD35c-SYD7Jv1aTsJVj6ljPz9E,50369
51
- monoco/features/issue/linter.py,sha256=DQ3UruGUUMZu63EFn71eczoFDoqj31JxHHaRR3Ej6O8,21067
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=Rbn5M_RLEHVPi5n0Y19-VCWZvVlRWcCRFYRbsw-Oc-I,24732
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=eo5Ks7VzWDQsqzuu9gcBuxIafvfqnzUDNDG9wbaghsc,5797
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.1.dist-info/METADATA,sha256=PZkujG6T9VUfcKukCNXem8IsQlJ9NruQZW0sWQfsoWI,4866
81
- monoco_toolkit-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
82
- monoco_toolkit-0.3.1.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
83
- monoco_toolkit-0.3.1.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
84
- monoco_toolkit-0.3.1.dist-info/RECORD,,
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,,
@@ -1,3 +0,0 @@
1
- """
2
- Monoco Agent Execution Layer.
3
- """
@@ -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