claude-mpm 4.1.3__py3-none-any.whl → 4.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +16 -19
- claude_mpm/agents/MEMORY.md +21 -49
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
- claude_mpm/agents/templates/api_qa.json +36 -116
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
- claude_mpm/agents/templates/code_analyzer.json +18 -36
- claude_mpm/agents/templates/data_engineer.json +43 -14
- claude_mpm/agents/templates/documentation.json +55 -74
- claude_mpm/agents/templates/engineer.json +56 -61
- claude_mpm/agents/templates/imagemagick.json +7 -2
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +36 -4
- claude_mpm/agents/templates/project_organizer.json +23 -71
- claude_mpm/agents/templates/qa.json +34 -2
- claude_mpm/agents/templates/refactoring_engineer.json +9 -5
- claude_mpm/agents/templates/research.json +36 -4
- claude_mpm/agents/templates/security.json +29 -2
- claude_mpm/agents/templates/ticketing.json +3 -3
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +28 -2
- claude_mpm/agents/templates/web_qa.json +38 -151
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/commands/agent_manager.py +221 -1
- claude_mpm/cli/commands/tickets.py +365 -784
- claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
- claude_mpm/core/framework_loader.py +91 -0
- claude_mpm/core/log_manager.py +49 -1
- claude_mpm/core/output_style_manager.py +24 -0
- claude_mpm/core/unified_agent_registry.py +46 -15
- claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
- claude_mpm/services/infrastructure/__init__.py +31 -5
- claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
- claude_mpm/services/infrastructure/monitoring/base.py +130 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +218 -0
- claude_mpm/services/infrastructure/monitoring/process.py +342 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
- claude_mpm/services/infrastructure/monitoring/service.py +367 -0
- claude_mpm/services/infrastructure/monitoring.py +67 -1030
- claude_mpm/services/memory/router.py +116 -10
- claude_mpm/services/project/analyzer.py +13 -4
- claude_mpm/services/project/analyzer_refactored.py +450 -0
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +410 -0
- claude_mpm/services/ticket_manager.py +5 -1
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/RECORD +77 -52
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.3.dist-info → claude_mpm-4.1.5.dist-info}/top_level.txt +0 -0
|
@@ -32,6 +32,9 @@ Examples:
|
|
|
32
32
|
claude-mpm agent-manager show --id engineer # Show agent details
|
|
33
33
|
claude-mpm agent-manager test --id my-agent # Test agent configuration
|
|
34
34
|
claude-mpm agent-manager templates # List available templates
|
|
35
|
+
claude-mpm agent-manager reset --dry-run # Preview agent cleanup
|
|
36
|
+
claude-mpm agent-manager reset --force # Remove all claude-mpm agents
|
|
37
|
+
claude-mpm agent-manager reset --project-only # Clean only project agents
|
|
35
38
|
""",
|
|
36
39
|
)
|
|
37
40
|
|
|
@@ -197,3 +200,34 @@ Examples:
|
|
|
197
200
|
default="text",
|
|
198
201
|
help="Output format (default: text)",
|
|
199
202
|
)
|
|
203
|
+
|
|
204
|
+
# Reset command
|
|
205
|
+
reset_parser = agent_subparsers.add_parser(
|
|
206
|
+
"reset", help="Remove claude-mpm authored agents for clean install"
|
|
207
|
+
)
|
|
208
|
+
reset_parser.add_argument(
|
|
209
|
+
"--force",
|
|
210
|
+
action="store_true",
|
|
211
|
+
help="Execute cleanup immediately without confirmation",
|
|
212
|
+
)
|
|
213
|
+
reset_parser.add_argument(
|
|
214
|
+
"--dry-run",
|
|
215
|
+
action="store_true",
|
|
216
|
+
help="Preview what would be removed without making changes",
|
|
217
|
+
)
|
|
218
|
+
reset_parser.add_argument(
|
|
219
|
+
"--project-only",
|
|
220
|
+
action="store_true",
|
|
221
|
+
help="Only clean project-level agents (.claude/agents)",
|
|
222
|
+
)
|
|
223
|
+
reset_parser.add_argument(
|
|
224
|
+
"--user-only",
|
|
225
|
+
action="store_true",
|
|
226
|
+
help="Only clean user-level agents (~/.claude/agents)",
|
|
227
|
+
)
|
|
228
|
+
reset_parser.add_argument(
|
|
229
|
+
"--format",
|
|
230
|
+
choices=["text", "json"],
|
|
231
|
+
default="text",
|
|
232
|
+
help="Output format (default: text)",
|
|
233
|
+
)
|
|
@@ -1283,6 +1283,14 @@ Extract tickets from these patterns:
|
|
|
1283
1283
|
if agent.get("model") and agent["model"] != "opus":
|
|
1284
1284
|
section += f"- **Model**: {agent['model']}\n"
|
|
1285
1285
|
|
|
1286
|
+
# Add memory routing information if available
|
|
1287
|
+
if agent.get("memory_routing"):
|
|
1288
|
+
memory_routing = agent["memory_routing"]
|
|
1289
|
+
if memory_routing.get("description"):
|
|
1290
|
+
section += (
|
|
1291
|
+
f"- **Memory Routing**: {memory_routing['description']}\n"
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1286
1294
|
# Add simple Context-Aware Agent Selection
|
|
1287
1295
|
section += "\n## Context-Aware Agent Selection\n\n"
|
|
1288
1296
|
section += (
|
|
@@ -1384,6 +1392,14 @@ Extract tickets from these patterns:
|
|
|
1384
1392
|
if routing_data:
|
|
1385
1393
|
agent_data["routing"] = routing_data
|
|
1386
1394
|
|
|
1395
|
+
# Try to load memory routing metadata from JSON template if not in YAML frontmatter
|
|
1396
|
+
if "memory_routing" not in agent_data:
|
|
1397
|
+
memory_routing_data = self._load_memory_routing_from_template(
|
|
1398
|
+
agent_file.stem
|
|
1399
|
+
)
|
|
1400
|
+
if memory_routing_data:
|
|
1401
|
+
agent_data["memory_routing"] = memory_routing_data
|
|
1402
|
+
|
|
1387
1403
|
# Cache the parsed metadata
|
|
1388
1404
|
self._cache_manager.set_agent_metadata(cache_key, agent_data, file_mtime)
|
|
1389
1405
|
|
|
@@ -1393,6 +1409,81 @@ Extract tickets from these patterns:
|
|
|
1393
1409
|
self.logger.debug(f"Could not parse metadata from {agent_file}: {e}")
|
|
1394
1410
|
return None
|
|
1395
1411
|
|
|
1412
|
+
def _load_memory_routing_from_template(
|
|
1413
|
+
self, agent_name: str
|
|
1414
|
+
) -> Optional[Dict[str, Any]]:
|
|
1415
|
+
"""Load memory routing metadata from agent JSON template.
|
|
1416
|
+
|
|
1417
|
+
Args:
|
|
1418
|
+
agent_name: Name of the agent (stem of the file)
|
|
1419
|
+
|
|
1420
|
+
Returns:
|
|
1421
|
+
Dictionary with memory routing metadata or None if not found
|
|
1422
|
+
"""
|
|
1423
|
+
try:
|
|
1424
|
+
import json
|
|
1425
|
+
|
|
1426
|
+
# Check if we have a framework path
|
|
1427
|
+
if not self.framework_path or self.framework_path == Path("__PACKAGED__"):
|
|
1428
|
+
# For packaged installations, try to load from package resources
|
|
1429
|
+
if files:
|
|
1430
|
+
try:
|
|
1431
|
+
templates_package = files("claude_mpm.agents.templates")
|
|
1432
|
+
template_file = templates_package / f"{agent_name}.json"
|
|
1433
|
+
|
|
1434
|
+
if template_file.is_file():
|
|
1435
|
+
template_content = template_file.read_text()
|
|
1436
|
+
template_data = json.loads(template_content)
|
|
1437
|
+
return template_data.get("memory_routing")
|
|
1438
|
+
except Exception as e:
|
|
1439
|
+
self.logger.debug(
|
|
1440
|
+
f"Could not load memory routing from packaged template for {agent_name}: {e}"
|
|
1441
|
+
)
|
|
1442
|
+
return None
|
|
1443
|
+
|
|
1444
|
+
# For development mode, load from filesystem
|
|
1445
|
+
templates_dir = (
|
|
1446
|
+
self.framework_path / "src" / "claude_mpm" / "agents" / "templates"
|
|
1447
|
+
)
|
|
1448
|
+
template_file = templates_dir / f"{agent_name}.json"
|
|
1449
|
+
|
|
1450
|
+
if template_file.exists():
|
|
1451
|
+
with open(template_file) as f:
|
|
1452
|
+
template_data = json.load(f)
|
|
1453
|
+
return template_data.get("memory_routing")
|
|
1454
|
+
|
|
1455
|
+
# Also check for variations in naming (underscore vs dash)
|
|
1456
|
+
# Handle common naming variations between deployed .md files and .json templates
|
|
1457
|
+
# Remove duplicates by using a set
|
|
1458
|
+
alternative_names = list(
|
|
1459
|
+
{
|
|
1460
|
+
agent_name.replace("-", "_"), # api-qa -> api_qa
|
|
1461
|
+
agent_name.replace("_", "-"), # api_qa -> api-qa
|
|
1462
|
+
agent_name.replace("-", ""), # api-qa -> apiqa
|
|
1463
|
+
agent_name.replace("_", ""), # api_qa -> apiqa
|
|
1464
|
+
agent_name.replace("-agent", ""), # research-agent -> research
|
|
1465
|
+
agent_name.replace("_agent", ""), # research_agent -> research
|
|
1466
|
+
agent_name + "_agent", # research -> research_agent
|
|
1467
|
+
agent_name + "-agent", # research -> research-agent
|
|
1468
|
+
}
|
|
1469
|
+
)
|
|
1470
|
+
|
|
1471
|
+
for alt_name in alternative_names:
|
|
1472
|
+
if alt_name != agent_name: # Skip the original name we already tried
|
|
1473
|
+
alt_file = templates_dir / f"{alt_name}.json"
|
|
1474
|
+
if alt_file.exists():
|
|
1475
|
+
with open(alt_file) as f:
|
|
1476
|
+
template_data = json.load(f)
|
|
1477
|
+
return template_data.get("memory_routing")
|
|
1478
|
+
|
|
1479
|
+
return None
|
|
1480
|
+
|
|
1481
|
+
except Exception as e:
|
|
1482
|
+
self.logger.debug(
|
|
1483
|
+
f"Could not load memory routing from template for {agent_name}: {e}"
|
|
1484
|
+
)
|
|
1485
|
+
return None
|
|
1486
|
+
|
|
1396
1487
|
def _load_routing_from_template(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
1397
1488
|
"""Load routing metadata from agent JSON template.
|
|
1398
1489
|
|
claude_mpm/core/log_manager.py
CHANGED
|
@@ -179,6 +179,11 @@ class LogManager:
|
|
|
179
179
|
# Add to cache
|
|
180
180
|
self._dir_cache[log_type] = log_dir
|
|
181
181
|
|
|
182
|
+
# One-time migration for MPM logs from old location to new subdirectory
|
|
183
|
+
if log_type == "mpm" and not hasattr(self, "_mpm_logs_migrated"):
|
|
184
|
+
await self._migrate_mpm_logs()
|
|
185
|
+
self._mpm_logs_migrated = True
|
|
186
|
+
|
|
182
187
|
# Schedule cleanup for old logs
|
|
183
188
|
await self.cleanup_old_logs(
|
|
184
189
|
log_dir,
|
|
@@ -206,7 +211,7 @@ class LogManager:
|
|
|
206
211
|
# Map log types to directory names
|
|
207
212
|
dir_mapping = {
|
|
208
213
|
"startup": "startup",
|
|
209
|
-
"mpm": "", #
|
|
214
|
+
"mpm": "mpm", # MPM logs in dedicated subdirectory
|
|
210
215
|
"prompts": "prompts",
|
|
211
216
|
"sessions": "sessions",
|
|
212
217
|
"agents": "agents",
|
|
@@ -343,6 +348,49 @@ class LogManager:
|
|
|
343
348
|
|
|
344
349
|
return deleted_count
|
|
345
350
|
|
|
351
|
+
async def _migrate_mpm_logs(self):
|
|
352
|
+
"""
|
|
353
|
+
One-time migration to move existing MPM logs to new subdirectory.
|
|
354
|
+
|
|
355
|
+
Moves mpm_*.log files from .claude-mpm/logs/ to .claude-mpm/logs/mpm/
|
|
356
|
+
"""
|
|
357
|
+
try:
|
|
358
|
+
old_location = self.base_log_dir
|
|
359
|
+
new_location = self.base_log_dir / "mpm"
|
|
360
|
+
|
|
361
|
+
# Only proceed if old location exists and has MPM logs
|
|
362
|
+
if not old_location.exists():
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
# Find all MPM log files in the old location
|
|
366
|
+
mpm_logs = list(old_location.glob("mpm_*.log"))
|
|
367
|
+
|
|
368
|
+
if not mpm_logs:
|
|
369
|
+
return # No logs to migrate
|
|
370
|
+
|
|
371
|
+
# Ensure new directory exists
|
|
372
|
+
new_location.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
|
|
374
|
+
migrated_count = 0
|
|
375
|
+
for log_file in mpm_logs:
|
|
376
|
+
try:
|
|
377
|
+
# Move file to new location
|
|
378
|
+
new_path = new_location / log_file.name
|
|
379
|
+
if not new_path.exists(): # Don't overwrite existing files
|
|
380
|
+
log_file.rename(new_path)
|
|
381
|
+
migrated_count += 1
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.debug(f"Could not migrate {log_file}: {e}")
|
|
384
|
+
|
|
385
|
+
if migrated_count > 0:
|
|
386
|
+
logger.info(
|
|
387
|
+
f"Migrated {migrated_count} MPM log files to {new_location}"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
# Migration is best-effort, don't fail if something goes wrong
|
|
392
|
+
logger.debug(f"MPM log migration skipped: {e}")
|
|
393
|
+
|
|
346
394
|
async def log_prompt(
|
|
347
395
|
self, prompt_type: str, content: str, metadata: Optional[Dict[str, Any]] = None
|
|
348
396
|
) -> Optional[Path]:
|
|
@@ -21,6 +21,10 @@ from ..utils.imports import safe_import
|
|
|
21
21
|
# Import with fallback support
|
|
22
22
|
get_logger = safe_import("claude_mpm.core.logger", "core.logger", ["get_logger"])
|
|
23
23
|
|
|
24
|
+
# Global cache for Claude version to avoid duplicate detection/logging
|
|
25
|
+
_CACHED_CLAUDE_VERSION: Optional[str] = None
|
|
26
|
+
_VERSION_DETECTED: bool = False
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class OutputStyleManager:
|
|
26
30
|
"""Manages output style deployment and version-based handling."""
|
|
@@ -41,10 +45,17 @@ class OutputStyleManager:
|
|
|
41
45
|
def _detect_claude_version(self) -> Optional[str]:
|
|
42
46
|
"""
|
|
43
47
|
Detect Claude Code version by running 'claude --version'.
|
|
48
|
+
Uses global cache to avoid duplicate detection and logging.
|
|
44
49
|
|
|
45
50
|
Returns:
|
|
46
51
|
Version string (e.g., "1.0.82") or None if Claude not found
|
|
47
52
|
"""
|
|
53
|
+
global _CACHED_CLAUDE_VERSION, _VERSION_DETECTED
|
|
54
|
+
|
|
55
|
+
# Return cached version if already detected
|
|
56
|
+
if _VERSION_DETECTED:
|
|
57
|
+
return _CACHED_CLAUDE_VERSION
|
|
58
|
+
|
|
48
59
|
try:
|
|
49
60
|
# Run claude --version command
|
|
50
61
|
result = subprocess.run(
|
|
@@ -57,6 +68,8 @@ class OutputStyleManager:
|
|
|
57
68
|
|
|
58
69
|
if result.returncode != 0:
|
|
59
70
|
self.logger.warning(f"Claude command failed: {result.stderr}")
|
|
71
|
+
_VERSION_DETECTED = True
|
|
72
|
+
_CACHED_CLAUDE_VERSION = None
|
|
60
73
|
return None
|
|
61
74
|
|
|
62
75
|
# Parse version from output
|
|
@@ -66,19 +79,30 @@ class OutputStyleManager:
|
|
|
66
79
|
|
|
67
80
|
if version_match:
|
|
68
81
|
version = version_match.group(1)
|
|
82
|
+
# Only log on first detection
|
|
69
83
|
self.logger.info(f"Detected Claude version: {version}")
|
|
84
|
+
_CACHED_CLAUDE_VERSION = version
|
|
85
|
+
_VERSION_DETECTED = True
|
|
70
86
|
return version
|
|
71
87
|
self.logger.warning(f"Could not parse version from: {version_output}")
|
|
88
|
+
_VERSION_DETECTED = True
|
|
89
|
+
_CACHED_CLAUDE_VERSION = None
|
|
72
90
|
return None
|
|
73
91
|
|
|
74
92
|
except FileNotFoundError:
|
|
75
93
|
self.logger.info("Claude Code not found in PATH")
|
|
94
|
+
_VERSION_DETECTED = True
|
|
95
|
+
_CACHED_CLAUDE_VERSION = None
|
|
76
96
|
return None
|
|
77
97
|
except subprocess.TimeoutExpired:
|
|
78
98
|
self.logger.warning("Claude version check timed out")
|
|
99
|
+
_VERSION_DETECTED = True
|
|
100
|
+
_CACHED_CLAUDE_VERSION = None
|
|
79
101
|
return None
|
|
80
102
|
except Exception as e:
|
|
81
103
|
self.logger.warning(f"Error detecting Claude version: {e}")
|
|
104
|
+
_VERSION_DETECTED = True
|
|
105
|
+
_CACHED_CLAUDE_VERSION = None
|
|
82
106
|
return None
|
|
83
107
|
|
|
84
108
|
def _compare_versions(self, version1: str, version2: str) -> int:
|
|
@@ -136,7 +136,13 @@ class UnifiedAgentRegistry:
|
|
|
136
136
|
|
|
137
137
|
# Discovery configuration
|
|
138
138
|
self.file_extensions = {".md", ".json", ".yaml", ".yml"}
|
|
139
|
-
self.ignore_patterns = {
|
|
139
|
+
self.ignore_patterns = {
|
|
140
|
+
"__pycache__",
|
|
141
|
+
".git",
|
|
142
|
+
"node_modules",
|
|
143
|
+
".pytest_cache",
|
|
144
|
+
"backup",
|
|
145
|
+
}
|
|
140
146
|
|
|
141
147
|
# Statistics
|
|
142
148
|
self.discovery_stats = {
|
|
@@ -166,15 +172,15 @@ class UnifiedAgentRegistry:
|
|
|
166
172
|
if user_path.exists():
|
|
167
173
|
self.discovery_paths.append(user_path)
|
|
168
174
|
|
|
169
|
-
# System-level agents
|
|
175
|
+
# System-level agents (includes templates as a subdirectory)
|
|
170
176
|
system_path = self.path_manager.get_system_agents_dir()
|
|
171
177
|
if system_path.exists():
|
|
172
178
|
self.discovery_paths.append(system_path)
|
|
173
179
|
|
|
174
|
-
# Templates directory
|
|
175
|
-
templates_path =
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
# NOTE: Templates directory is NOT added separately because:
|
|
181
|
+
# - templates_path = system_path / "templates"
|
|
182
|
+
# - The rglob("*") in _discover_path will already find templates
|
|
183
|
+
# - Adding it separately causes duplicate discovery
|
|
178
184
|
|
|
179
185
|
logger.debug(
|
|
180
186
|
f"Discovery paths configured: {[str(p) for p in self.discovery_paths]}"
|
|
@@ -256,7 +262,10 @@ class UnifiedAgentRegistry:
|
|
|
256
262
|
try:
|
|
257
263
|
metadata = self._create_agent_metadata(file_path, agent_name, tier)
|
|
258
264
|
if metadata:
|
|
259
|
-
|
|
265
|
+
# Store all discovered agents temporarily for tier precedence
|
|
266
|
+
# Use a unique key that includes tier to prevent overwrites
|
|
267
|
+
registry_key = f"{agent_name}_{tier.value}"
|
|
268
|
+
self.registry[registry_key] = metadata
|
|
260
269
|
self.discovered_files.add(file_path)
|
|
261
270
|
logger.debug(
|
|
262
271
|
f"Discovered agent: {agent_name} ({tier.value}) at {file_path}"
|
|
@@ -269,9 +278,29 @@ class UnifiedAgentRegistry:
|
|
|
269
278
|
# Remove extension and use filename as agent name
|
|
270
279
|
name = file_path.stem
|
|
271
280
|
|
|
272
|
-
# Skip certain files
|
|
273
|
-
skip_files = {
|
|
274
|
-
|
|
281
|
+
# Skip certain files and non-agent templates
|
|
282
|
+
skip_files = {
|
|
283
|
+
"README",
|
|
284
|
+
"INSTRUCTIONS",
|
|
285
|
+
"template",
|
|
286
|
+
"example",
|
|
287
|
+
"base_agent",
|
|
288
|
+
"base_agent_template",
|
|
289
|
+
"agent_template",
|
|
290
|
+
"agent_schema",
|
|
291
|
+
"base_pm",
|
|
292
|
+
"workflow",
|
|
293
|
+
"output_style",
|
|
294
|
+
"memory",
|
|
295
|
+
"optimization_report",
|
|
296
|
+
"vercel_ops_instructions",
|
|
297
|
+
"agent-template",
|
|
298
|
+
"agent-schema", # Also handle hyphenated versions
|
|
299
|
+
}
|
|
300
|
+
# Case-insensitive comparison
|
|
301
|
+
if name.replace("-", "_").upper() in {
|
|
302
|
+
s.replace("-", "_").upper() for s in skip_files
|
|
303
|
+
}:
|
|
275
304
|
return None
|
|
276
305
|
|
|
277
306
|
# Normalize name
|
|
@@ -424,12 +453,14 @@ class UnifiedAgentRegistry:
|
|
|
424
453
|
|
|
425
454
|
def _apply_tier_precedence(self) -> None:
|
|
426
455
|
"""Apply tier precedence rules to resolve conflicts."""
|
|
427
|
-
# Group agents by name
|
|
456
|
+
# Group agents by their actual name (without tier suffix)
|
|
428
457
|
agent_groups = {}
|
|
429
|
-
for
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
agent_groups
|
|
458
|
+
for registry_key, metadata in self.registry.items():
|
|
459
|
+
# Extract the actual agent name (registry_key is "name_tier")
|
|
460
|
+
agent_name = metadata.name # Use the actual name from metadata
|
|
461
|
+
if agent_name not in agent_groups:
|
|
462
|
+
agent_groups[agent_name] = []
|
|
463
|
+
agent_groups[agent_name].append(metadata)
|
|
433
464
|
|
|
434
465
|
# Resolve conflicts using tier precedence
|
|
435
466
|
resolved_registry = {}
|
|
@@ -34,10 +34,14 @@ class AgentDiscoveryService:
|
|
|
34
34
|
self.logger = get_logger(__name__)
|
|
35
35
|
self.templates_dir = templates_dir
|
|
36
36
|
|
|
37
|
-
def list_available_agents(self) -> List[Dict[str, Any]]:
|
|
37
|
+
def list_available_agents(self, log_discovery: bool = True) -> List[Dict[str, Any]]:
|
|
38
38
|
"""
|
|
39
39
|
List all available agent templates with their metadata.
|
|
40
40
|
|
|
41
|
+
Args:
|
|
42
|
+
log_discovery: Whether to log discovery results (default: True).
|
|
43
|
+
Set to False when called from multi-source discovery to avoid duplicate logs.
|
|
44
|
+
|
|
41
45
|
Returns:
|
|
42
46
|
List of agent information dictionaries containing:
|
|
43
47
|
- name: Agent name
|
|
@@ -73,7 +77,11 @@ class AgentDiscoveryService:
|
|
|
73
77
|
# Sort by agent name for consistent ordering
|
|
74
78
|
agents.sort(key=lambda x: x.get("name", ""))
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
# Only log if requested (to avoid duplicate logging from multi-source discovery)
|
|
81
|
+
if log_discovery:
|
|
82
|
+
self.logger.info(
|
|
83
|
+
f"Discovered {len(agents)} available agent templates from {self.templates_dir.name}"
|
|
84
|
+
)
|
|
77
85
|
return agents
|
|
78
86
|
|
|
79
87
|
def get_filtered_templates(
|
|
@@ -153,7 +161,8 @@ class AgentDiscoveryService:
|
|
|
153
161
|
Dictionary mapping categories to lists of agent names
|
|
154
162
|
"""
|
|
155
163
|
categories = {}
|
|
156
|
-
|
|
164
|
+
# Don't log discovery when called internally
|
|
165
|
+
agents = self.list_available_agents(log_discovery=False)
|
|
157
166
|
|
|
158
167
|
for agent in agents:
|
|
159
168
|
agent_name = agent.get("name", "unknown")
|