monoco-toolkit 0.3.10__py3-none-any.whl → 0.3.12__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/__main__.py +8 -0
- monoco/core/artifacts/__init__.py +16 -0
- monoco/core/artifacts/manager.py +575 -0
- monoco/core/artifacts/models.py +161 -0
- monoco/core/automation/__init__.py +51 -0
- monoco/core/automation/config.py +338 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +723 -0
- monoco/core/config.py +31 -4
- monoco/core/executor/__init__.py +38 -0
- monoco/core/executor/agent_action.py +254 -0
- monoco/core/executor/git_action.py +303 -0
- monoco/core/executor/im_action.py +309 -0
- monoco/core/executor/pytest_action.py +218 -0
- monoco/core/git.py +38 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/ingestion/__init__.py +20 -0
- monoco/core/ingestion/discovery.py +248 -0
- monoco/core/ingestion/watcher.py +343 -0
- monoco/core/ingestion/worker.py +436 -0
- monoco/core/loader.py +633 -0
- monoco/core/registry.py +34 -25
- monoco/core/router/__init__.py +55 -0
- monoco/core/router/action.py +341 -0
- monoco/core/router/router.py +392 -0
- monoco/core/scheduler/__init__.py +63 -0
- monoco/core/scheduler/base.py +152 -0
- monoco/core/scheduler/engines.py +175 -0
- monoco/core/scheduler/events.py +171 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/skills.py +119 -80
- monoco/core/watcher/__init__.py +57 -0
- monoco/core/watcher/base.py +365 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +200 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +77 -1
- monoco/daemon/commands.py +10 -0
- monoco/daemon/events.py +34 -0
- monoco/daemon/mailroom_service.py +196 -0
- monoco/daemon/models.py +1 -0
- monoco/daemon/scheduler.py +207 -0
- monoco/daemon/services.py +27 -58
- monoco/daemon/triggers.py +55 -0
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/adapter.py +17 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/{core/resources/en/skills/monoco_core → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/resources/{roles/role-engineer.yaml → zh/roles/monoco_role_engineer.yaml} +3 -3
- monoco/features/agent/resources/{roles/role-manager.yaml → zh/roles/monoco_role_manager.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-planner.yaml → zh/roles/monoco_role_planner.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-reviewer.yaml → zh/roles/monoco_role_reviewer.yaml} +8 -8
- monoco/{core/resources/zh/skills/monoco_core → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/worker.py +1 -1
- monoco/features/artifact/__init__.py +0 -0
- monoco/features/artifact/adapter.py +33 -0
- monoco/features/artifact/resources/zh/AGENTS.md +14 -0
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +278 -0
- monoco/features/glossary/adapter.py +18 -7
- monoco/features/glossary/resources/en/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/glossary/resources/zh/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/hooks/__init__.py +11 -0
- monoco/features/hooks/adapter.py +67 -0
- monoco/features/hooks/commands.py +309 -0
- monoco/features/hooks/core.py +441 -0
- monoco/features/hooks/resources/ADDING_HOOKS.md +234 -0
- monoco/features/i18n/adapter.py +18 -5
- monoco/features/i18n/core.py +482 -17
- monoco/features/i18n/resources/en/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/en/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/issue/adapter.py +19 -6
- monoco/features/issue/commands.py +352 -20
- monoco/features/issue/core.py +475 -16
- monoco/features/issue/engine/machine.py +114 -4
- monoco/features/issue/linter.py +60 -5
- monoco/features/issue/models.py +2 -2
- monoco/features/issue/resources/en/AGENTS.md +109 -0
- monoco/features/issue/resources/en/skills/{monoco_issue → monoco_atom_issue}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/resources/hooks/post-checkout.sh +39 -0
- monoco/features/issue/resources/hooks/pre-commit.sh +41 -0
- monoco/features/issue/resources/hooks/pre-push.sh +35 -0
- monoco/features/issue/resources/zh/AGENTS.md +109 -0
- monoco/features/issue/resources/zh/skills/{monoco_issue → monoco_atom_issue_lifecycle}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/validator.py +101 -1
- monoco/features/memo/adapter.py +21 -8
- monoco/features/memo/cli.py +103 -10
- monoco/features/memo/core.py +178 -92
- monoco/features/memo/models.py +53 -0
- monoco/features/memo/resources/en/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/en/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/spike/adapter.py +18 -5
- monoco/features/spike/commands.py +5 -3
- monoco/features/spike/resources/en/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/en/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/main.py +38 -1
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/METADATA +7 -1
- monoco_toolkit-0.3.12.dist-info/RECORD +202 -0
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -91
- monoco/features/agent/session.py +0 -121
- monoco_toolkit-0.3.10.dist-info/RECORD +0 -156
- /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
- /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler Service - Unified event-driven architecture (FEAT-0164).
|
|
3
|
+
|
|
4
|
+
This module implements a unified event-driven scheduler service that:
|
|
5
|
+
1. Uses AgentScheduler for agent lifecycle management (FEAT-0160)
|
|
6
|
+
2. Integrates Watcher framework for file system events (FEAT-0161)
|
|
7
|
+
3. Uses ActionRouter for event routing (FEAT-0161)
|
|
8
|
+
4. Uses new Handler framework from core.automation (FEAT-0162)
|
|
9
|
+
|
|
10
|
+
Replaces the old architecture based on SessionManager + SemaphoreManager + polling loops.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
from typing import Dict, Optional, List, Any
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from monoco.daemon.services import ProjectManager
|
|
20
|
+
from monoco.core.scheduler import (
|
|
21
|
+
AgentEventType,
|
|
22
|
+
event_bus,
|
|
23
|
+
AgentScheduler,
|
|
24
|
+
LocalProcessScheduler,
|
|
25
|
+
)
|
|
26
|
+
from monoco.core.router import ActionRouter
|
|
27
|
+
from monoco.core.watcher import WatchConfig, IssueWatcher, MemoWatcher, TaskWatcher
|
|
28
|
+
from monoco.core.automation.handlers import start_all_handlers, stop_all_handlers
|
|
29
|
+
from monoco.core.config import get_config
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("monoco.daemon.scheduler")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SchedulerService:
|
|
35
|
+
"""
|
|
36
|
+
Unified event-driven scheduler service.
|
|
37
|
+
|
|
38
|
+
Responsibilities:
|
|
39
|
+
- Initialize and manage AgentScheduler
|
|
40
|
+
- Setup and manage Watchers for file system events
|
|
41
|
+
- Configure ActionRouter for event routing
|
|
42
|
+
- Start/stop all handlers
|
|
43
|
+
|
|
44
|
+
Architecture:
|
|
45
|
+
```
|
|
46
|
+
SchedulerService
|
|
47
|
+
├── AgentScheduler (LocalProcessScheduler)
|
|
48
|
+
│ └── Manages agent process lifecycle
|
|
49
|
+
├── Watchers
|
|
50
|
+
│ ├── IssueWatcher -> EventBus
|
|
51
|
+
│ ├── MemoWatcher -> EventBus
|
|
52
|
+
│ └── TaskWatcher -> EventBus
|
|
53
|
+
├── ActionRouter
|
|
54
|
+
│ └── Routes events to Actions
|
|
55
|
+
└── Handlers (from core.automation)
|
|
56
|
+
├── TaskFileHandler
|
|
57
|
+
├── IssueStageHandler
|
|
58
|
+
├── MemoThresholdHandler
|
|
59
|
+
└── PRCreatedHandler
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, project_manager: ProjectManager):
|
|
64
|
+
self.project_manager = project_manager
|
|
65
|
+
|
|
66
|
+
# AgentScheduler (FEAT-0160)
|
|
67
|
+
scheduler_config = self._load_scheduler_config()
|
|
68
|
+
self.agent_scheduler: AgentScheduler = LocalProcessScheduler(
|
|
69
|
+
max_concurrent=scheduler_config.get("max_concurrent", 5),
|
|
70
|
+
project_root=Path.cwd(),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# ActionRouter (FEAT-0161)
|
|
74
|
+
self.action_router = ActionRouter(event_bus)
|
|
75
|
+
|
|
76
|
+
# Watchers (FEAT-0161)
|
|
77
|
+
self.watchers: List[Any] = []
|
|
78
|
+
|
|
79
|
+
# Handlers (FEAT-0162)
|
|
80
|
+
self.handlers: List[Any] = []
|
|
81
|
+
|
|
82
|
+
# Background tasks
|
|
83
|
+
self._tasks: List[asyncio.Task] = []
|
|
84
|
+
self._running = False
|
|
85
|
+
|
|
86
|
+
def _load_scheduler_config(self) -> Dict[str, Any]:
|
|
87
|
+
"""Load scheduler configuration from config files and env vars."""
|
|
88
|
+
config = {"max_concurrent": 5}
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
settings = get_config()
|
|
92
|
+
|
|
93
|
+
# Check for concurrency config
|
|
94
|
+
if hasattr(settings, "agent") and hasattr(settings.agent, "concurrency"):
|
|
95
|
+
concurrency_config = settings.agent.concurrency
|
|
96
|
+
if hasattr(concurrency_config, "global_max"):
|
|
97
|
+
config["max_concurrent"] = concurrency_config.global_max
|
|
98
|
+
|
|
99
|
+
# Check for environment variable override
|
|
100
|
+
env_max_agents = os.environ.get("MONOCO_MAX_AGENTS")
|
|
101
|
+
if env_max_agents:
|
|
102
|
+
try:
|
|
103
|
+
config["max_concurrent"] = int(env_max_agents)
|
|
104
|
+
logger.info(f"Overriding max_concurrent from environment: {env_max_agents}")
|
|
105
|
+
except ValueError:
|
|
106
|
+
logger.warning(f"Invalid MONOCO_MAX_AGENTS value: {env_max_agents}")
|
|
107
|
+
|
|
108
|
+
return config
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.warning(f"Failed to load scheduler config: {e}. Using defaults.")
|
|
111
|
+
return config
|
|
112
|
+
|
|
113
|
+
async def start(self):
|
|
114
|
+
"""Start the scheduler service."""
|
|
115
|
+
logger.info("Starting Scheduler Service (unified event-driven architecture)...")
|
|
116
|
+
self._running = True
|
|
117
|
+
|
|
118
|
+
# 1. Start EventBus
|
|
119
|
+
await event_bus.start()
|
|
120
|
+
|
|
121
|
+
# 2. Start AgentScheduler
|
|
122
|
+
await self.agent_scheduler.start()
|
|
123
|
+
|
|
124
|
+
# 3. Setup and start Watchers
|
|
125
|
+
self._setup_watchers()
|
|
126
|
+
for watcher in self.watchers:
|
|
127
|
+
await watcher.start()
|
|
128
|
+
|
|
129
|
+
# 4. Start Handlers (FEAT-0162)
|
|
130
|
+
self.handlers = start_all_handlers(self.agent_scheduler)
|
|
131
|
+
|
|
132
|
+
# 5. Start ActionRouter
|
|
133
|
+
await self.action_router.start()
|
|
134
|
+
|
|
135
|
+
logger.info("Scheduler Service started with unified event-driven architecture")
|
|
136
|
+
|
|
137
|
+
def stop(self):
|
|
138
|
+
"""Stop the scheduler service."""
|
|
139
|
+
logger.info("Stopping Scheduler Service...")
|
|
140
|
+
self._running = False
|
|
141
|
+
|
|
142
|
+
# Cancel background tasks
|
|
143
|
+
for task in self._tasks:
|
|
144
|
+
task.cancel()
|
|
145
|
+
|
|
146
|
+
# Stop ActionRouter
|
|
147
|
+
asyncio.create_task(self.action_router.stop())
|
|
148
|
+
|
|
149
|
+
# Stop Handlers
|
|
150
|
+
stop_all_handlers(self.handlers)
|
|
151
|
+
self.handlers = []
|
|
152
|
+
|
|
153
|
+
# Stop Watchers
|
|
154
|
+
for watcher in self.watchers:
|
|
155
|
+
asyncio.create_task(watcher.stop())
|
|
156
|
+
self.watchers = []
|
|
157
|
+
|
|
158
|
+
# Stop AgentScheduler
|
|
159
|
+
asyncio.create_task(self.agent_scheduler.stop())
|
|
160
|
+
|
|
161
|
+
# Stop EventBus
|
|
162
|
+
asyncio.create_task(event_bus.stop())
|
|
163
|
+
|
|
164
|
+
logger.info("Scheduler Service stopped")
|
|
165
|
+
|
|
166
|
+
def _setup_watchers(self):
|
|
167
|
+
"""Initialize all filesystem watchers."""
|
|
168
|
+
for project_ctx in self.project_manager.projects.values():
|
|
169
|
+
# IssueWatcher
|
|
170
|
+
config = WatchConfig(
|
|
171
|
+
path=project_ctx.issues_root,
|
|
172
|
+
patterns=["*.md"],
|
|
173
|
+
recursive=True,
|
|
174
|
+
)
|
|
175
|
+
self.watchers.append(IssueWatcher(config, event_bus))
|
|
176
|
+
|
|
177
|
+
# MemoWatcher
|
|
178
|
+
memo_path = project_ctx.path / "Memos" / "inbox.md"
|
|
179
|
+
if memo_path.exists():
|
|
180
|
+
memo_config = WatchConfig(
|
|
181
|
+
path=memo_path,
|
|
182
|
+
patterns=["*.md"],
|
|
183
|
+
)
|
|
184
|
+
self.watchers.append(MemoWatcher(memo_config, event_bus))
|
|
185
|
+
|
|
186
|
+
# TaskWatcher (if tasks.md exists)
|
|
187
|
+
task_path = project_ctx.path / "tasks.md"
|
|
188
|
+
if task_path.exists():
|
|
189
|
+
task_config = WatchConfig(
|
|
190
|
+
path=task_path,
|
|
191
|
+
patterns=["*.md"],
|
|
192
|
+
)
|
|
193
|
+
self.watchers.append(TaskWatcher(task_config, event_bus))
|
|
194
|
+
|
|
195
|
+
logger.info(f"Setup {len(self.watchers)} watchers")
|
|
196
|
+
|
|
197
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
198
|
+
"""Get scheduler service statistics."""
|
|
199
|
+
return {
|
|
200
|
+
"running": self._running,
|
|
201
|
+
"event_bus": event_bus.get_stats(),
|
|
202
|
+
"agent_scheduler": self.agent_scheduler.get_stats(),
|
|
203
|
+
"watchers": len(self.watchers),
|
|
204
|
+
"handlers": len(self.handlers),
|
|
205
|
+
"action_router": self.action_router.get_stats(),
|
|
206
|
+
"projects": len(self.project_manager.projects),
|
|
207
|
+
}
|
monoco/daemon/services.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import List, Optional, Dict, Any
|
|
|
3
3
|
from asyncio import Queue
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
from monoco.core.workspace import MonocoProject, Workspace
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger("monoco.daemon.services")
|
|
9
9
|
|
|
@@ -31,6 +31,7 @@ class Broadcaster:
|
|
|
31
31
|
if not self.subscribers:
|
|
32
32
|
return
|
|
33
33
|
|
|
34
|
+
import json
|
|
34
35
|
message = {"event": event_type, "data": json.dumps(payload)}
|
|
35
36
|
|
|
36
37
|
# Dispatch to all queues
|
|
@@ -40,12 +41,6 @@ class Broadcaster:
|
|
|
40
41
|
logger.debug(f"Broadcasted {event_type} to {len(self.subscribers)} clients.")
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
# Monitors moved to monoco.core.git and monoco.features.issue.monitor
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
from monoco.core.workspace import MonocoProject, Workspace
|
|
47
|
-
|
|
48
|
-
|
|
49
44
|
class ProjectContext:
|
|
50
45
|
"""
|
|
51
46
|
Holds the runtime state for a single project.
|
|
@@ -58,7 +53,31 @@ class ProjectContext:
|
|
|
58
53
|
self.name = project.name
|
|
59
54
|
self.path = project.path
|
|
60
55
|
self.issues_root = project.issues_root
|
|
61
|
-
|
|
56
|
+
|
|
57
|
+
async def on_upsert(issue_data: dict):
|
|
58
|
+
await broadcaster.broadcast(
|
|
59
|
+
"issue_upserted", {"issue": issue_data, "project_id": self.id}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def on_delete(issue_data: dict):
|
|
63
|
+
await broadcaster.broadcast(
|
|
64
|
+
"issue_deleted", {"id": issue_data["id"], "project_id": self.id}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
from monoco.features.issue.monitor import IssueMonitor
|
|
68
|
+
self.monitor = IssueMonitor(self.issues_root, on_upsert, on_delete)
|
|
69
|
+
|
|
70
|
+
async def notify_move(self, old_path: str, new_path: str, issue_data: dict):
|
|
71
|
+
"""Explicitly notify frontend about a logical move (Physical path changed)."""
|
|
72
|
+
await self.broadcaster.broadcast(
|
|
73
|
+
"issue_moved",
|
|
74
|
+
{
|
|
75
|
+
"old_path": old_path,
|
|
76
|
+
"new_path": new_path,
|
|
77
|
+
"issue": issue_data,
|
|
78
|
+
"project_id": self.id,
|
|
79
|
+
},
|
|
80
|
+
)
|
|
62
81
|
|
|
63
82
|
async def start(self):
|
|
64
83
|
await self.monitor.start()
|
|
@@ -113,53 +132,3 @@ class ProjectManager:
|
|
|
113
132
|
}
|
|
114
133
|
for p in self.projects.values()
|
|
115
134
|
]
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
from monoco.features.issue.monitor import IssueMonitor
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class ProjectContext:
|
|
122
|
-
"""
|
|
123
|
-
Holds the runtime state for a single project.
|
|
124
|
-
Now wraps the core MonocoProject primitive.
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
def __init__(self, project: MonocoProject, broadcaster: Broadcaster):
|
|
128
|
-
self.project = project
|
|
129
|
-
self.id = project.id
|
|
130
|
-
self.name = project.name
|
|
131
|
-
self.path = project.path
|
|
132
|
-
self.issues_root = project.issues_root
|
|
133
|
-
|
|
134
|
-
async def on_upsert(issue_data: dict):
|
|
135
|
-
await broadcaster.broadcast(
|
|
136
|
-
"issue_upserted", {"issue": issue_data, "project_id": self.id}
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
async def on_delete(issue_data: dict):
|
|
140
|
-
# We skip broadcast here if it's part of a move?
|
|
141
|
-
# Actually, standard upsert/delete is fine, but we need a specialized event for MOVE
|
|
142
|
-
# to help VS Code redirect without closing/reopening.
|
|
143
|
-
await broadcaster.broadcast(
|
|
144
|
-
"issue_deleted", {"id": issue_data["id"], "project_id": self.id}
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
self.monitor = IssueMonitor(self.issues_root, on_upsert, on_delete)
|
|
148
|
-
|
|
149
|
-
async def notify_move(self, old_path: str, new_path: str, issue_data: dict):
|
|
150
|
-
"""Explicitly notify frontend about a logical move (Physical path changed)."""
|
|
151
|
-
await self.broadcaster.broadcast(
|
|
152
|
-
"issue_moved",
|
|
153
|
-
{
|
|
154
|
-
"old_path": old_path,
|
|
155
|
-
"new_path": new_path,
|
|
156
|
-
"issue": issue_data,
|
|
157
|
-
"project_id": self.id,
|
|
158
|
-
},
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
async def start(self):
|
|
162
|
-
await self.monitor.start()
|
|
163
|
-
|
|
164
|
-
def stop(self):
|
|
165
|
-
self.monitor.stop()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, List, Any
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from monoco.features.memo.core import load_memos
|
|
4
|
+
from monoco.features.issue.models import IssueMetadata, IssueStatus, IssueStage
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from monoco.features.issue.models import IssueMetadata
|
|
8
|
+
|
|
9
|
+
class TriggerPolicy:
|
|
10
|
+
"""
|
|
11
|
+
Base class for trigger policies.
|
|
12
|
+
"""
|
|
13
|
+
def evaluate(self, context: dict) -> bool:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
class MemoAccumulationPolicy(TriggerPolicy):
|
|
17
|
+
"""
|
|
18
|
+
Trigger when pending memos exceed a threshold.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, count_threshold: int = 5):
|
|
21
|
+
self.count_threshold = count_threshold
|
|
22
|
+
|
|
23
|
+
def evaluate(self, context: dict) -> bool:
|
|
24
|
+
issues_root = context.get("issues_root")
|
|
25
|
+
if not issues_root:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
if isinstance(issues_root, str):
|
|
29
|
+
issues_root = Path(issues_root)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
memos = load_memos(issues_root)
|
|
33
|
+
pending_memos = [m for m in memos if m.status == "pending"]
|
|
34
|
+
return len(pending_memos) >= self.count_threshold
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"Error evaluating MemoAccumulationPolicy: {e}")
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
class HandoverPolicy(TriggerPolicy):
|
|
40
|
+
"""
|
|
41
|
+
Trigger when an issue enters a specific state (e.g. Open/Doing for Engineer).
|
|
42
|
+
"""
|
|
43
|
+
def __init__(self, target_status: IssueStatus, target_stage: IssueStage):
|
|
44
|
+
self.target_status = target_status
|
|
45
|
+
self.target_stage = target_stage
|
|
46
|
+
|
|
47
|
+
def evaluate(self, context: dict) -> bool:
|
|
48
|
+
issue: Optional[IssueMetadata] = context.get("issue")
|
|
49
|
+
if not issue:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
issue.status == self.target_status
|
|
54
|
+
and issue.stage == self.target_stage
|
|
55
|
+
)
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent feature module - CLI interface for agent operations.
|
|
3
|
+
|
|
4
|
+
Note: The old SessionManager/RuntimeSession/ApoptosisManager architecture
|
|
5
|
+
has been removed in FEAT-0164. This module now provides CLI commands that
|
|
6
|
+
use the new AgentScheduler abstraction from core.scheduler.
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
from .models import RoleTemplate, AgentRoleConfig as AgentConfig, SchedulerConfig
|
|
2
10
|
from .worker import Worker
|
|
3
11
|
from .config import load_scheduler_config, load_agent_config
|
|
4
12
|
from .defaults import DEFAULT_ROLES
|
|
5
|
-
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
13
|
+
|
|
14
|
+
# Re-export engines from core.scheduler for backward compatibility
|
|
15
|
+
from monoco.core.scheduler import (
|
|
16
|
+
EngineAdapter,
|
|
17
|
+
EngineFactory,
|
|
18
|
+
GeminiAdapter,
|
|
19
|
+
ClaudeAdapter,
|
|
20
|
+
QwenAdapter,
|
|
21
|
+
KimiAdapter,
|
|
22
|
+
)
|
|
8
23
|
|
|
9
24
|
__all__ = [
|
|
10
25
|
"RoleTemplate",
|
|
@@ -14,8 +29,11 @@ __all__ = [
|
|
|
14
29
|
"Worker",
|
|
15
30
|
"load_scheduler_config",
|
|
16
31
|
"DEFAULT_ROLES",
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
32
|
+
# Re-exported from core.scheduler
|
|
33
|
+
"EngineAdapter",
|
|
34
|
+
"EngineFactory",
|
|
35
|
+
"GeminiAdapter",
|
|
36
|
+
"ClaudeAdapter",
|
|
37
|
+
"QwenAdapter",
|
|
38
|
+
"KimiAdapter",
|
|
21
39
|
]
|
monoco/features/agent/adapter.py
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Dict
|
|
3
|
-
from monoco.core.
|
|
3
|
+
from monoco.core.loader import FeatureModule, FeatureMetadata
|
|
4
|
+
from monoco.core.feature import IntegrationData
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
class AgentFeature(
|
|
7
|
-
|
|
8
|
-
def name(self) -> str:
|
|
9
|
-
return "agent"
|
|
7
|
+
class AgentFeature(FeatureModule):
|
|
8
|
+
"""Agent management feature module with unified lifecycle support."""
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
@property
|
|
11
|
+
def metadata(self) -> FeatureMetadata:
|
|
12
|
+
return FeatureMetadata(
|
|
13
|
+
name="agent",
|
|
14
|
+
version="1.0.0",
|
|
15
|
+
description="Agent session and role management",
|
|
16
|
+
dependencies=["core"],
|
|
17
|
+
priority=20,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def _on_mount(self, context: "FeatureContext") -> None: # type: ignore
|
|
21
|
+
"""Agent feature doesn't require special initialization."""
|
|
13
22
|
pass
|
|
14
23
|
|
|
15
24
|
def integrate(self, root: Path, config: Dict) -> IntegrationData:
|
|
25
|
+
"""Provide integration data for agent environment."""
|
|
16
26
|
# Determine language from config, default to 'en'
|
|
17
27
|
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
18
28
|
|