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.
Files changed (41) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/tickets.py +365 -784
  3. claude_mpm/core/output_style_manager.py +24 -0
  4. claude_mpm/core/unified_agent_registry.py +46 -15
  5. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  6. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  7. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  8. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  9. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  10. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  11. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  12. claude_mpm/services/infrastructure/__init__.py +31 -5
  13. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  14. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  15. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  16. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  17. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  18. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  19. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  20. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  21. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  22. claude_mpm/services/project/analyzer.py +13 -4
  23. claude_mpm/services/project/analyzer_refactored.py +450 -0
  24. claude_mpm/services/project/analyzer_v2.py +566 -0
  25. claude_mpm/services/project/architecture_analyzer.py +461 -0
  26. claude_mpm/services/project/dependency_analyzer.py +462 -0
  27. claude_mpm/services/project/language_analyzer.py +265 -0
  28. claude_mpm/services/project/metrics_collector.py +410 -0
  29. claude_mpm/services/ticket_manager.py +5 -1
  30. claude_mpm/services/ticket_services/__init__.py +26 -0
  31. claude_mpm/services/ticket_services/crud_service.py +328 -0
  32. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  33. claude_mpm/services/ticket_services/search_service.py +324 -0
  34. claude_mpm/services/ticket_services/validation_service.py +303 -0
  35. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  36. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/RECORD +41 -17
  38. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/WHEEL +0 -0
  39. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/entry_points.txt +0 -0
  40. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.5.dist-info}/licenses/LICENSE +0 -0
  41. {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
+ }