claude-mpm 4.1.4__py3-none-any.whl → 4.1.5__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/tickets.py +365 -784
- claude_mpm/core/output_style_manager.py +24 -0
- claude_mpm/core/unified_agent_registry.py +46 -15
- claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
- claude_mpm/services/infrastructure/__init__.py +31 -5
- claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
- claude_mpm/services/infrastructure/monitoring/base.py +130 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +218 -0
- claude_mpm/services/infrastructure/monitoring/process.py +342 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
- claude_mpm/services/infrastructure/monitoring/service.py +367 -0
- claude_mpm/services/infrastructure/monitoring.py +67 -1030
- claude_mpm/services/project/analyzer.py +13 -4
- claude_mpm/services/project/analyzer_refactored.py +450 -0
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +410 -0
- claude_mpm/services/ticket_manager.py +5 -1
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/RECORD +41 -17
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Operation Service - Core Operations for Agent Lifecycle
|
|
4
|
+
=============================================================
|
|
5
|
+
|
|
6
|
+
Handles core agent operations (create, update, delete, restore).
|
|
7
|
+
Extracted from AgentLifecycleManager to follow Single Responsibility Principle.
|
|
8
|
+
|
|
9
|
+
Key Responsibilities:
|
|
10
|
+
- Execute agent CRUD operations
|
|
11
|
+
- Coordinate with AgentManager for file operations
|
|
12
|
+
- Track operation results and history
|
|
13
|
+
- Handle operation locking and concurrency
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
from claude_mpm.core.base_service import BaseService
|
|
24
|
+
from claude_mpm.core.unified_paths import get_path_manager
|
|
25
|
+
from claude_mpm.models.agent_definition import AgentDefinition
|
|
26
|
+
from claude_mpm.services.agents.management import AgentManager
|
|
27
|
+
from claude_mpm.services.agents.registry.modification_tracker import (
|
|
28
|
+
AgentModificationTracker,
|
|
29
|
+
ModificationTier,
|
|
30
|
+
ModificationType,
|
|
31
|
+
)
|
|
32
|
+
from claude_mpm.utils.path_operations import path_ops
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LifecycleOperation(Enum):
|
|
36
|
+
"""Agent lifecycle operations."""
|
|
37
|
+
|
|
38
|
+
CREATE = "create"
|
|
39
|
+
UPDATE = "update"
|
|
40
|
+
DELETE = "delete"
|
|
41
|
+
RESTORE = "restore"
|
|
42
|
+
MIGRATE = "migrate"
|
|
43
|
+
REPLICATE = "replicate"
|
|
44
|
+
VALIDATE = "validate"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class LifecycleOperationResult:
|
|
49
|
+
"""Result of a lifecycle operation."""
|
|
50
|
+
|
|
51
|
+
operation: LifecycleOperation
|
|
52
|
+
agent_name: str
|
|
53
|
+
success: bool
|
|
54
|
+
duration_ms: float
|
|
55
|
+
error_message: Optional[str] = None
|
|
56
|
+
modification_id: Optional[str] = None
|
|
57
|
+
persistence_id: Optional[str] = None
|
|
58
|
+
cache_invalidated: bool = False
|
|
59
|
+
registry_updated: bool = False
|
|
60
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AgentOperationService(BaseService):
|
|
64
|
+
"""
|
|
65
|
+
Service for executing agent lifecycle operations.
|
|
66
|
+
|
|
67
|
+
Responsibilities:
|
|
68
|
+
- Create, update, delete agents
|
|
69
|
+
- Coordinate with AgentManager for file operations
|
|
70
|
+
- Track operation history
|
|
71
|
+
- Handle concurrency and locking
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, agent_manager: Optional[AgentManager] = None):
|
|
75
|
+
"""Initialize the operation service."""
|
|
76
|
+
super().__init__("agent_operation_service")
|
|
77
|
+
|
|
78
|
+
# Dependencies
|
|
79
|
+
self.agent_manager = agent_manager
|
|
80
|
+
self.modification_tracker: Optional[AgentModificationTracker] = None
|
|
81
|
+
|
|
82
|
+
# Operation tracking
|
|
83
|
+
self.operation_history: List[LifecycleOperationResult] = []
|
|
84
|
+
self.active_operations: Dict[str, LifecycleOperation] = {}
|
|
85
|
+
|
|
86
|
+
# Operation lock for thread safety
|
|
87
|
+
self._operation_lock = asyncio.Lock()
|
|
88
|
+
|
|
89
|
+
# Performance metrics
|
|
90
|
+
self.metrics = {
|
|
91
|
+
"total_operations": 0,
|
|
92
|
+
"successful_operations": 0,
|
|
93
|
+
"failed_operations": 0,
|
|
94
|
+
"average_duration_ms": 0.0,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
self.logger.info("AgentOperationService initialized")
|
|
98
|
+
|
|
99
|
+
def set_modification_tracker(self, tracker: AgentModificationTracker):
|
|
100
|
+
"""Set the modification tracker dependency."""
|
|
101
|
+
self.modification_tracker = tracker
|
|
102
|
+
|
|
103
|
+
async def create_agent(
|
|
104
|
+
self,
|
|
105
|
+
agent_name: str,
|
|
106
|
+
agent_content: str,
|
|
107
|
+
tier: ModificationTier = ModificationTier.USER,
|
|
108
|
+
agent_type: str = "custom",
|
|
109
|
+
**kwargs,
|
|
110
|
+
) -> LifecycleOperationResult:
|
|
111
|
+
"""
|
|
112
|
+
Create a new agent.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
agent_name: Name of the agent
|
|
116
|
+
agent_content: Content of the agent file
|
|
117
|
+
tier: Target tier for creation
|
|
118
|
+
agent_type: Type of agent
|
|
119
|
+
**kwargs: Additional metadata
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
LifecycleOperationResult with operation details
|
|
123
|
+
"""
|
|
124
|
+
start_time = time.time()
|
|
125
|
+
|
|
126
|
+
async with self._operation_lock:
|
|
127
|
+
self.active_operations[agent_name] = LifecycleOperation.CREATE
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
# Create agent definition
|
|
131
|
+
agent_def = await self._create_agent_definition(
|
|
132
|
+
agent_name, agent_content, tier, agent_type, **kwargs
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Determine location
|
|
136
|
+
location = (
|
|
137
|
+
"project" if tier == ModificationTier.PROJECT else "framework"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Create agent using AgentManager
|
|
141
|
+
file_path = await self._execute_agent_creation(
|
|
142
|
+
agent_name, agent_def, location, tier, agent_content
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Track modification if tracker available
|
|
146
|
+
modification_id = None
|
|
147
|
+
if self.modification_tracker:
|
|
148
|
+
modification = await self.modification_tracker.track_modification(
|
|
149
|
+
agent_name=agent_name,
|
|
150
|
+
modification_type=ModificationType.CREATE,
|
|
151
|
+
file_path=str(file_path),
|
|
152
|
+
tier=tier,
|
|
153
|
+
agent_type=agent_type,
|
|
154
|
+
**kwargs,
|
|
155
|
+
)
|
|
156
|
+
modification_id = modification.modification_id
|
|
157
|
+
|
|
158
|
+
# Create result
|
|
159
|
+
result = LifecycleOperationResult(
|
|
160
|
+
operation=LifecycleOperation.CREATE,
|
|
161
|
+
agent_name=agent_name,
|
|
162
|
+
success=True,
|
|
163
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
164
|
+
modification_id=modification_id,
|
|
165
|
+
metadata={"file_path": str(file_path), "tier": tier.value},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
self._update_metrics(result)
|
|
169
|
+
self.operation_history.append(result)
|
|
170
|
+
|
|
171
|
+
self.logger.info(
|
|
172
|
+
f"Created agent '{agent_name}' in {result.duration_ms:.1f}ms"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
result = LifecycleOperationResult(
|
|
179
|
+
operation=LifecycleOperation.CREATE,
|
|
180
|
+
agent_name=agent_name,
|
|
181
|
+
success=False,
|
|
182
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
183
|
+
error_message=str(e),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self._update_metrics(result)
|
|
187
|
+
self.operation_history.append(result)
|
|
188
|
+
|
|
189
|
+
self.logger.error(f"Failed to create agent '{agent_name}': {e}")
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
finally:
|
|
193
|
+
self.active_operations.pop(agent_name, None)
|
|
194
|
+
|
|
195
|
+
async def update_agent(
|
|
196
|
+
self,
|
|
197
|
+
agent_name: str,
|
|
198
|
+
agent_content: str,
|
|
199
|
+
file_path: str,
|
|
200
|
+
tier: ModificationTier,
|
|
201
|
+
**kwargs,
|
|
202
|
+
) -> LifecycleOperationResult:
|
|
203
|
+
"""
|
|
204
|
+
Update an existing agent.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
agent_name: Name of the agent
|
|
208
|
+
agent_content: New content for the agent
|
|
209
|
+
file_path: Current file path
|
|
210
|
+
tier: Agent tier
|
|
211
|
+
**kwargs: Additional metadata
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
LifecycleOperationResult with operation details
|
|
215
|
+
"""
|
|
216
|
+
start_time = time.time()
|
|
217
|
+
|
|
218
|
+
async with self._operation_lock:
|
|
219
|
+
self.active_operations[agent_name] = LifecycleOperation.UPDATE
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
# Update agent using AgentManager
|
|
223
|
+
await self._execute_agent_update(
|
|
224
|
+
agent_name, agent_content, file_path, **kwargs
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Track modification if tracker available
|
|
228
|
+
modification_id = None
|
|
229
|
+
if self.modification_tracker:
|
|
230
|
+
modification = await self.modification_tracker.track_modification(
|
|
231
|
+
agent_name=agent_name,
|
|
232
|
+
modification_type=ModificationType.MODIFY,
|
|
233
|
+
file_path=file_path,
|
|
234
|
+
tier=tier,
|
|
235
|
+
**kwargs,
|
|
236
|
+
)
|
|
237
|
+
modification_id = modification.modification_id
|
|
238
|
+
|
|
239
|
+
# Create result
|
|
240
|
+
result = LifecycleOperationResult(
|
|
241
|
+
operation=LifecycleOperation.UPDATE,
|
|
242
|
+
agent_name=agent_name,
|
|
243
|
+
success=True,
|
|
244
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
245
|
+
modification_id=modification_id,
|
|
246
|
+
metadata={"file_path": file_path},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
self._update_metrics(result)
|
|
250
|
+
self.operation_history.append(result)
|
|
251
|
+
|
|
252
|
+
self.logger.info(
|
|
253
|
+
f"Updated agent '{agent_name}' in {result.duration_ms:.1f}ms"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return result
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
result = LifecycleOperationResult(
|
|
260
|
+
operation=LifecycleOperation.UPDATE,
|
|
261
|
+
agent_name=agent_name,
|
|
262
|
+
success=False,
|
|
263
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
264
|
+
error_message=str(e),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
self._update_metrics(result)
|
|
268
|
+
self.operation_history.append(result)
|
|
269
|
+
|
|
270
|
+
self.logger.error(f"Failed to update agent '{agent_name}': {e}")
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
finally:
|
|
274
|
+
self.active_operations.pop(agent_name, None)
|
|
275
|
+
|
|
276
|
+
async def delete_agent(
|
|
277
|
+
self,
|
|
278
|
+
agent_name: str,
|
|
279
|
+
file_path: str,
|
|
280
|
+
tier: ModificationTier,
|
|
281
|
+
create_backup: bool = True,
|
|
282
|
+
**kwargs,
|
|
283
|
+
) -> LifecycleOperationResult:
|
|
284
|
+
"""
|
|
285
|
+
Delete an agent.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
agent_name: Name of the agent
|
|
289
|
+
file_path: Path to agent file
|
|
290
|
+
tier: Agent tier
|
|
291
|
+
create_backup: Whether to create backup before deletion
|
|
292
|
+
**kwargs: Additional metadata
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
LifecycleOperationResult with operation details
|
|
296
|
+
"""
|
|
297
|
+
start_time = time.time()
|
|
298
|
+
|
|
299
|
+
async with self._operation_lock:
|
|
300
|
+
self.active_operations[agent_name] = LifecycleOperation.DELETE
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
# Create backup if requested
|
|
304
|
+
backup_path = None
|
|
305
|
+
if create_backup:
|
|
306
|
+
backup_path = await self._create_deletion_backup(
|
|
307
|
+
agent_name, file_path
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Track modification if tracker available
|
|
311
|
+
modification_id = None
|
|
312
|
+
if self.modification_tracker:
|
|
313
|
+
modification = await self.modification_tracker.track_modification(
|
|
314
|
+
agent_name=agent_name,
|
|
315
|
+
modification_type=ModificationType.DELETE,
|
|
316
|
+
file_path=file_path,
|
|
317
|
+
tier=tier,
|
|
318
|
+
backup_path=backup_path,
|
|
319
|
+
**kwargs,
|
|
320
|
+
)
|
|
321
|
+
modification_id = modification.modification_id
|
|
322
|
+
|
|
323
|
+
# Delete agent
|
|
324
|
+
await self._execute_agent_deletion(agent_name, file_path)
|
|
325
|
+
|
|
326
|
+
# Create result
|
|
327
|
+
result = LifecycleOperationResult(
|
|
328
|
+
operation=LifecycleOperation.DELETE,
|
|
329
|
+
agent_name=agent_name,
|
|
330
|
+
success=True,
|
|
331
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
332
|
+
modification_id=modification_id,
|
|
333
|
+
metadata={"backup_path": backup_path},
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
self._update_metrics(result)
|
|
337
|
+
self.operation_history.append(result)
|
|
338
|
+
|
|
339
|
+
self.logger.info(
|
|
340
|
+
f"Deleted agent '{agent_name}' in {result.duration_ms:.1f}ms"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return result
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
result = LifecycleOperationResult(
|
|
347
|
+
operation=LifecycleOperation.DELETE,
|
|
348
|
+
agent_name=agent_name,
|
|
349
|
+
success=False,
|
|
350
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
351
|
+
error_message=str(e),
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
self._update_metrics(result)
|
|
355
|
+
self.operation_history.append(result)
|
|
356
|
+
|
|
357
|
+
self.logger.error(f"Failed to delete agent '{agent_name}': {e}")
|
|
358
|
+
return result
|
|
359
|
+
|
|
360
|
+
finally:
|
|
361
|
+
self.active_operations.pop(agent_name, None)
|
|
362
|
+
|
|
363
|
+
async def _execute_agent_creation(
|
|
364
|
+
self,
|
|
365
|
+
agent_name: str,
|
|
366
|
+
agent_def: AgentDefinition,
|
|
367
|
+
location: str,
|
|
368
|
+
tier: ModificationTier,
|
|
369
|
+
agent_content: str,
|
|
370
|
+
) -> Path:
|
|
371
|
+
"""Execute agent creation through AgentManager or fallback."""
|
|
372
|
+
try:
|
|
373
|
+
if self.agent_manager:
|
|
374
|
+
file_path = await self._run_sync_in_executor(
|
|
375
|
+
self.agent_manager.create_agent,
|
|
376
|
+
agent_name,
|
|
377
|
+
agent_def,
|
|
378
|
+
location,
|
|
379
|
+
)
|
|
380
|
+
return Path(file_path)
|
|
381
|
+
# Fallback to direct file creation
|
|
382
|
+
file_path = await self._determine_agent_file_path(agent_name, tier)
|
|
383
|
+
path_ops.ensure_dir(file_path.parent)
|
|
384
|
+
path_ops.safe_write(file_path, agent_content)
|
|
385
|
+
return file_path
|
|
386
|
+
except Exception as e:
|
|
387
|
+
self.logger.error(f"AgentManager failed to create agent: {e}")
|
|
388
|
+
# Fallback to direct file creation
|
|
389
|
+
file_path = await self._determine_agent_file_path(agent_name, tier)
|
|
390
|
+
path_ops.ensure_dir(file_path.parent)
|
|
391
|
+
path_ops.safe_write(file_path, agent_content)
|
|
392
|
+
return file_path
|
|
393
|
+
|
|
394
|
+
async def _execute_agent_update(
|
|
395
|
+
self, agent_name: str, agent_content: str, file_path: str, **kwargs
|
|
396
|
+
):
|
|
397
|
+
"""Execute agent update through AgentManager or fallback."""
|
|
398
|
+
try:
|
|
399
|
+
if self.agent_manager:
|
|
400
|
+
# Read current agent to get full definition
|
|
401
|
+
current_def = await self._run_sync_in_executor(
|
|
402
|
+
self.agent_manager.read_agent, agent_name
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if current_def:
|
|
406
|
+
# Update raw content
|
|
407
|
+
current_def.raw_content = agent_content
|
|
408
|
+
|
|
409
|
+
# Apply metadata updates
|
|
410
|
+
for key, value in kwargs.items():
|
|
411
|
+
if hasattr(current_def.metadata, key):
|
|
412
|
+
setattr(current_def.metadata, key, value)
|
|
413
|
+
|
|
414
|
+
# Update via AgentManager
|
|
415
|
+
await self._run_sync_in_executor(
|
|
416
|
+
self.agent_manager.update_agent,
|
|
417
|
+
agent_name,
|
|
418
|
+
{"raw_content": agent_content},
|
|
419
|
+
True,
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
raise Exception("Could not read current agent definition")
|
|
423
|
+
else:
|
|
424
|
+
# Fallback to direct file update
|
|
425
|
+
path = Path(file_path)
|
|
426
|
+
if path_ops.validate_exists(path):
|
|
427
|
+
path_ops.safe_write(path, agent_content)
|
|
428
|
+
except Exception as e:
|
|
429
|
+
self.logger.error(f"AgentManager failed to update agent: {e}")
|
|
430
|
+
# Fallback to direct file update
|
|
431
|
+
path = Path(file_path)
|
|
432
|
+
if path_ops.validate_exists(path):
|
|
433
|
+
path_ops.safe_write(path, agent_content)
|
|
434
|
+
|
|
435
|
+
async def _execute_agent_deletion(self, agent_name: str, file_path: str):
|
|
436
|
+
"""Execute agent deletion through AgentManager or fallback."""
|
|
437
|
+
try:
|
|
438
|
+
if self.agent_manager:
|
|
439
|
+
success = await self._run_sync_in_executor(
|
|
440
|
+
self.agent_manager.delete_agent, agent_name
|
|
441
|
+
)
|
|
442
|
+
if not success:
|
|
443
|
+
raise Exception("AgentManager delete failed")
|
|
444
|
+
else:
|
|
445
|
+
# Fallback to direct file deletion
|
|
446
|
+
path = Path(file_path)
|
|
447
|
+
if path_ops.validate_exists(path):
|
|
448
|
+
path_ops.safe_delete(path)
|
|
449
|
+
except Exception as e:
|
|
450
|
+
self.logger.error(f"AgentManager failed to delete agent: {e}")
|
|
451
|
+
# Fallback to direct file deletion
|
|
452
|
+
path = Path(file_path)
|
|
453
|
+
if path_ops.validate_exists(path):
|
|
454
|
+
path_ops.safe_delete(path)
|
|
455
|
+
|
|
456
|
+
async def _determine_agent_file_path(
|
|
457
|
+
self, agent_name: str, tier: ModificationTier
|
|
458
|
+
) -> Path:
|
|
459
|
+
"""Determine appropriate file path for agent."""
|
|
460
|
+
if tier == ModificationTier.USER:
|
|
461
|
+
base_path = get_path_manager().get_user_agents_dir()
|
|
462
|
+
elif tier == ModificationTier.PROJECT:
|
|
463
|
+
base_path = get_path_manager().get_project_agents_dir()
|
|
464
|
+
else: # SYSTEM
|
|
465
|
+
base_path = Path.cwd() / "claude_pm" / "agents"
|
|
466
|
+
|
|
467
|
+
path_ops.ensure_dir(base_path)
|
|
468
|
+
return base_path / f"{agent_name}_agent.py"
|
|
469
|
+
|
|
470
|
+
async def _create_deletion_backup(
|
|
471
|
+
self, agent_name: str, file_path: str
|
|
472
|
+
) -> Optional[str]:
|
|
473
|
+
"""Create backup before agent deletion."""
|
|
474
|
+
try:
|
|
475
|
+
source_path = Path(file_path)
|
|
476
|
+
if not path_ops.validate_exists(source_path):
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
backup_dir = get_path_manager().get_tracking_dir() / "backups"
|
|
480
|
+
path_ops.ensure_dir(backup_dir)
|
|
481
|
+
|
|
482
|
+
from datetime import datetime
|
|
483
|
+
|
|
484
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
485
|
+
backup_filename = f"{agent_name}_deleted_{timestamp}{source_path.suffix}"
|
|
486
|
+
backup_path = backup_dir / backup_filename
|
|
487
|
+
|
|
488
|
+
path_ops.safe_copy(source_path, backup_path)
|
|
489
|
+
return str(backup_path)
|
|
490
|
+
|
|
491
|
+
except Exception as e:
|
|
492
|
+
self.logger.warning(
|
|
493
|
+
f"Failed to create deletion backup for {agent_name}: {e}"
|
|
494
|
+
)
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
async def _create_agent_definition(
|
|
498
|
+
self,
|
|
499
|
+
agent_name: str,
|
|
500
|
+
agent_content: str,
|
|
501
|
+
tier: ModificationTier,
|
|
502
|
+
agent_type: str,
|
|
503
|
+
**kwargs,
|
|
504
|
+
) -> AgentDefinition:
|
|
505
|
+
"""Create an AgentDefinition from parameters."""
|
|
506
|
+
# Import here to avoid circular dependency
|
|
507
|
+
from claude_mpm.services.agents.deployment.agent_definition_factory import (
|
|
508
|
+
AgentDefinitionFactory,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
factory = AgentDefinitionFactory()
|
|
512
|
+
return factory.create_agent_definition(
|
|
513
|
+
agent_name, agent_content, tier, agent_type, **kwargs
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
async def _run_sync_in_executor(self, func, *args, **kwargs):
|
|
517
|
+
"""Run a synchronous function in an executor."""
|
|
518
|
+
loop = asyncio.get_event_loop()
|
|
519
|
+
return await loop.run_in_executor(None, func, *args, **kwargs)
|
|
520
|
+
|
|
521
|
+
def _update_metrics(self, result: LifecycleOperationResult):
|
|
522
|
+
"""Update performance metrics."""
|
|
523
|
+
self.metrics["total_operations"] += 1
|
|
524
|
+
|
|
525
|
+
if result.success:
|
|
526
|
+
self.metrics["successful_operations"] += 1
|
|
527
|
+
else:
|
|
528
|
+
self.metrics["failed_operations"] += 1
|
|
529
|
+
|
|
530
|
+
# Update average duration
|
|
531
|
+
current_avg = self.metrics["average_duration_ms"]
|
|
532
|
+
total_ops = self.metrics["total_operations"]
|
|
533
|
+
self.metrics["average_duration_ms"] = (
|
|
534
|
+
current_avg * (total_ops - 1) + result.duration_ms
|
|
535
|
+
) / total_ops
|
|
536
|
+
|
|
537
|
+
def get_operation_history(
|
|
538
|
+
self, agent_name: Optional[str] = None, limit: int = 100
|
|
539
|
+
) -> List[LifecycleOperationResult]:
|
|
540
|
+
"""Get operation history with optional filtering."""
|
|
541
|
+
history = self.operation_history
|
|
542
|
+
|
|
543
|
+
if agent_name:
|
|
544
|
+
history = [op for op in history if op.agent_name == agent_name]
|
|
545
|
+
|
|
546
|
+
return sorted(history, key=lambda x: x.duration_ms, reverse=True)[:limit]
|
|
547
|
+
|
|
548
|
+
def get_active_operations(self) -> Dict[str, LifecycleOperation]:
|
|
549
|
+
"""Get currently active operations."""
|
|
550
|
+
return self.active_operations.copy()
|
|
551
|
+
|
|
552
|
+
def get_metrics(self) -> Dict[str, Any]:
|
|
553
|
+
"""Get performance metrics."""
|
|
554
|
+
return self.metrics.copy()
|
|
555
|
+
|
|
556
|
+
async def _initialize(self) -> None:
|
|
557
|
+
"""Initialize the operation service."""
|
|
558
|
+
self.logger.info("AgentOperationService initialized")
|
|
559
|
+
|
|
560
|
+
async def _cleanup(self) -> None:
|
|
561
|
+
"""Cleanup the operation service."""
|
|
562
|
+
# Wait for active operations to complete
|
|
563
|
+
while self.active_operations:
|
|
564
|
+
await asyncio.sleep(0.1)
|
|
565
|
+
|
|
566
|
+
self.logger.info("AgentOperationService cleaned up")
|
|
567
|
+
|
|
568
|
+
async def _health_check(self) -> Dict[str, bool]:
|
|
569
|
+
"""Perform health check."""
|
|
570
|
+
return {
|
|
571
|
+
"agent_manager_available": self.agent_manager is not None,
|
|
572
|
+
"no_stuck_operations": len(self.active_operations) == 0,
|
|
573
|
+
}
|