claude-mpm 4.1.2__py3-none-any.whl → 4.1.3__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/agents/templates/engineer.json +33 -11
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +114 -595
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Validation Service
|
|
3
|
+
========================
|
|
4
|
+
|
|
5
|
+
WHY: This service encapsulates all agent validation logic, including frontmatter
|
|
6
|
+
validation, file integrity checks, and deployment state validation. It provides
|
|
7
|
+
a clean interface for the CLI to validate and fix agent issues.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Uses FrontmatterValidator for low-level validation operations
|
|
11
|
+
- Provides high-level validation and fix operations for the CLI
|
|
12
|
+
- Generates detailed validation reports for user feedback
|
|
13
|
+
- Maintains separation between validation logic and CLI presentation
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from ...agents.frontmatter_validator import FrontmatterValidator
|
|
21
|
+
from ...core.agent_registry import AgentRegistryAdapter
|
|
22
|
+
from ...core.logger import get_logger
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IAgentValidationService(ABC):
|
|
26
|
+
"""Interface for agent validation operations."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def validate_agent(self, agent_name: str) -> Dict[str, Any]:
|
|
30
|
+
"""Validate a single agent."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def validate_all_agents(self) -> Dict[str, Any]:
|
|
34
|
+
"""Validate all deployed agents."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def fix_agent_frontmatter(
|
|
38
|
+
self, agent_name: str, dry_run: bool = True
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
"""Fix frontmatter issues for a single agent."""
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def fix_all_agents(self, dry_run: bool = True) -> Dict[str, Any]:
|
|
44
|
+
"""Fix frontmatter issues for all agents."""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def check_agent_integrity(self, agent_name: str) -> Dict[str, Any]:
|
|
48
|
+
"""Verify agent file structure and content integrity."""
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def validate_deployment_state(self) -> Dict[str, Any]:
|
|
52
|
+
"""Check deployment consistency across all agents."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AgentValidationService(IAgentValidationService):
|
|
56
|
+
"""Service for validating and fixing agent deployment issues."""
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
"""Initialize the validation service."""
|
|
60
|
+
self.logger = get_logger(__name__)
|
|
61
|
+
self.validator = FrontmatterValidator()
|
|
62
|
+
self._registry = None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def registry(self):
|
|
66
|
+
"""Get agent registry instance (lazy loaded)."""
|
|
67
|
+
if self._registry is None:
|
|
68
|
+
try:
|
|
69
|
+
adapter = AgentRegistryAdapter()
|
|
70
|
+
self._registry = adapter.registry
|
|
71
|
+
except Exception as e:
|
|
72
|
+
self.logger.error(f"Failed to initialize agent registry: {e}")
|
|
73
|
+
raise RuntimeError(f"Could not initialize agent registry: {e}")
|
|
74
|
+
return self._registry
|
|
75
|
+
|
|
76
|
+
def validate_agent(self, agent_name: str) -> Dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Validate a single agent.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
agent_name: Name of the agent to validate
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary containing validation results
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
# Get agent from registry
|
|
88
|
+
agent = self.registry.get_agent(agent_name)
|
|
89
|
+
if not agent:
|
|
90
|
+
return {
|
|
91
|
+
"success": False,
|
|
92
|
+
"agent": agent_name,
|
|
93
|
+
"error": f"Agent '{agent_name}' not found",
|
|
94
|
+
"available_agents": list(self.registry.list_agents().keys()),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Validate agent file
|
|
98
|
+
agent_path = Path(agent.path)
|
|
99
|
+
if not agent_path.exists():
|
|
100
|
+
return {
|
|
101
|
+
"success": False,
|
|
102
|
+
"agent": agent_name,
|
|
103
|
+
"path": str(agent_path),
|
|
104
|
+
"error": "Agent file not found",
|
|
105
|
+
"is_valid": False,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Perform validation
|
|
109
|
+
result = self.validator.validate_file(agent_path)
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"success": True,
|
|
113
|
+
"agent": agent_name,
|
|
114
|
+
"path": str(agent_path),
|
|
115
|
+
"is_valid": result.is_valid,
|
|
116
|
+
"errors": result.errors,
|
|
117
|
+
"warnings": result.warnings,
|
|
118
|
+
"corrections_available": len(result.corrections) > 0,
|
|
119
|
+
"corrections": result.corrections,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
self.logger.error(
|
|
124
|
+
f"Error validating agent {agent_name}: {e}", exc_info=True
|
|
125
|
+
)
|
|
126
|
+
return {"success": False, "agent": agent_name, "error": str(e)}
|
|
127
|
+
|
|
128
|
+
def validate_all_agents(self) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Validate all deployed agents.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary containing validation results for all agents
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
all_agents = self.registry.list_agents()
|
|
137
|
+
if not all_agents:
|
|
138
|
+
return {
|
|
139
|
+
"success": True,
|
|
140
|
+
"message": "No agents found to validate",
|
|
141
|
+
"total_agents": 0,
|
|
142
|
+
"results": [],
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
results = []
|
|
146
|
+
total_issues = 0
|
|
147
|
+
total_errors = 0
|
|
148
|
+
total_warnings = 0
|
|
149
|
+
agents_with_issues = []
|
|
150
|
+
|
|
151
|
+
for agent_id, metadata in all_agents.items():
|
|
152
|
+
agent_path = Path(metadata["path"])
|
|
153
|
+
|
|
154
|
+
# Check if file exists
|
|
155
|
+
if not agent_path.exists():
|
|
156
|
+
results.append(
|
|
157
|
+
{
|
|
158
|
+
"agent": agent_id,
|
|
159
|
+
"path": str(agent_path),
|
|
160
|
+
"is_valid": False,
|
|
161
|
+
"error": "File not found",
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
total_errors += 1
|
|
165
|
+
agents_with_issues.append(agent_id)
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
# Validate the agent
|
|
169
|
+
validation_result = self.validator.validate_file(agent_path)
|
|
170
|
+
|
|
171
|
+
agent_result = {
|
|
172
|
+
"agent": agent_id,
|
|
173
|
+
"path": str(agent_path),
|
|
174
|
+
"is_valid": validation_result.is_valid,
|
|
175
|
+
"errors": validation_result.errors,
|
|
176
|
+
"warnings": validation_result.warnings,
|
|
177
|
+
"corrections_available": len(validation_result.corrections) > 0,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
results.append(agent_result)
|
|
181
|
+
|
|
182
|
+
if validation_result.errors:
|
|
183
|
+
total_errors += len(validation_result.errors)
|
|
184
|
+
total_issues += len(validation_result.errors)
|
|
185
|
+
agents_with_issues.append(agent_id)
|
|
186
|
+
|
|
187
|
+
if validation_result.warnings:
|
|
188
|
+
total_warnings += len(validation_result.warnings)
|
|
189
|
+
total_issues += len(validation_result.warnings)
|
|
190
|
+
if agent_id not in agents_with_issues:
|
|
191
|
+
agents_with_issues.append(agent_id)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"success": True,
|
|
195
|
+
"total_agents": len(all_agents),
|
|
196
|
+
"agents_checked": len(results),
|
|
197
|
+
"total_issues": total_issues,
|
|
198
|
+
"total_errors": total_errors,
|
|
199
|
+
"total_warnings": total_warnings,
|
|
200
|
+
"agents_with_issues": agents_with_issues,
|
|
201
|
+
"results": results,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f"Error validating all agents: {e}", exc_info=True)
|
|
206
|
+
return {"success": False, "error": str(e)}
|
|
207
|
+
|
|
208
|
+
def fix_agent_frontmatter(
|
|
209
|
+
self, agent_name: str, dry_run: bool = True
|
|
210
|
+
) -> Dict[str, Any]:
|
|
211
|
+
"""
|
|
212
|
+
Fix frontmatter issues for a single agent.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
agent_name: Name of the agent to fix
|
|
216
|
+
dry_run: If True, don't actually write changes
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Dictionary containing fix results
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
# Get agent from registry
|
|
223
|
+
agent = self.registry.get_agent(agent_name)
|
|
224
|
+
if not agent:
|
|
225
|
+
return {
|
|
226
|
+
"success": False,
|
|
227
|
+
"agent": agent_name,
|
|
228
|
+
"error": f"Agent '{agent_name}' not found",
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
agent_path = Path(agent.path)
|
|
232
|
+
if not agent_path.exists():
|
|
233
|
+
return {
|
|
234
|
+
"success": False,
|
|
235
|
+
"agent": agent_name,
|
|
236
|
+
"path": str(agent_path),
|
|
237
|
+
"error": "Agent file not found",
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Fix the agent
|
|
241
|
+
result = self.validator.correct_file(agent_path, dry_run=dry_run)
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
"success": True,
|
|
245
|
+
"agent": agent_name,
|
|
246
|
+
"path": str(agent_path),
|
|
247
|
+
"dry_run": dry_run,
|
|
248
|
+
"was_valid": result.is_valid and not result.corrections,
|
|
249
|
+
"errors_found": result.errors,
|
|
250
|
+
"warnings_found": result.warnings,
|
|
251
|
+
"corrections_made": result.corrections if not dry_run else [],
|
|
252
|
+
"corrections_available": result.corrections if dry_run else [],
|
|
253
|
+
"is_fixed": not dry_run and result.is_valid,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
self.logger.error(f"Error fixing agent {agent_name}: {e}", exc_info=True)
|
|
258
|
+
return {"success": False, "agent": agent_name, "error": str(e)}
|
|
259
|
+
|
|
260
|
+
def fix_all_agents(self, dry_run: bool = True) -> Dict[str, Any]:
|
|
261
|
+
"""
|
|
262
|
+
Fix frontmatter issues for all agents.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
dry_run: If True, don't actually write changes
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Dictionary containing fix results for all agents
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
all_agents = self.registry.list_agents()
|
|
272
|
+
if not all_agents:
|
|
273
|
+
return {
|
|
274
|
+
"success": True,
|
|
275
|
+
"message": "No agents found to fix",
|
|
276
|
+
"total_agents": 0,
|
|
277
|
+
"results": [],
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
results = []
|
|
281
|
+
total_issues = 0
|
|
282
|
+
total_fixed = 0
|
|
283
|
+
agents_fixed = []
|
|
284
|
+
agents_with_errors = []
|
|
285
|
+
|
|
286
|
+
for agent_id, metadata in all_agents.items():
|
|
287
|
+
agent_path = Path(metadata["path"])
|
|
288
|
+
|
|
289
|
+
# Check if file exists
|
|
290
|
+
if not agent_path.exists():
|
|
291
|
+
results.append(
|
|
292
|
+
{
|
|
293
|
+
"agent": agent_id,
|
|
294
|
+
"path": str(agent_path),
|
|
295
|
+
"skipped": True,
|
|
296
|
+
"reason": "File not found",
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
agents_with_errors.append(agent_id)
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
# Fix the agent
|
|
303
|
+
fix_result = self.validator.correct_file(agent_path, dry_run=dry_run)
|
|
304
|
+
|
|
305
|
+
agent_result = {
|
|
306
|
+
"agent": agent_id,
|
|
307
|
+
"path": str(agent_path),
|
|
308
|
+
"was_valid": fix_result.is_valid and not fix_result.corrections,
|
|
309
|
+
"errors_found": len(fix_result.errors),
|
|
310
|
+
"warnings_found": len(fix_result.warnings),
|
|
311
|
+
"corrections_made": (
|
|
312
|
+
len(fix_result.corrections) if not dry_run else 0
|
|
313
|
+
),
|
|
314
|
+
"corrections_available": (
|
|
315
|
+
len(fix_result.corrections) if dry_run else 0
|
|
316
|
+
),
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
results.append(agent_result)
|
|
320
|
+
|
|
321
|
+
if fix_result.errors:
|
|
322
|
+
total_issues += len(fix_result.errors)
|
|
323
|
+
|
|
324
|
+
if fix_result.warnings:
|
|
325
|
+
total_issues += len(fix_result.warnings)
|
|
326
|
+
|
|
327
|
+
if fix_result.corrections:
|
|
328
|
+
if not dry_run:
|
|
329
|
+
total_fixed += len(fix_result.corrections)
|
|
330
|
+
agents_fixed.append(agent_id)
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
"success": True,
|
|
334
|
+
"dry_run": dry_run,
|
|
335
|
+
"total_agents": len(all_agents),
|
|
336
|
+
"agents_checked": len(results),
|
|
337
|
+
"total_issues_found": total_issues,
|
|
338
|
+
"total_corrections_made": total_fixed if not dry_run else 0,
|
|
339
|
+
"total_corrections_available": total_fixed if dry_run else 0,
|
|
340
|
+
"agents_fixed": agents_fixed,
|
|
341
|
+
"agents_with_errors": agents_with_errors,
|
|
342
|
+
"results": results,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
self.logger.error(f"Error fixing all agents: {e}", exc_info=True)
|
|
347
|
+
return {"success": False, "error": str(e)}
|
|
348
|
+
|
|
349
|
+
def check_agent_integrity(self, agent_name: str) -> Dict[str, Any]:
|
|
350
|
+
"""
|
|
351
|
+
Verify agent file structure and content integrity.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
agent_name: Name of the agent to check
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Dictionary containing integrity check results
|
|
358
|
+
"""
|
|
359
|
+
try:
|
|
360
|
+
# Get agent from registry
|
|
361
|
+
agent = self.registry.get_agent(agent_name)
|
|
362
|
+
if not agent:
|
|
363
|
+
return {
|
|
364
|
+
"success": False,
|
|
365
|
+
"agent": agent_name,
|
|
366
|
+
"error": f"Agent '{agent_name}' not found",
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
agent_path = Path(agent.path)
|
|
370
|
+
checks = {
|
|
371
|
+
"file_exists": False,
|
|
372
|
+
"has_frontmatter": False,
|
|
373
|
+
"has_content": False,
|
|
374
|
+
"valid_frontmatter": False,
|
|
375
|
+
"required_fields": [],
|
|
376
|
+
"missing_fields": [],
|
|
377
|
+
"file_size": 0,
|
|
378
|
+
"line_count": 0,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# Check file existence
|
|
382
|
+
if not agent_path.exists():
|
|
383
|
+
return {
|
|
384
|
+
"success": True,
|
|
385
|
+
"agent": agent_name,
|
|
386
|
+
"path": str(agent_path),
|
|
387
|
+
"integrity": checks,
|
|
388
|
+
"is_valid": False,
|
|
389
|
+
"issues": ["File does not exist"],
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
checks["file_exists"] = True
|
|
393
|
+
|
|
394
|
+
# Read file content
|
|
395
|
+
try:
|
|
396
|
+
content = agent_path.read_text()
|
|
397
|
+
checks["file_size"] = len(content)
|
|
398
|
+
checks["line_count"] = len(content.splitlines())
|
|
399
|
+
checks["has_content"] = len(content.strip()) > 0
|
|
400
|
+
except Exception as e:
|
|
401
|
+
return {
|
|
402
|
+
"success": True,
|
|
403
|
+
"agent": agent_name,
|
|
404
|
+
"path": str(agent_path),
|
|
405
|
+
"integrity": checks,
|
|
406
|
+
"is_valid": False,
|
|
407
|
+
"issues": [f"Cannot read file: {e}"],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# Check for frontmatter
|
|
411
|
+
if content.startswith("---"):
|
|
412
|
+
checks["has_frontmatter"] = True
|
|
413
|
+
|
|
414
|
+
# Validate frontmatter
|
|
415
|
+
validation_result = self.validator.validate_file(agent_path)
|
|
416
|
+
checks["valid_frontmatter"] = validation_result.is_valid
|
|
417
|
+
|
|
418
|
+
# Extract frontmatter to check fields
|
|
419
|
+
try:
|
|
420
|
+
frontmatter_match = self.validator._extract_frontmatter(content)
|
|
421
|
+
if frontmatter_match:
|
|
422
|
+
import yaml
|
|
423
|
+
|
|
424
|
+
frontmatter = yaml.safe_load(frontmatter_match[0])
|
|
425
|
+
|
|
426
|
+
# Check required fields
|
|
427
|
+
required = ["name", "type", "description"]
|
|
428
|
+
for field in required:
|
|
429
|
+
if field in frontmatter:
|
|
430
|
+
checks["required_fields"].append(field)
|
|
431
|
+
else:
|
|
432
|
+
checks["missing_fields"].append(field)
|
|
433
|
+
except Exception:
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
# Determine overall validity
|
|
437
|
+
issues = []
|
|
438
|
+
if not checks["file_exists"]:
|
|
439
|
+
issues.append("File does not exist")
|
|
440
|
+
if not checks["has_content"]:
|
|
441
|
+
issues.append("File is empty")
|
|
442
|
+
if not checks["has_frontmatter"]:
|
|
443
|
+
issues.append("No frontmatter found")
|
|
444
|
+
if checks["has_frontmatter"] and not checks["valid_frontmatter"]:
|
|
445
|
+
issues.append("Invalid frontmatter format")
|
|
446
|
+
if checks["missing_fields"]:
|
|
447
|
+
issues.append(
|
|
448
|
+
f"Missing required fields: {', '.join(checks['missing_fields'])}"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
is_valid = len(issues) == 0
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
"success": True,
|
|
455
|
+
"agent": agent_name,
|
|
456
|
+
"path": str(agent_path),
|
|
457
|
+
"integrity": checks,
|
|
458
|
+
"is_valid": is_valid,
|
|
459
|
+
"issues": issues,
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
except Exception as e:
|
|
463
|
+
self.logger.error(
|
|
464
|
+
f"Error checking integrity for agent {agent_name}: {e}", exc_info=True
|
|
465
|
+
)
|
|
466
|
+
return {"success": False, "agent": agent_name, "error": str(e)}
|
|
467
|
+
|
|
468
|
+
def validate_deployment_state(self) -> Dict[str, Any]:
|
|
469
|
+
"""
|
|
470
|
+
Check deployment consistency across all agents.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
Dictionary containing deployment state validation results
|
|
474
|
+
"""
|
|
475
|
+
try:
|
|
476
|
+
deployment_state = {
|
|
477
|
+
"total_registered": 0,
|
|
478
|
+
"files_found": 0,
|
|
479
|
+
"files_missing": [],
|
|
480
|
+
"duplicate_names": {},
|
|
481
|
+
"conflicting_paths": {},
|
|
482
|
+
"deployment_issues": [],
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
all_agents = self.registry.list_agents()
|
|
486
|
+
deployment_state["total_registered"] = len(all_agents)
|
|
487
|
+
|
|
488
|
+
# Track agent names and paths for duplicate detection
|
|
489
|
+
name_to_agents = {}
|
|
490
|
+
path_to_agents = {}
|
|
491
|
+
|
|
492
|
+
for agent_id, metadata in all_agents.items():
|
|
493
|
+
agent_path = Path(metadata["path"])
|
|
494
|
+
agent_name = metadata.get("name", agent_id)
|
|
495
|
+
|
|
496
|
+
# Check file existence
|
|
497
|
+
if agent_path.exists():
|
|
498
|
+
deployment_state["files_found"] += 1
|
|
499
|
+
else:
|
|
500
|
+
deployment_state["files_missing"].append(
|
|
501
|
+
{"agent": agent_id, "path": str(agent_path)}
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Track names for duplicates
|
|
505
|
+
if agent_name in name_to_agents:
|
|
506
|
+
if agent_name not in deployment_state["duplicate_names"]:
|
|
507
|
+
deployment_state["duplicate_names"][agent_name] = []
|
|
508
|
+
deployment_state["duplicate_names"][agent_name].append(agent_id)
|
|
509
|
+
if (
|
|
510
|
+
name_to_agents[agent_name]
|
|
511
|
+
not in deployment_state["duplicate_names"][agent_name]
|
|
512
|
+
):
|
|
513
|
+
deployment_state["duplicate_names"][agent_name].append(
|
|
514
|
+
name_to_agents[agent_name]
|
|
515
|
+
)
|
|
516
|
+
else:
|
|
517
|
+
name_to_agents[agent_name] = agent_id
|
|
518
|
+
|
|
519
|
+
# Track paths for conflicts
|
|
520
|
+
path_str = str(agent_path)
|
|
521
|
+
if path_str in path_to_agents:
|
|
522
|
+
if path_str not in deployment_state["conflicting_paths"]:
|
|
523
|
+
deployment_state["conflicting_paths"][path_str] = []
|
|
524
|
+
deployment_state["conflicting_paths"][path_str].append(agent_id)
|
|
525
|
+
if (
|
|
526
|
+
path_to_agents[path_str]
|
|
527
|
+
not in deployment_state["conflicting_paths"][path_str]
|
|
528
|
+
):
|
|
529
|
+
deployment_state["conflicting_paths"][path_str].append(
|
|
530
|
+
path_to_agents[path_str]
|
|
531
|
+
)
|
|
532
|
+
else:
|
|
533
|
+
path_to_agents[path_str] = agent_id
|
|
534
|
+
|
|
535
|
+
# Identify deployment issues
|
|
536
|
+
if deployment_state["files_missing"]:
|
|
537
|
+
deployment_state["deployment_issues"].append(
|
|
538
|
+
f"{len(deployment_state['files_missing'])} agents have missing files"
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
if deployment_state["duplicate_names"]:
|
|
542
|
+
deployment_state["deployment_issues"].append(
|
|
543
|
+
f"{len(deployment_state['duplicate_names'])} duplicate agent names found"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
if deployment_state["conflicting_paths"]:
|
|
547
|
+
deployment_state["deployment_issues"].append(
|
|
548
|
+
f"{len(deployment_state['conflicting_paths'])} path conflicts found"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Determine if deployment is healthy
|
|
552
|
+
is_healthy = (
|
|
553
|
+
len(deployment_state["files_missing"]) == 0
|
|
554
|
+
and len(deployment_state["duplicate_names"]) == 0
|
|
555
|
+
and len(deployment_state["conflicting_paths"]) == 0
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
"success": True,
|
|
560
|
+
"is_healthy": is_healthy,
|
|
561
|
+
"deployment_state": deployment_state,
|
|
562
|
+
"summary": {
|
|
563
|
+
"total_agents": deployment_state["total_registered"],
|
|
564
|
+
"healthy_agents": deployment_state["files_found"]
|
|
565
|
+
- len(deployment_state["duplicate_names"])
|
|
566
|
+
- len(deployment_state["conflicting_paths"]),
|
|
567
|
+
"issues_count": len(deployment_state["deployment_issues"]),
|
|
568
|
+
},
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
except Exception as e:
|
|
572
|
+
self.logger.error(f"Error validating deployment state: {e}", exc_info=True)
|
|
573
|
+
return {"success": False, "error": str(e)}
|
|
574
|
+
|
|
575
|
+
def _extract_frontmatter(self, content: str) -> Optional[Tuple[str, str]]:
|
|
576
|
+
"""
|
|
577
|
+
Extract frontmatter and content from an agent file.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
content: File content
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
Tuple of (frontmatter, content) or None if no frontmatter
|
|
584
|
+
"""
|
|
585
|
+
pattern = r"^---\n(.*?)\n---\n(.*)$"
|
|
586
|
+
match = re.match(pattern, content, re.DOTALL)
|
|
587
|
+
if match:
|
|
588
|
+
return match.groups()
|
|
589
|
+
return None
|