monoco-toolkit 0.3.11__py3-none-any.whl → 0.4.0__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/automation/__init__.py +40 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +805 -0
- monoco/core/config.py +29 -11
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/git.py +15 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +17 -0
- monoco/core/router/action.py +202 -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 +197 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +63 -0
- monoco/core/watcher/base.py +382 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +192 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/events.py +34 -0
- monoco/daemon/scheduler.py +157 -201
- monoco/daemon/services.py +42 -243
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/worker.py +1 -1
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +133 -60
- monoco/features/issue/core.py +385 -40
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/features/spike/commands.py +5 -3
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/execution.py +0 -67
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -127
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/agent/session.py +0 -169
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TaskWatcher - Monitors task files for changes.
|
|
3
|
+
|
|
4
|
+
Part of Layer 1 (File Watcher) in the event automation framework.
|
|
5
|
+
Emits events for task status changes and completion.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional, Set
|
|
16
|
+
|
|
17
|
+
from monoco.core.scheduler import AgentEventType, EventBus, event_bus
|
|
18
|
+
|
|
19
|
+
from .base import (
|
|
20
|
+
ChangeType,
|
|
21
|
+
FieldChange,
|
|
22
|
+
FileEvent,
|
|
23
|
+
FilesystemWatcher,
|
|
24
|
+
WatchConfig,
|
|
25
|
+
PollingWatcher,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TaskFileEvent(FileEvent):
|
|
32
|
+
"""FileEvent specific to Task files."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
path: Path,
|
|
37
|
+
change_type: ChangeType,
|
|
38
|
+
task_changes: Optional[List[Dict[str, Any]]] = None,
|
|
39
|
+
**kwargs,
|
|
40
|
+
):
|
|
41
|
+
super().__init__(
|
|
42
|
+
path=path,
|
|
43
|
+
change_type=change_type,
|
|
44
|
+
watcher_name="TaskWatcher",
|
|
45
|
+
**kwargs,
|
|
46
|
+
)
|
|
47
|
+
self.task_changes = task_changes or []
|
|
48
|
+
|
|
49
|
+
def to_agent_event_type(self) -> Optional[AgentEventType]:
|
|
50
|
+
"""Tasks map to issue updates for now."""
|
|
51
|
+
return AgentEventType.ISSUE_UPDATED
|
|
52
|
+
|
|
53
|
+
def to_payload(self) -> Dict[str, Any]:
|
|
54
|
+
"""Convert to payload with Task-specific fields."""
|
|
55
|
+
payload = super().to_payload()
|
|
56
|
+
payload["task_changes"] = self.task_changes
|
|
57
|
+
return payload
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class TaskItem:
|
|
62
|
+
"""Represents a single task item."""
|
|
63
|
+
content: str
|
|
64
|
+
state: str # " ", "x", "X", "-", "/"
|
|
65
|
+
line_number: int
|
|
66
|
+
level: int = 0
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_completed(self) -> bool:
|
|
70
|
+
return self.state.lower() == "x"
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_in_progress(self) -> bool:
|
|
74
|
+
return self.state in ("-", "/")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TaskWatcher(PollingWatcher):
|
|
78
|
+
"""
|
|
79
|
+
Watcher for task files.
|
|
80
|
+
|
|
81
|
+
Monitors task files (e.g., tasks.md, TODO.md) for:
|
|
82
|
+
- Task creation
|
|
83
|
+
- Task status changes (todo -> doing -> done)
|
|
84
|
+
- Task completion
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
>>> config = WatchConfig(
|
|
88
|
+
... path=Path("./tasks.md"),
|
|
89
|
+
... patterns=["*.md"],
|
|
90
|
+
... )
|
|
91
|
+
>>> watcher = TaskWatcher(config)
|
|
92
|
+
>>> await watcher.start()
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
# Regex to match task items
|
|
96
|
+
TASK_PATTERN = re.compile(
|
|
97
|
+
r"^(\s*)-\s*\[([ xX\-/])\]\s*(.+)$",
|
|
98
|
+
re.MULTILINE,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
config: WatchConfig,
|
|
104
|
+
event_bus: Optional[EventBus] = None,
|
|
105
|
+
name: str = "TaskWatcher",
|
|
106
|
+
):
|
|
107
|
+
super().__init__(config, event_bus, name)
|
|
108
|
+
self._task_cache: Dict[str, TaskItem] = {} # task_id -> TaskItem
|
|
109
|
+
|
|
110
|
+
async def _check_changes(self) -> None:
|
|
111
|
+
"""Check for task file changes."""
|
|
112
|
+
if not self.config.path.exists():
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
content = self._read_file_content(self.config.path) or ""
|
|
117
|
+
current_tasks = self._parse_tasks(content)
|
|
118
|
+
|
|
119
|
+
# Detect changes
|
|
120
|
+
task_changes = self._detect_task_changes(current_tasks)
|
|
121
|
+
|
|
122
|
+
if task_changes:
|
|
123
|
+
await self._emit_task_changes(task_changes)
|
|
124
|
+
|
|
125
|
+
# Update cache
|
|
126
|
+
self._task_cache = current_tasks
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Error checking task file: {e}")
|
|
130
|
+
|
|
131
|
+
def _parse_tasks(self, content: str) -> Dict[str, TaskItem]:
|
|
132
|
+
"""Parse task items from content."""
|
|
133
|
+
tasks = {}
|
|
134
|
+
lines = content.split("\n")
|
|
135
|
+
|
|
136
|
+
for line_num, line in enumerate(lines, 1):
|
|
137
|
+
match = self.TASK_PATTERN.match(line)
|
|
138
|
+
if match:
|
|
139
|
+
indent = match.group(1)
|
|
140
|
+
state = match.group(2)
|
|
141
|
+
task_content = match.group(3).strip()
|
|
142
|
+
|
|
143
|
+
# Generate task ID from content hash
|
|
144
|
+
import hashlib
|
|
145
|
+
task_id = hashlib.md5(
|
|
146
|
+
f"{line_num}:{task_content}".encode()
|
|
147
|
+
).hexdigest()[:12]
|
|
148
|
+
|
|
149
|
+
tasks[task_id] = TaskItem(
|
|
150
|
+
content=task_content,
|
|
151
|
+
state=state,
|
|
152
|
+
line_number=line_num,
|
|
153
|
+
level=len(indent) // 2,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return tasks
|
|
157
|
+
|
|
158
|
+
def _detect_task_changes(
|
|
159
|
+
self,
|
|
160
|
+
current_tasks: Dict[str, TaskItem],
|
|
161
|
+
) -> List[Dict[str, Any]]:
|
|
162
|
+
"""Detect changes between cached and current tasks."""
|
|
163
|
+
changes = []
|
|
164
|
+
current_ids = set(current_tasks.keys())
|
|
165
|
+
cached_ids = set(self._task_cache.keys())
|
|
166
|
+
|
|
167
|
+
# New tasks
|
|
168
|
+
for task_id in current_ids - cached_ids:
|
|
169
|
+
task = current_tasks[task_id]
|
|
170
|
+
changes.append({
|
|
171
|
+
"type": "created",
|
|
172
|
+
"task_id": task_id,
|
|
173
|
+
"content": task.content,
|
|
174
|
+
"state": task.state,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
# Deleted tasks
|
|
178
|
+
for task_id in cached_ids - current_ids:
|
|
179
|
+
task = self._task_cache[task_id]
|
|
180
|
+
changes.append({
|
|
181
|
+
"type": "deleted",
|
|
182
|
+
"task_id": task_id,
|
|
183
|
+
"content": task.content,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
# Modified tasks
|
|
187
|
+
for task_id in current_ids & cached_ids:
|
|
188
|
+
current = current_tasks[task_id]
|
|
189
|
+
cached = self._task_cache[task_id]
|
|
190
|
+
|
|
191
|
+
if current.state != cached.state:
|
|
192
|
+
changes.append({
|
|
193
|
+
"type": "state_changed",
|
|
194
|
+
"task_id": task_id,
|
|
195
|
+
"content": current.content,
|
|
196
|
+
"old_state": cached.state,
|
|
197
|
+
"new_state": current.state,
|
|
198
|
+
"is_completed": current.is_completed,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return changes
|
|
202
|
+
|
|
203
|
+
async def _emit_task_changes(self, changes: List[Dict[str, Any]]) -> None:
|
|
204
|
+
"""Emit events for task changes."""
|
|
205
|
+
event = TaskFileEvent(
|
|
206
|
+
path=self.config.path,
|
|
207
|
+
change_type=ChangeType.MODIFIED,
|
|
208
|
+
task_changes=changes,
|
|
209
|
+
metadata={
|
|
210
|
+
"total_changes": len(changes),
|
|
211
|
+
"completed_tasks": sum(1 for c in changes if c.get("is_completed")),
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
await self.emit(event)
|
|
215
|
+
|
|
216
|
+
# Log summary
|
|
217
|
+
created = sum(1 for c in changes if c["type"] == "created")
|
|
218
|
+
completed = sum(1 for c in changes if c["type"] == "state_changed" and c.get("is_completed"))
|
|
219
|
+
logger.debug(f"Task changes: {created} created, {completed} completed")
|
|
220
|
+
|
|
221
|
+
def get_task_stats(self) -> Dict[str, int]:
|
|
222
|
+
"""Get task statistics."""
|
|
223
|
+
total = len(self._task_cache)
|
|
224
|
+
completed = sum(1 for t in self._task_cache.values() if t.is_completed)
|
|
225
|
+
in_progress = sum(1 for t in self._task_cache.values() if t.is_in_progress)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
"total": total,
|
|
229
|
+
"completed": completed,
|
|
230
|
+
"in_progress": in_progress,
|
|
231
|
+
"pending": total - completed - in_progress,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
235
|
+
"""Get watcher statistics."""
|
|
236
|
+
stats = super().get_stats()
|
|
237
|
+
stats.update(self.get_task_stats())
|
|
238
|
+
return stats
|
monoco/daemon/app.py
CHANGED
|
@@ -5,10 +5,10 @@ from sse_starlette.sse import EventSourceResponse
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
-
from typing import Optional,
|
|
8
|
+
from typing import Optional, Dict
|
|
9
9
|
from monoco.daemon.services import Broadcaster, ProjectManager
|
|
10
10
|
from monoco.core.git import GitMonitor
|
|
11
|
-
from monoco.core.config import get_config
|
|
11
|
+
from monoco.core.config import get_config
|
|
12
12
|
from monoco.daemon.scheduler import SchedulerService
|
|
13
13
|
from monoco.daemon.mailroom_service import MailroomService
|
|
14
14
|
|
|
@@ -18,11 +18,6 @@ logger = logging.getLogger("monoco.daemon")
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from monoco.core.config import get_config
|
|
20
20
|
from monoco.features.issue.core import list_issues
|
|
21
|
-
from monoco.core.execution import (
|
|
22
|
-
scan_execution_profiles,
|
|
23
|
-
get_profile_detail,
|
|
24
|
-
ExecutionProfile,
|
|
25
|
-
)
|
|
26
21
|
|
|
27
22
|
description = """
|
|
28
23
|
Monoco Daemon Process
|
|
@@ -34,7 +29,6 @@ Monoco Daemon Process
|
|
|
34
29
|
# Service Instances
|
|
35
30
|
broadcaster = Broadcaster()
|
|
36
31
|
git_monitor: GitMonitor | None = None
|
|
37
|
-
config_monitors: List[ConfigMonitor] = []
|
|
38
32
|
project_manager: ProjectManager | None = None
|
|
39
33
|
scheduler_service: SchedulerService | None = None
|
|
40
34
|
mailroom_service: MailroomService | None = None
|
|
@@ -45,7 +39,7 @@ async def lifespan(app: FastAPI):
|
|
|
45
39
|
# Startup
|
|
46
40
|
logger.info("Starting Monoco Daemon services...")
|
|
47
41
|
|
|
48
|
-
global project_manager, git_monitor,
|
|
42
|
+
global project_manager, git_monitor, scheduler_service, mailroom_service
|
|
49
43
|
# Use MONOCO_SERVER_ROOT if set, otherwise CWD
|
|
50
44
|
env_root = os.getenv("MONOCO_SERVER_ROOT")
|
|
51
45
|
workspace_root = Path(env_root) if env_root else Path.cwd()
|
|
@@ -55,26 +49,8 @@ async def lifespan(app: FastAPI):
|
|
|
55
49
|
async def on_git_change(new_hash: str):
|
|
56
50
|
await broadcaster.broadcast("HEAD_UPDATED", {"ref": "HEAD", "hash": new_hash})
|
|
57
51
|
|
|
58
|
-
async def on_config_change(path: str):
|
|
59
|
-
logger.info(f"Config file changed: {path}, broadcasting update...")
|
|
60
|
-
await broadcaster.broadcast(
|
|
61
|
-
"CONFIG_UPDATED", {"scope": "workspace", "path": path}
|
|
62
|
-
)
|
|
63
|
-
|
|
64
52
|
git_monitor = GitMonitor(workspace_root, on_git_change)
|
|
65
53
|
|
|
66
|
-
project_config_path = get_config_path(ConfigScope.PROJECT, str(workspace_root))
|
|
67
|
-
workspace_config_path = get_config_path(ConfigScope.WORKSPACE, str(workspace_root))
|
|
68
|
-
|
|
69
|
-
config_monitors = [
|
|
70
|
-
ConfigMonitor(
|
|
71
|
-
project_config_path, lambda: on_config_change(str(project_config_path))
|
|
72
|
-
),
|
|
73
|
-
ConfigMonitor(
|
|
74
|
-
workspace_config_path, lambda: on_config_change(str(workspace_config_path))
|
|
75
|
-
),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
54
|
await project_manager.start_all()
|
|
79
55
|
# Start Scheduler
|
|
80
56
|
scheduler_service = SchedulerService(project_manager)
|
|
@@ -88,15 +64,12 @@ async def lifespan(app: FastAPI):
|
|
|
88
64
|
await mailroom_service.start()
|
|
89
65
|
|
|
90
66
|
git_task = asyncio.create_task(git_monitor.start())
|
|
91
|
-
config_tasks = [asyncio.create_task(m.start()) for m in config_monitors]
|
|
92
67
|
|
|
93
68
|
yield
|
|
94
69
|
# Shutdown
|
|
95
70
|
logger.info("Shutting down Monoco Daemon services...")
|
|
96
71
|
if git_monitor:
|
|
97
72
|
git_monitor.stop()
|
|
98
|
-
for m in config_monitors:
|
|
99
|
-
m.stop()
|
|
100
73
|
if project_manager:
|
|
101
74
|
project_manager.stop_all()
|
|
102
75
|
if scheduler_service:
|
|
@@ -105,7 +78,6 @@ async def lifespan(app: FastAPI):
|
|
|
105
78
|
await mailroom_service.stop()
|
|
106
79
|
|
|
107
80
|
await git_task
|
|
108
|
-
await asyncio.gather(*config_tasks)
|
|
109
81
|
|
|
110
82
|
|
|
111
83
|
app = FastAPI(
|
|
@@ -463,35 +435,6 @@ async def refresh_monitor():
|
|
|
463
435
|
return {"status": "refreshed", "head": current_hash}
|
|
464
436
|
|
|
465
437
|
|
|
466
|
-
# --- Execution Profiles ---
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
@app.get("/api/v1/execution/profiles", response_model=List[ExecutionProfile])
|
|
470
|
-
async def get_execution_profiles(project_id: Optional[str] = None):
|
|
471
|
-
"""
|
|
472
|
-
List all execution profiles available for the project/workspace.
|
|
473
|
-
"""
|
|
474
|
-
project = None
|
|
475
|
-
if project_id:
|
|
476
|
-
project = get_project_or_404(project_id)
|
|
477
|
-
elif project_manager and project_manager.projects:
|
|
478
|
-
# Fallback to first project if none specified
|
|
479
|
-
project = list(project_manager.projects.values())[0]
|
|
480
|
-
|
|
481
|
-
return scan_execution_profiles(project.path if project else None)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
@app.get("/api/v1/execution/profiles/detail", response_model=ExecutionProfile)
|
|
485
|
-
async def get_execution_profile_detail(path: str):
|
|
486
|
-
"""
|
|
487
|
-
Get full content of an execution profile.
|
|
488
|
-
"""
|
|
489
|
-
profile = get_profile_detail(path)
|
|
490
|
-
if not profile:
|
|
491
|
-
raise HTTPException(status_code=404, detail="Profile not found")
|
|
492
|
-
return profile
|
|
493
|
-
|
|
494
|
-
|
|
495
438
|
# --- Workspace State Management ---
|
|
496
439
|
from monoco.core.state import WorkspaceState
|
|
497
440
|
|