claude-mpm 0.3.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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Lifecycle Manager - ISS-0118 Integration Service
|
|
4
|
+
======================================================
|
|
5
|
+
|
|
6
|
+
Comprehensive agent lifecycle management integrating modification tracking,
|
|
7
|
+
persistence, and registry services for complete agent management across
|
|
8
|
+
the three-tier hierarchy.
|
|
9
|
+
|
|
10
|
+
Key Features:
|
|
11
|
+
- Unified agent lifecycle management (create, modify, delete, restore)
|
|
12
|
+
- Integrated modification tracking and persistence
|
|
13
|
+
- Automatic cache invalidation and registry updates
|
|
14
|
+
- Comprehensive backup and versioning system
|
|
15
|
+
- Conflict detection and resolution workflows
|
|
16
|
+
- Performance monitoring and optimization
|
|
17
|
+
|
|
18
|
+
Performance Impact:
|
|
19
|
+
- <100ms end-to-end agent operations
|
|
20
|
+
- Automatic cache coherency maintenance
|
|
21
|
+
- Intelligent persistence routing
|
|
22
|
+
- Real-time modification detection
|
|
23
|
+
|
|
24
|
+
Created for ISS-0118: Agent Registry and Hierarchical Discovery System
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import logging
|
|
29
|
+
import time
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from datetime import datetime, timedelta
|
|
32
|
+
from enum import Enum
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Dict, List, Optional, Set, Any, Tuple, Union
|
|
35
|
+
|
|
36
|
+
from claude_mpm.services.shared_prompt_cache import SharedPromptCache
|
|
37
|
+
from claude_mpm.services.agent_registry import AgentRegistry, AgentMetadata
|
|
38
|
+
from claude_mpm.services.agent_modification_tracker import (
|
|
39
|
+
AgentModificationTracker,
|
|
40
|
+
AgentModification,
|
|
41
|
+
ModificationType,
|
|
42
|
+
ModificationTier
|
|
43
|
+
)
|
|
44
|
+
from claude_mpm.services.agent_persistence_service import (
|
|
45
|
+
AgentPersistenceService,
|
|
46
|
+
PersistenceRecord,
|
|
47
|
+
PersistenceStrategy,
|
|
48
|
+
PersistenceOperation
|
|
49
|
+
)
|
|
50
|
+
from claude_mpm.core.base_service import BaseService
|
|
51
|
+
from claude_mpm.utils.path_operations import path_ops
|
|
52
|
+
from claude_mpm.utils.config_manager import ConfigurationManager
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LifecycleOperation(Enum):
|
|
56
|
+
"""Agent lifecycle operations."""
|
|
57
|
+
CREATE = "create"
|
|
58
|
+
UPDATE = "update"
|
|
59
|
+
DELETE = "delete"
|
|
60
|
+
RESTORE = "restore"
|
|
61
|
+
MIGRATE = "migrate"
|
|
62
|
+
REPLICATE = "replicate"
|
|
63
|
+
VALIDATE = "validate"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LifecycleState(Enum):
|
|
67
|
+
"""Agent lifecycle states."""
|
|
68
|
+
ACTIVE = "active"
|
|
69
|
+
MODIFIED = "modified"
|
|
70
|
+
DELETED = "deleted"
|
|
71
|
+
CONFLICTED = "conflicted"
|
|
72
|
+
MIGRATING = "migrating"
|
|
73
|
+
VALIDATING = "validating"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class AgentLifecycleRecord:
|
|
78
|
+
"""Complete lifecycle record for an agent."""
|
|
79
|
+
|
|
80
|
+
agent_name: str
|
|
81
|
+
current_state: LifecycleState
|
|
82
|
+
tier: ModificationTier
|
|
83
|
+
file_path: str
|
|
84
|
+
created_at: float
|
|
85
|
+
last_modified: float
|
|
86
|
+
version: str
|
|
87
|
+
modifications: List[str] = field(default_factory=list) # Modification IDs
|
|
88
|
+
persistence_operations: List[str] = field(default_factory=list) # Operation IDs
|
|
89
|
+
backup_paths: List[str] = field(default_factory=list)
|
|
90
|
+
validation_status: str = "valid"
|
|
91
|
+
validation_errors: List[str] = field(default_factory=list)
|
|
92
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def age_days(self) -> float:
|
|
96
|
+
"""Get age in days."""
|
|
97
|
+
return (time.time() - self.created_at) / (24 * 3600)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def last_modified_datetime(self) -> datetime:
|
|
101
|
+
"""Get last modified as datetime."""
|
|
102
|
+
return datetime.fromtimestamp(self.last_modified)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class LifecycleOperationResult:
|
|
107
|
+
"""Result of a lifecycle operation."""
|
|
108
|
+
|
|
109
|
+
operation: LifecycleOperation
|
|
110
|
+
agent_name: str
|
|
111
|
+
success: bool
|
|
112
|
+
duration_ms: float
|
|
113
|
+
error_message: Optional[str] = None
|
|
114
|
+
modification_id: Optional[str] = None
|
|
115
|
+
persistence_id: Optional[str] = None
|
|
116
|
+
cache_invalidated: bool = False
|
|
117
|
+
registry_updated: bool = False
|
|
118
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class AgentLifecycleManager(BaseService):
|
|
122
|
+
"""
|
|
123
|
+
Agent Lifecycle Manager - Unified agent management across hierarchy tiers.
|
|
124
|
+
|
|
125
|
+
Features:
|
|
126
|
+
- Complete agent lifecycle management (CRUD operations)
|
|
127
|
+
- Integrated modification tracking and persistence
|
|
128
|
+
- Automatic cache invalidation and registry synchronization
|
|
129
|
+
- Comprehensive backup and versioning system
|
|
130
|
+
- Real-time conflict detection and resolution
|
|
131
|
+
- Performance monitoring and optimization
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
135
|
+
"""Initialize the agent lifecycle manager."""
|
|
136
|
+
super().__init__("agent_lifecycle_manager", config)
|
|
137
|
+
|
|
138
|
+
# Configuration
|
|
139
|
+
self.enable_auto_backup = self.get_config("enable_auto_backup", True)
|
|
140
|
+
self.enable_auto_validation = self.get_config("enable_auto_validation", True)
|
|
141
|
+
self.enable_cache_invalidation = self.get_config("enable_cache_invalidation", True)
|
|
142
|
+
self.enable_registry_sync = self.get_config("enable_registry_sync", True)
|
|
143
|
+
self.default_persistence_strategy = PersistenceStrategy(
|
|
144
|
+
self.get_config("default_persistence_strategy", PersistenceStrategy.USER_OVERRIDE.value)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Core services
|
|
148
|
+
self.shared_cache: Optional[SharedPromptCache] = None
|
|
149
|
+
self.agent_registry: Optional[AgentRegistry] = None
|
|
150
|
+
self.modification_tracker: Optional[AgentModificationTracker] = None
|
|
151
|
+
self.persistence_service: Optional[AgentPersistenceService] = None
|
|
152
|
+
|
|
153
|
+
# Lifecycle tracking
|
|
154
|
+
self.agent_records: Dict[str, AgentLifecycleRecord] = {}
|
|
155
|
+
self.operation_history: List[LifecycleOperationResult] = []
|
|
156
|
+
self.active_operations: Dict[str, LifecycleOperation] = {}
|
|
157
|
+
|
|
158
|
+
# Performance metrics
|
|
159
|
+
self.performance_metrics = {
|
|
160
|
+
'total_operations': 0,
|
|
161
|
+
'successful_operations': 0,
|
|
162
|
+
'failed_operations': 0,
|
|
163
|
+
'average_duration_ms': 0.0,
|
|
164
|
+
'cache_hit_rate': 0.0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Operation lock for thread safety
|
|
168
|
+
self._operation_lock = asyncio.Lock()
|
|
169
|
+
|
|
170
|
+
# Configuration manager
|
|
171
|
+
self.config_mgr = ConfigurationManager(cache_enabled=True)
|
|
172
|
+
|
|
173
|
+
self.logger.info("AgentLifecycleManager initialized")
|
|
174
|
+
|
|
175
|
+
async def _initialize(self) -> None:
|
|
176
|
+
"""Initialize the lifecycle manager."""
|
|
177
|
+
self.logger.info("Initializing AgentLifecycleManager...")
|
|
178
|
+
|
|
179
|
+
# Initialize core services
|
|
180
|
+
await self._initialize_core_services()
|
|
181
|
+
|
|
182
|
+
# Load existing agent records
|
|
183
|
+
await self._load_agent_records()
|
|
184
|
+
|
|
185
|
+
# Start service integrations
|
|
186
|
+
await self._setup_service_integrations()
|
|
187
|
+
|
|
188
|
+
# Perform initial registry sync
|
|
189
|
+
if self.enable_registry_sync:
|
|
190
|
+
await self._sync_with_registry()
|
|
191
|
+
|
|
192
|
+
self.logger.info("AgentLifecycleManager initialized successfully")
|
|
193
|
+
|
|
194
|
+
async def _cleanup(self) -> None:
|
|
195
|
+
"""Cleanup lifecycle manager resources."""
|
|
196
|
+
self.logger.info("Cleaning up AgentLifecycleManager...")
|
|
197
|
+
|
|
198
|
+
# Save agent records
|
|
199
|
+
await self._save_agent_records()
|
|
200
|
+
|
|
201
|
+
# Stop core services if we own them
|
|
202
|
+
await self._cleanup_core_services()
|
|
203
|
+
|
|
204
|
+
self.logger.info("AgentLifecycleManager cleaned up")
|
|
205
|
+
|
|
206
|
+
async def _health_check(self) -> Dict[str, bool]:
|
|
207
|
+
"""Perform lifecycle manager health checks."""
|
|
208
|
+
checks = {}
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Check core service availability
|
|
212
|
+
checks["cache_service"] = self.shared_cache is not None
|
|
213
|
+
checks["registry_service"] = self.agent_registry is not None
|
|
214
|
+
checks["tracker_service"] = self.modification_tracker is not None
|
|
215
|
+
checks["persistence_service"] = self.persistence_service is not None
|
|
216
|
+
|
|
217
|
+
# Check operation capabilities
|
|
218
|
+
checks["can_create_agents"] = await self._test_create_capability()
|
|
219
|
+
checks["can_modify_agents"] = await self._test_modify_capability()
|
|
220
|
+
checks["can_delete_agents"] = await self._test_delete_capability()
|
|
221
|
+
|
|
222
|
+
# Check data integrity
|
|
223
|
+
checks["agent_records_valid"] = len(self.agent_records) >= 0
|
|
224
|
+
checks["operation_history_valid"] = len(self.operation_history) >= 0
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
self.logger.error(f"Lifecycle manager health check failed: {e}")
|
|
228
|
+
checks["health_check_error"] = False
|
|
229
|
+
|
|
230
|
+
return checks
|
|
231
|
+
|
|
232
|
+
async def _initialize_core_services(self) -> None:
|
|
233
|
+
"""Initialize core service dependencies."""
|
|
234
|
+
try:
|
|
235
|
+
# Initialize SharedPromptCache
|
|
236
|
+
self.shared_cache = SharedPromptCache.get_instance()
|
|
237
|
+
|
|
238
|
+
# Initialize AgentRegistry
|
|
239
|
+
self.agent_registry = AgentRegistry(cache_service=self.shared_cache)
|
|
240
|
+
|
|
241
|
+
# Initialize AgentModificationTracker
|
|
242
|
+
self.modification_tracker = AgentModificationTracker()
|
|
243
|
+
await self.modification_tracker.start()
|
|
244
|
+
|
|
245
|
+
# Initialize AgentPersistenceService
|
|
246
|
+
self.persistence_service = AgentPersistenceService()
|
|
247
|
+
await self.persistence_service.start()
|
|
248
|
+
|
|
249
|
+
self.logger.info("Core services initialized successfully")
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
self.logger.error(f"Failed to initialize core services: {e}")
|
|
253
|
+
raise
|
|
254
|
+
|
|
255
|
+
async def _setup_service_integrations(self) -> None:
|
|
256
|
+
"""Set up integrations between services."""
|
|
257
|
+
try:
|
|
258
|
+
# Register modification callback
|
|
259
|
+
if self.modification_tracker:
|
|
260
|
+
self.modification_tracker.register_modification_callback(
|
|
261
|
+
self._handle_modification_event
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
self.logger.debug("Service integrations set up successfully")
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
self.logger.warning(f"Failed to setup some service integrations: {e}")
|
|
268
|
+
|
|
269
|
+
async def _load_agent_records(self) -> None:
|
|
270
|
+
"""Load existing agent lifecycle records."""
|
|
271
|
+
try:
|
|
272
|
+
records_file = Path.home() / '.claude-pm' / 'agent_tracking' / 'lifecycle_records.json'
|
|
273
|
+
if path_ops.validate_exists(records_file):
|
|
274
|
+
data = self.config_mgr.load_json(records_file)
|
|
275
|
+
|
|
276
|
+
for agent_name, record_data in data.items():
|
|
277
|
+
record = AgentLifecycleRecord(**record_data)
|
|
278
|
+
# Convert string enum back to enum
|
|
279
|
+
record.current_state = LifecycleState(record_data['current_state'])
|
|
280
|
+
record.tier = ModificationTier(record_data['tier'])
|
|
281
|
+
self.agent_records[agent_name] = record
|
|
282
|
+
|
|
283
|
+
self.logger.info(f"Loaded {len(self.agent_records)} agent records")
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
self.logger.warning(f"Failed to load agent records: {e}")
|
|
287
|
+
|
|
288
|
+
async def _save_agent_records(self) -> None:
|
|
289
|
+
"""Save agent lifecycle records to disk."""
|
|
290
|
+
try:
|
|
291
|
+
records_file = Path.home() / '.claude-pm' / 'agent_tracking' / 'lifecycle_records.json'
|
|
292
|
+
path_ops.ensure_dir(records_file.parent)
|
|
293
|
+
|
|
294
|
+
data = {}
|
|
295
|
+
for agent_name, record in self.agent_records.items():
|
|
296
|
+
record_dict = record.__dict__.copy()
|
|
297
|
+
# Convert enums to strings for JSON serialization
|
|
298
|
+
record_dict['current_state'] = record.current_state.value
|
|
299
|
+
record_dict['tier'] = record.tier.value
|
|
300
|
+
data[agent_name] = record_dict
|
|
301
|
+
|
|
302
|
+
# Use save_json with custom encoder for datetime serialization
|
|
303
|
+
import json
|
|
304
|
+
|
|
305
|
+
# First convert to JSON string with custom encoder, then save
|
|
306
|
+
json_str = json.dumps(data, indent=2, default=str)
|
|
307
|
+
records_file.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
with open(records_file, 'w', encoding='utf-8') as f:
|
|
309
|
+
f.write(json_str)
|
|
310
|
+
|
|
311
|
+
self.logger.debug(f"Saved {len(self.agent_records)} agent records")
|
|
312
|
+
|
|
313
|
+
except Exception as e:
|
|
314
|
+
self.logger.error(f"Failed to save agent records: {e}")
|
|
315
|
+
|
|
316
|
+
async def _sync_with_registry(self) -> None:
|
|
317
|
+
"""Synchronize with agent registry."""
|
|
318
|
+
try:
|
|
319
|
+
if not self.agent_registry:
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
# Discover all agents via registry
|
|
323
|
+
await self.agent_registry.discover_agents()
|
|
324
|
+
all_agents = await self.agent_registry.list_agents()
|
|
325
|
+
|
|
326
|
+
# Update lifecycle records with registry data
|
|
327
|
+
for agent_metadata in all_agents:
|
|
328
|
+
if agent_metadata.name not in self.agent_records:
|
|
329
|
+
# Create new lifecycle record
|
|
330
|
+
tier_map = {
|
|
331
|
+
'project': ModificationTier.PROJECT,
|
|
332
|
+
'user': ModificationTier.USER,
|
|
333
|
+
'system': ModificationTier.SYSTEM
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
record = AgentLifecycleRecord(
|
|
337
|
+
agent_name=agent_metadata.name,
|
|
338
|
+
current_state=LifecycleState.ACTIVE,
|
|
339
|
+
tier=tier_map.get(agent_metadata.tier, ModificationTier.USER),
|
|
340
|
+
file_path=agent_metadata.path,
|
|
341
|
+
created_at=agent_metadata.last_modified or time.time(),
|
|
342
|
+
last_modified=agent_metadata.last_modified or time.time(),
|
|
343
|
+
version="1.0.0",
|
|
344
|
+
metadata={
|
|
345
|
+
'type': agent_metadata.type,
|
|
346
|
+
'description': agent_metadata.description,
|
|
347
|
+
'capabilities': agent_metadata.capabilities,
|
|
348
|
+
'validated': agent_metadata.validated
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
self.agent_records[agent_metadata.name] = record
|
|
353
|
+
|
|
354
|
+
self.logger.info(f"Synchronized with registry: {len(all_agents)} agents")
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
self.logger.error(f"Failed to sync with registry: {e}")
|
|
358
|
+
|
|
359
|
+
async def create_agent(self,
|
|
360
|
+
agent_name: str,
|
|
361
|
+
agent_content: str,
|
|
362
|
+
tier: ModificationTier = ModificationTier.USER,
|
|
363
|
+
agent_type: str = "custom",
|
|
364
|
+
**kwargs) -> LifecycleOperationResult:
|
|
365
|
+
"""
|
|
366
|
+
Create a new agent with complete lifecycle tracking.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
agent_name: Name of the agent to create
|
|
370
|
+
agent_content: Content of the agent file
|
|
371
|
+
tier: Target tier for creation
|
|
372
|
+
agent_type: Type of agent (for classification)
|
|
373
|
+
**kwargs: Additional metadata
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
LifecycleOperationResult with operation details
|
|
377
|
+
"""
|
|
378
|
+
start_time = time.time()
|
|
379
|
+
|
|
380
|
+
async with self._operation_lock:
|
|
381
|
+
self.active_operations[agent_name] = LifecycleOperation.CREATE
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
# Check if agent already exists
|
|
385
|
+
if agent_name in self.agent_records:
|
|
386
|
+
return LifecycleOperationResult(
|
|
387
|
+
operation=LifecycleOperation.CREATE,
|
|
388
|
+
agent_name=agent_name,
|
|
389
|
+
success=False,
|
|
390
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
391
|
+
error_message="Agent already exists"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Determine file path
|
|
395
|
+
file_path = await self._determine_agent_file_path(agent_name, tier)
|
|
396
|
+
|
|
397
|
+
# Track modification
|
|
398
|
+
modification = await self.modification_tracker.track_modification(
|
|
399
|
+
agent_name=agent_name,
|
|
400
|
+
modification_type=ModificationType.CREATE,
|
|
401
|
+
file_path=str(file_path),
|
|
402
|
+
tier=tier,
|
|
403
|
+
agent_type=agent_type,
|
|
404
|
+
**kwargs
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Persist agent
|
|
408
|
+
persistence_record = await self.persistence_service.persist_agent(
|
|
409
|
+
agent_name=agent_name,
|
|
410
|
+
agent_content=agent_content,
|
|
411
|
+
source_tier=tier,
|
|
412
|
+
target_tier=tier,
|
|
413
|
+
strategy=self.default_persistence_strategy
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Create lifecycle record
|
|
417
|
+
lifecycle_record = AgentLifecycleRecord(
|
|
418
|
+
agent_name=agent_name,
|
|
419
|
+
current_state=LifecycleState.ACTIVE,
|
|
420
|
+
tier=tier,
|
|
421
|
+
file_path=str(file_path),
|
|
422
|
+
created_at=time.time(),
|
|
423
|
+
last_modified=time.time(),
|
|
424
|
+
version="1.0.0",
|
|
425
|
+
modifications=[modification.modification_id],
|
|
426
|
+
persistence_operations=[persistence_record.operation_id],
|
|
427
|
+
metadata={
|
|
428
|
+
'agent_type': agent_type,
|
|
429
|
+
**kwargs
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
self.agent_records[agent_name] = lifecycle_record
|
|
434
|
+
|
|
435
|
+
# Invalidate cache and update registry
|
|
436
|
+
cache_invalidated = await self._invalidate_agent_cache(agent_name)
|
|
437
|
+
registry_updated = await self._update_registry(agent_name)
|
|
438
|
+
|
|
439
|
+
# Create result
|
|
440
|
+
result = LifecycleOperationResult(
|
|
441
|
+
operation=LifecycleOperation.CREATE,
|
|
442
|
+
agent_name=agent_name,
|
|
443
|
+
success=persistence_record.success,
|
|
444
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
445
|
+
modification_id=modification.modification_id,
|
|
446
|
+
persistence_id=persistence_record.operation_id,
|
|
447
|
+
cache_invalidated=cache_invalidated,
|
|
448
|
+
registry_updated=registry_updated,
|
|
449
|
+
metadata={'file_path': str(file_path)}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if not persistence_record.success:
|
|
453
|
+
result.error_message = persistence_record.error_message
|
|
454
|
+
lifecycle_record.current_state = LifecycleState.CONFLICTED
|
|
455
|
+
|
|
456
|
+
# Update performance metrics
|
|
457
|
+
await self._update_performance_metrics(result)
|
|
458
|
+
|
|
459
|
+
self.operation_history.append(result)
|
|
460
|
+
self.logger.info(f"Created agent '{agent_name}' in {result.duration_ms:.1f}ms")
|
|
461
|
+
|
|
462
|
+
return result
|
|
463
|
+
|
|
464
|
+
except Exception as e:
|
|
465
|
+
result = LifecycleOperationResult(
|
|
466
|
+
operation=LifecycleOperation.CREATE,
|
|
467
|
+
agent_name=agent_name,
|
|
468
|
+
success=False,
|
|
469
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
470
|
+
error_message=str(e)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
self.operation_history.append(result)
|
|
474
|
+
await self._update_performance_metrics(result)
|
|
475
|
+
|
|
476
|
+
self.logger.error(f"Failed to create agent '{agent_name}': {e}")
|
|
477
|
+
return result
|
|
478
|
+
|
|
479
|
+
finally:
|
|
480
|
+
self.active_operations.pop(agent_name, None)
|
|
481
|
+
|
|
482
|
+
async def update_agent(self,
|
|
483
|
+
agent_name: str,
|
|
484
|
+
agent_content: str,
|
|
485
|
+
**kwargs) -> LifecycleOperationResult:
|
|
486
|
+
"""
|
|
487
|
+
Update an existing agent with lifecycle tracking.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
agent_name: Name of the agent to update
|
|
491
|
+
agent_content: New content for the agent
|
|
492
|
+
**kwargs: Additional metadata
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
LifecycleOperationResult with operation details
|
|
496
|
+
"""
|
|
497
|
+
start_time = time.time()
|
|
498
|
+
|
|
499
|
+
async with self._operation_lock:
|
|
500
|
+
self.active_operations[agent_name] = LifecycleOperation.UPDATE
|
|
501
|
+
|
|
502
|
+
try:
|
|
503
|
+
# Check if agent exists
|
|
504
|
+
if agent_name not in self.agent_records:
|
|
505
|
+
return LifecycleOperationResult(
|
|
506
|
+
operation=LifecycleOperation.UPDATE,
|
|
507
|
+
agent_name=agent_name,
|
|
508
|
+
success=False,
|
|
509
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
510
|
+
error_message="Agent not found"
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
record = self.agent_records[agent_name]
|
|
514
|
+
|
|
515
|
+
# Track modification
|
|
516
|
+
modification = await self.modification_tracker.track_modification(
|
|
517
|
+
agent_name=agent_name,
|
|
518
|
+
modification_type=ModificationType.MODIFY,
|
|
519
|
+
file_path=record.file_path,
|
|
520
|
+
tier=record.tier,
|
|
521
|
+
**kwargs
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
# Persist agent
|
|
525
|
+
persistence_record = await self.persistence_service.persist_agent(
|
|
526
|
+
agent_name=agent_name,
|
|
527
|
+
agent_content=agent_content,
|
|
528
|
+
source_tier=record.tier,
|
|
529
|
+
strategy=self.default_persistence_strategy
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Update lifecycle record
|
|
533
|
+
record.current_state = LifecycleState.MODIFIED
|
|
534
|
+
record.last_modified = time.time()
|
|
535
|
+
record.modifications.append(modification.modification_id)
|
|
536
|
+
record.persistence_operations.append(persistence_record.operation_id)
|
|
537
|
+
|
|
538
|
+
# Increment version
|
|
539
|
+
current_version = record.version.split('.')
|
|
540
|
+
current_version[-1] = str(int(current_version[-1]) + 1)
|
|
541
|
+
record.version = '.'.join(current_version)
|
|
542
|
+
|
|
543
|
+
# Invalidate cache and update registry
|
|
544
|
+
cache_invalidated = await self._invalidate_agent_cache(agent_name)
|
|
545
|
+
registry_updated = await self._update_registry(agent_name)
|
|
546
|
+
|
|
547
|
+
# Create result
|
|
548
|
+
result = LifecycleOperationResult(
|
|
549
|
+
operation=LifecycleOperation.UPDATE,
|
|
550
|
+
agent_name=agent_name,
|
|
551
|
+
success=persistence_record.success,
|
|
552
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
553
|
+
modification_id=modification.modification_id,
|
|
554
|
+
persistence_id=persistence_record.operation_id,
|
|
555
|
+
cache_invalidated=cache_invalidated,
|
|
556
|
+
registry_updated=registry_updated,
|
|
557
|
+
metadata={'new_version': record.version}
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
if not persistence_record.success:
|
|
561
|
+
result.error_message = persistence_record.error_message
|
|
562
|
+
record.current_state = LifecycleState.CONFLICTED
|
|
563
|
+
|
|
564
|
+
# Update performance metrics
|
|
565
|
+
await self._update_performance_metrics(result)
|
|
566
|
+
|
|
567
|
+
self.operation_history.append(result)
|
|
568
|
+
self.logger.info(f"Updated agent '{agent_name}' to version {record.version} in {result.duration_ms:.1f}ms")
|
|
569
|
+
|
|
570
|
+
return result
|
|
571
|
+
|
|
572
|
+
except Exception as e:
|
|
573
|
+
result = LifecycleOperationResult(
|
|
574
|
+
operation=LifecycleOperation.UPDATE,
|
|
575
|
+
agent_name=agent_name,
|
|
576
|
+
success=False,
|
|
577
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
578
|
+
error_message=str(e)
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
self.operation_history.append(result)
|
|
582
|
+
await self._update_performance_metrics(result)
|
|
583
|
+
|
|
584
|
+
self.logger.error(f"Failed to update agent '{agent_name}': {e}")
|
|
585
|
+
return result
|
|
586
|
+
|
|
587
|
+
finally:
|
|
588
|
+
self.active_operations.pop(agent_name, None)
|
|
589
|
+
|
|
590
|
+
async def delete_agent(self, agent_name: str, **kwargs) -> LifecycleOperationResult:
|
|
591
|
+
"""
|
|
592
|
+
Delete an agent with lifecycle tracking.
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
agent_name: Name of the agent to delete
|
|
596
|
+
**kwargs: Additional metadata
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
LifecycleOperationResult with operation details
|
|
600
|
+
"""
|
|
601
|
+
start_time = time.time()
|
|
602
|
+
|
|
603
|
+
async with self._operation_lock:
|
|
604
|
+
self.active_operations[agent_name] = LifecycleOperation.DELETE
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
# Check if agent exists
|
|
608
|
+
if agent_name not in self.agent_records:
|
|
609
|
+
return LifecycleOperationResult(
|
|
610
|
+
operation=LifecycleOperation.DELETE,
|
|
611
|
+
agent_name=agent_name,
|
|
612
|
+
success=False,
|
|
613
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
614
|
+
error_message="Agent not found"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
record = self.agent_records[agent_name]
|
|
618
|
+
|
|
619
|
+
# Create backup before deletion
|
|
620
|
+
backup_path = None
|
|
621
|
+
if self.enable_auto_backup:
|
|
622
|
+
backup_path = await self._create_deletion_backup(agent_name, record)
|
|
623
|
+
|
|
624
|
+
# Track modification
|
|
625
|
+
modification = await self.modification_tracker.track_modification(
|
|
626
|
+
agent_name=agent_name,
|
|
627
|
+
modification_type=ModificationType.DELETE,
|
|
628
|
+
file_path=record.file_path,
|
|
629
|
+
tier=record.tier,
|
|
630
|
+
backup_path=backup_path,
|
|
631
|
+
**kwargs
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Delete file
|
|
635
|
+
file_path = Path(record.file_path)
|
|
636
|
+
if path_ops.validate_exists(file_path):
|
|
637
|
+
path_ops.safe_delete(file_path)
|
|
638
|
+
|
|
639
|
+
# Update lifecycle record
|
|
640
|
+
record.current_state = LifecycleState.DELETED
|
|
641
|
+
record.last_modified = time.time()
|
|
642
|
+
record.modifications.append(modification.modification_id)
|
|
643
|
+
if backup_path:
|
|
644
|
+
record.backup_paths.append(backup_path)
|
|
645
|
+
|
|
646
|
+
# Invalidate cache and update registry
|
|
647
|
+
cache_invalidated = await self._invalidate_agent_cache(agent_name)
|
|
648
|
+
registry_updated = await self._update_registry(agent_name)
|
|
649
|
+
|
|
650
|
+
# Create result
|
|
651
|
+
result = LifecycleOperationResult(
|
|
652
|
+
operation=LifecycleOperation.DELETE,
|
|
653
|
+
agent_name=agent_name,
|
|
654
|
+
success=True,
|
|
655
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
656
|
+
modification_id=modification.modification_id,
|
|
657
|
+
cache_invalidated=cache_invalidated,
|
|
658
|
+
registry_updated=registry_updated,
|
|
659
|
+
metadata={'backup_path': backup_path}
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Update performance metrics
|
|
663
|
+
await self._update_performance_metrics(result)
|
|
664
|
+
|
|
665
|
+
self.operation_history.append(result)
|
|
666
|
+
self.logger.info(f"Deleted agent '{agent_name}' in {result.duration_ms:.1f}ms")
|
|
667
|
+
|
|
668
|
+
return result
|
|
669
|
+
|
|
670
|
+
except Exception as e:
|
|
671
|
+
result = LifecycleOperationResult(
|
|
672
|
+
operation=LifecycleOperation.DELETE,
|
|
673
|
+
agent_name=agent_name,
|
|
674
|
+
success=False,
|
|
675
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
676
|
+
error_message=str(e)
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
self.operation_history.append(result)
|
|
680
|
+
await self._update_performance_metrics(result)
|
|
681
|
+
|
|
682
|
+
self.logger.error(f"Failed to delete agent '{agent_name}': {e}")
|
|
683
|
+
return result
|
|
684
|
+
|
|
685
|
+
finally:
|
|
686
|
+
self.active_operations.pop(agent_name, None)
|
|
687
|
+
|
|
688
|
+
async def _determine_agent_file_path(self, agent_name: str, tier: ModificationTier) -> Path:
|
|
689
|
+
"""Determine appropriate file path for agent."""
|
|
690
|
+
if tier == ModificationTier.USER:
|
|
691
|
+
base_path = Path.home() / '.claude-pm' / 'agents'
|
|
692
|
+
elif tier == ModificationTier.PROJECT:
|
|
693
|
+
base_path = Path.cwd() / '.claude-pm' / 'agents'
|
|
694
|
+
else: # SYSTEM
|
|
695
|
+
base_path = Path.cwd() / 'claude_pm' / 'agents'
|
|
696
|
+
|
|
697
|
+
path_ops.ensure_dir(base_path)
|
|
698
|
+
return base_path / f"{agent_name}_agent.py"
|
|
699
|
+
|
|
700
|
+
async def _create_deletion_backup(self, agent_name: str, record: AgentLifecycleRecord) -> Optional[str]:
|
|
701
|
+
"""Create backup before agent deletion."""
|
|
702
|
+
try:
|
|
703
|
+
source_path = Path(record.file_path)
|
|
704
|
+
if not path_ops.validate_exists(source_path):
|
|
705
|
+
return None
|
|
706
|
+
|
|
707
|
+
backup_dir = Path.home() / '.claude-pm' / 'agent_tracking' / 'backups'
|
|
708
|
+
path_ops.ensure_dir(backup_dir)
|
|
709
|
+
|
|
710
|
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
711
|
+
backup_filename = f"{agent_name}_deleted_{timestamp}{source_path.suffix}"
|
|
712
|
+
backup_path = backup_dir / backup_filename
|
|
713
|
+
|
|
714
|
+
path_ops.safe_copy(source_path, backup_path)
|
|
715
|
+
return str(backup_path)
|
|
716
|
+
|
|
717
|
+
except Exception as e:
|
|
718
|
+
self.logger.warning(f"Failed to create deletion backup for {agent_name}: {e}")
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
async def _invalidate_agent_cache(self, agent_name: str) -> bool:
|
|
722
|
+
"""Invalidate cache entries for agent."""
|
|
723
|
+
if not self.enable_cache_invalidation or not self.shared_cache:
|
|
724
|
+
return False
|
|
725
|
+
|
|
726
|
+
try:
|
|
727
|
+
patterns = [
|
|
728
|
+
f"agent_profile:{agent_name}:*",
|
|
729
|
+
f"task_prompt:{agent_name}:*",
|
|
730
|
+
f"agent_registry_discovery",
|
|
731
|
+
f"agent_profile_enhanced:{agent_name}:*"
|
|
732
|
+
]
|
|
733
|
+
|
|
734
|
+
for pattern in patterns:
|
|
735
|
+
await asyncio.get_event_loop().run_in_executor(
|
|
736
|
+
None,
|
|
737
|
+
lambda p=pattern: self.shared_cache.invalidate(p)
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
return True
|
|
741
|
+
|
|
742
|
+
except Exception as e:
|
|
743
|
+
self.logger.warning(f"Failed to invalidate cache for {agent_name}: {e}")
|
|
744
|
+
return False
|
|
745
|
+
|
|
746
|
+
async def _update_registry(self, agent_name: str) -> bool:
|
|
747
|
+
"""Update agent registry after modification."""
|
|
748
|
+
if not self.enable_registry_sync or not self.agent_registry:
|
|
749
|
+
return False
|
|
750
|
+
|
|
751
|
+
try:
|
|
752
|
+
# Refresh specific agent in registry
|
|
753
|
+
await self.agent_registry.refresh_agent(agent_name)
|
|
754
|
+
return True
|
|
755
|
+
|
|
756
|
+
except Exception as e:
|
|
757
|
+
self.logger.warning(f"Failed to update registry for {agent_name}: {e}")
|
|
758
|
+
return False
|
|
759
|
+
|
|
760
|
+
async def _update_performance_metrics(self, result: LifecycleOperationResult) -> None:
|
|
761
|
+
"""Update performance metrics with operation result."""
|
|
762
|
+
self.performance_metrics['total_operations'] += 1
|
|
763
|
+
|
|
764
|
+
if result.success:
|
|
765
|
+
self.performance_metrics['successful_operations'] += 1
|
|
766
|
+
else:
|
|
767
|
+
self.performance_metrics['failed_operations'] += 1
|
|
768
|
+
|
|
769
|
+
# Update average duration
|
|
770
|
+
total_ops = self.performance_metrics['total_operations']
|
|
771
|
+
current_avg = self.performance_metrics['average_duration_ms']
|
|
772
|
+
new_avg = ((current_avg * (total_ops - 1)) + result.duration_ms) / total_ops
|
|
773
|
+
self.performance_metrics['average_duration_ms'] = new_avg
|
|
774
|
+
|
|
775
|
+
# Update cache hit rate if cache was involved
|
|
776
|
+
if result.cache_invalidated:
|
|
777
|
+
# This would be more sophisticated in practice
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
async def _handle_modification_event(self, modification: AgentModification) -> None:
|
|
781
|
+
"""Handle modification events from tracker."""
|
|
782
|
+
try:
|
|
783
|
+
agent_name = modification.agent_name
|
|
784
|
+
|
|
785
|
+
# Update lifecycle record if exists
|
|
786
|
+
if agent_name in self.agent_records:
|
|
787
|
+
record = self.agent_records[agent_name]
|
|
788
|
+
record.last_modified = modification.timestamp
|
|
789
|
+
record.modifications.append(modification.modification_id)
|
|
790
|
+
|
|
791
|
+
# Update state based on modification type
|
|
792
|
+
if modification.modification_type == ModificationType.DELETE:
|
|
793
|
+
record.current_state = LifecycleState.DELETED
|
|
794
|
+
elif modification.modification_type in [ModificationType.CREATE, ModificationType.MODIFY]:
|
|
795
|
+
record.current_state = LifecycleState.MODIFIED
|
|
796
|
+
|
|
797
|
+
self.logger.debug(f"Updated lifecycle record for {agent_name} due to {modification.modification_type.value}")
|
|
798
|
+
|
|
799
|
+
except Exception as e:
|
|
800
|
+
self.logger.error(f"Error handling modification event: {e}")
|
|
801
|
+
|
|
802
|
+
async def _test_create_capability(self) -> bool:
|
|
803
|
+
"""Test agent creation capability."""
|
|
804
|
+
try:
|
|
805
|
+
# This would test if we can create agents in the configured tiers
|
|
806
|
+
return (self.modification_tracker is not None and
|
|
807
|
+
self.persistence_service is not None)
|
|
808
|
+
except Exception:
|
|
809
|
+
return False
|
|
810
|
+
|
|
811
|
+
async def _test_modify_capability(self) -> bool:
|
|
812
|
+
"""Test agent modification capability."""
|
|
813
|
+
try:
|
|
814
|
+
# This would test if we can modify existing agents
|
|
815
|
+
return (self.modification_tracker is not None and
|
|
816
|
+
self.persistence_service is not None)
|
|
817
|
+
except Exception:
|
|
818
|
+
return False
|
|
819
|
+
|
|
820
|
+
async def _test_delete_capability(self) -> bool:
|
|
821
|
+
"""Test agent deletion capability."""
|
|
822
|
+
try:
|
|
823
|
+
# This would test if we can delete agents
|
|
824
|
+
return self.modification_tracker is not None
|
|
825
|
+
except Exception:
|
|
826
|
+
return False
|
|
827
|
+
|
|
828
|
+
async def _cleanup_core_services(self) -> None:
|
|
829
|
+
"""Cleanup core services if we manage their lifecycle."""
|
|
830
|
+
try:
|
|
831
|
+
if self.modification_tracker:
|
|
832
|
+
await self.modification_tracker.stop()
|
|
833
|
+
|
|
834
|
+
if self.persistence_service:
|
|
835
|
+
await self.persistence_service.stop()
|
|
836
|
+
|
|
837
|
+
except Exception as e:
|
|
838
|
+
self.logger.error(f"Error cleaning up core services: {e}")
|
|
839
|
+
|
|
840
|
+
# Public API Methods
|
|
841
|
+
|
|
842
|
+
async def get_agent_status(self, agent_name: str) -> Optional[AgentLifecycleRecord]:
|
|
843
|
+
"""Get current status of an agent."""
|
|
844
|
+
return self.agent_records.get(agent_name)
|
|
845
|
+
|
|
846
|
+
async def list_agents(self, state_filter: Optional[LifecycleState] = None) -> List[AgentLifecycleRecord]:
|
|
847
|
+
"""List agents with optional state filtering."""
|
|
848
|
+
agents = list(self.agent_records.values())
|
|
849
|
+
|
|
850
|
+
if state_filter:
|
|
851
|
+
agents = [agent for agent in agents if agent.current_state == state_filter]
|
|
852
|
+
|
|
853
|
+
return sorted(agents, key=lambda x: x.last_modified, reverse=True)
|
|
854
|
+
|
|
855
|
+
async def get_operation_history(self, agent_name: Optional[str] = None, limit: int = 100) -> List[LifecycleOperationResult]:
|
|
856
|
+
"""Get operation history with optional filtering."""
|
|
857
|
+
history = self.operation_history
|
|
858
|
+
|
|
859
|
+
if agent_name:
|
|
860
|
+
history = [op for op in history if op.agent_name == agent_name]
|
|
861
|
+
|
|
862
|
+
return sorted(history, key=lambda x: x.duration_ms, reverse=True)[:limit]
|
|
863
|
+
|
|
864
|
+
async def get_lifecycle_stats(self) -> Dict[str, Any]:
|
|
865
|
+
"""Get comprehensive lifecycle statistics."""
|
|
866
|
+
stats = {
|
|
867
|
+
'total_agents': len(self.agent_records),
|
|
868
|
+
'active_operations': len(self.active_operations),
|
|
869
|
+
'performance_metrics': self.performance_metrics.copy()
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
# State distribution
|
|
873
|
+
state_counts = {}
|
|
874
|
+
for record in self.agent_records.values():
|
|
875
|
+
state_counts[record.current_state.value] = state_counts.get(record.current_state.value, 0) + 1
|
|
876
|
+
|
|
877
|
+
stats['agents_by_state'] = state_counts
|
|
878
|
+
|
|
879
|
+
# Tier distribution
|
|
880
|
+
tier_counts = {}
|
|
881
|
+
for record in self.agent_records.values():
|
|
882
|
+
tier_counts[record.tier.value] = tier_counts.get(record.tier.value, 0) + 1
|
|
883
|
+
|
|
884
|
+
stats['agents_by_tier'] = tier_counts
|
|
885
|
+
|
|
886
|
+
# Recent activity
|
|
887
|
+
recent_ops = [
|
|
888
|
+
op for op in self.operation_history
|
|
889
|
+
if (time.time() - (op.duration_ms / 1000)) < 3600 # Last hour
|
|
890
|
+
]
|
|
891
|
+
stats['recent_operations'] = len(recent_ops)
|
|
892
|
+
|
|
893
|
+
return stats
|
|
894
|
+
|
|
895
|
+
async def restore_agent(self, agent_name: str, backup_path: Optional[str] = None) -> LifecycleOperationResult:
|
|
896
|
+
"""Restore agent from backup."""
|
|
897
|
+
start_time = time.time()
|
|
898
|
+
|
|
899
|
+
try:
|
|
900
|
+
record = self.agent_records.get(agent_name)
|
|
901
|
+
if not record:
|
|
902
|
+
return LifecycleOperationResult(
|
|
903
|
+
operation=LifecycleOperation.RESTORE,
|
|
904
|
+
agent_name=agent_name,
|
|
905
|
+
success=False,
|
|
906
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
907
|
+
error_message="Agent record not found"
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
# Use latest backup if not specified
|
|
911
|
+
if not backup_path and record.backup_paths:
|
|
912
|
+
backup_path = record.backup_paths[-1]
|
|
913
|
+
|
|
914
|
+
if not backup_path or not path_ops.validate_exists(backup_path):
|
|
915
|
+
return LifecycleOperationResult(
|
|
916
|
+
operation=LifecycleOperation.RESTORE,
|
|
917
|
+
agent_name=agent_name,
|
|
918
|
+
success=False,
|
|
919
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
920
|
+
error_message="No valid backup found"
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
# Read backup content
|
|
924
|
+
backup_content = path_ops.safe_read(backup_path)
|
|
925
|
+
if not backup_content:
|
|
926
|
+
return LifecycleOperationResult(
|
|
927
|
+
operation=LifecycleOperation.RESTORE,
|
|
928
|
+
agent_name=agent_name,
|
|
929
|
+
success=False,
|
|
930
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
931
|
+
error_message="Failed to read backup content"
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
# Restore via update operation
|
|
935
|
+
return await self.update_agent(
|
|
936
|
+
agent_name=agent_name,
|
|
937
|
+
agent_content=backup_content,
|
|
938
|
+
restored_from=backup_path
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
except Exception as e:
|
|
942
|
+
return LifecycleOperationResult(
|
|
943
|
+
operation=LifecycleOperation.RESTORE,
|
|
944
|
+
agent_name=agent_name,
|
|
945
|
+
success=False,
|
|
946
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
947
|
+
error_message=str(e)
|
|
948
|
+
)
|