claude-mpm 4.1.4__py3-none-any.whl → 4.1.6__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/research.json +39 -13
- claude_mpm/cli/__init__.py +2 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/configure.py +1221 -0
- claude_mpm/cli/commands/configure_tui.py +1921 -0
- claude_mpm/cli/commands/tickets.py +365 -784
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/configure_parser.py +119 -0
- claude_mpm/cli/startup_logging.py +39 -12
- claude_mpm/constants.py +1 -0
- claude_mpm/core/output_style_manager.py +24 -0
- claude_mpm/core/socketio_pool.py +35 -3
- claude_mpm/core/unified_agent_registry.py +46 -15
- claude_mpm/dashboard/static/css/connection-status.css +370 -0
- claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/js/connection-manager.js +536 -0
- claude_mpm/dashboard/templates/index.html +11 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
- 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/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
- claude_mpm/services/event_bus/direct_relay.py +173 -0
- claude_mpm/services/infrastructure/__init__.py +31 -5
- claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
- claude_mpm/services/infrastructure/monitoring/base.py +130 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +218 -0
- claude_mpm/services/infrastructure/monitoring/process.py +342 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
- claude_mpm/services/infrastructure/monitoring/service.py +367 -0
- claude_mpm/services/infrastructure/monitoring.py +67 -1030
- claude_mpm/services/project/analyzer.py +13 -4
- claude_mpm/services/project/analyzer_refactored.py +450 -0
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +410 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/connection_manager.py +516 -0
- claude_mpm/services/socketio/server/core.py +63 -0
- claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
- claude_mpm/services/socketio/server/main.py +27 -1
- claude_mpm/services/ticket_manager.py +5 -1
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +67 -46
- claude_mpm/agents/OUTPUT_STYLE.md +0 -73
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
- claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
- claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/top_level.txt +0 -0
|
@@ -331,6 +331,13 @@ def create_parser(
|
|
|
331
331
|
except ImportError:
|
|
332
332
|
pass
|
|
333
333
|
|
|
334
|
+
try:
|
|
335
|
+
from .configure_parser import add_configure_subparser
|
|
336
|
+
|
|
337
|
+
add_configure_subparser(subparsers)
|
|
338
|
+
except ImportError:
|
|
339
|
+
pass
|
|
340
|
+
|
|
334
341
|
# Import and add additional command parsers from commands module
|
|
335
342
|
try:
|
|
336
343
|
from ..commands.aggregate import add_aggregate_parser
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure command parser for claude-mpm CLI.
|
|
3
|
+
|
|
4
|
+
WHY: This module contains all arguments specific to the interactive configuration
|
|
5
|
+
management interface, allowing users to enable/disable agents, edit templates,
|
|
6
|
+
and manage behavior files.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
from ...constants import CLICommands
|
|
12
|
+
from .base_parser import add_common_arguments
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_configure_subparser(subparsers) -> argparse.ArgumentParser:
|
|
16
|
+
"""
|
|
17
|
+
Add the configure subparser for interactive configuration management.
|
|
18
|
+
|
|
19
|
+
WHY: Users need an interactive way to manage agent configurations,
|
|
20
|
+
templates, and behavior files through a terminal-based interface.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
subparsers: The subparsers object from the main parser
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The configured configure subparser
|
|
27
|
+
"""
|
|
28
|
+
# Configure command - interactive TUI configuration
|
|
29
|
+
configure_parser = subparsers.add_parser(
|
|
30
|
+
CLICommands.CONFIGURE.value,
|
|
31
|
+
help="Interactive terminal-based configuration interface for managing agents and behaviors",
|
|
32
|
+
description="Launch an interactive Rich-based TUI for configuring claude-mpm agents, templates, and behavior files",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Add common arguments
|
|
36
|
+
add_common_arguments(configure_parser)
|
|
37
|
+
|
|
38
|
+
# Configuration scope options
|
|
39
|
+
scope_group = configure_parser.add_argument_group("configuration scope")
|
|
40
|
+
scope_group.add_argument(
|
|
41
|
+
"--scope",
|
|
42
|
+
choices=["project", "user"],
|
|
43
|
+
default="project",
|
|
44
|
+
help="Configuration scope to manage (default: project)",
|
|
45
|
+
)
|
|
46
|
+
# Note: --project-dir is already defined in base_parser.py
|
|
47
|
+
|
|
48
|
+
# Direct navigation options (skip main menu)
|
|
49
|
+
nav_group = configure_parser.add_argument_group("direct navigation")
|
|
50
|
+
nav_group.add_argument(
|
|
51
|
+
"--agents", action="store_true", help="Jump directly to agent management"
|
|
52
|
+
)
|
|
53
|
+
nav_group.add_argument(
|
|
54
|
+
"--templates", action="store_true", help="Jump directly to template editing"
|
|
55
|
+
)
|
|
56
|
+
nav_group.add_argument(
|
|
57
|
+
"--behaviors",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="Jump directly to behavior file management",
|
|
60
|
+
)
|
|
61
|
+
nav_group.add_argument(
|
|
62
|
+
"--version-info",
|
|
63
|
+
action="store_true",
|
|
64
|
+
help="Display version information and exit",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Non-interactive options
|
|
68
|
+
noninteractive_group = configure_parser.add_argument_group(
|
|
69
|
+
"non-interactive options"
|
|
70
|
+
)
|
|
71
|
+
noninteractive_group.add_argument(
|
|
72
|
+
"--list-agents", action="store_true", help="List all available agents and exit"
|
|
73
|
+
)
|
|
74
|
+
noninteractive_group.add_argument(
|
|
75
|
+
"--enable-agent",
|
|
76
|
+
type=str,
|
|
77
|
+
metavar="AGENT_NAME",
|
|
78
|
+
help="Enable a specific agent and exit",
|
|
79
|
+
)
|
|
80
|
+
noninteractive_group.add_argument(
|
|
81
|
+
"--disable-agent",
|
|
82
|
+
type=str,
|
|
83
|
+
metavar="AGENT_NAME",
|
|
84
|
+
help="Disable a specific agent and exit",
|
|
85
|
+
)
|
|
86
|
+
noninteractive_group.add_argument(
|
|
87
|
+
"--export-config",
|
|
88
|
+
type=str,
|
|
89
|
+
metavar="FILE",
|
|
90
|
+
help="Export current configuration to a file",
|
|
91
|
+
)
|
|
92
|
+
noninteractive_group.add_argument(
|
|
93
|
+
"--import-config",
|
|
94
|
+
type=str,
|
|
95
|
+
metavar="FILE",
|
|
96
|
+
help="Import configuration from a file",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Display options
|
|
100
|
+
display_group = configure_parser.add_argument_group("display options")
|
|
101
|
+
display_group.add_argument(
|
|
102
|
+
"--no-colors", action="store_true", help="Disable colored output in the TUI"
|
|
103
|
+
)
|
|
104
|
+
display_group.add_argument(
|
|
105
|
+
"--compact", action="store_true", help="Use compact display mode"
|
|
106
|
+
)
|
|
107
|
+
display_group.add_argument(
|
|
108
|
+
"--force-rich",
|
|
109
|
+
action="store_true",
|
|
110
|
+
help="Force use of Rich menu interface instead of full-screen Textual TUI",
|
|
111
|
+
)
|
|
112
|
+
display_group.add_argument(
|
|
113
|
+
"--no-textual",
|
|
114
|
+
dest="use_textual",
|
|
115
|
+
action="store_false",
|
|
116
|
+
help="Disable Textual TUI and use Rich menu interface",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return configure_parser
|
|
@@ -57,17 +57,34 @@ def log_memory_stats(logger=None, prefix="Memory Usage"):
|
|
|
57
57
|
rss_mb = memory_info.rss / (1024 * 1024)
|
|
58
58
|
vms_mb = memory_info.vms / (1024 * 1024)
|
|
59
59
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
# On macOS, VMS can report misleading values (400+ TB)
|
|
61
|
+
# Skip VMS reporting if it's unreasonably large
|
|
62
|
+
import platform
|
|
63
|
+
|
|
64
|
+
if platform.system() == "Darwin" and vms_mb > 100000: # > 100GB is suspicious
|
|
65
|
+
# Get percentage of system memory if available
|
|
66
|
+
try:
|
|
67
|
+
memory_percent = process.memory_percent()
|
|
68
|
+
logger.info(
|
|
69
|
+
f"{prefix}: RSS={rss_mb:.1f}MB, System={memory_percent:.1f}%"
|
|
70
|
+
)
|
|
71
|
+
return {"rss_mb": rss_mb, "vms_mb": None, "percent": memory_percent}
|
|
72
|
+
except:
|
|
73
|
+
logger.info(f"{prefix}: RSS={rss_mb:.1f}MB")
|
|
74
|
+
return {"rss_mb": rss_mb, "vms_mb": None, "percent": None}
|
|
75
|
+
else:
|
|
76
|
+
# Normal VMS reporting for non-macOS or reasonable values
|
|
77
|
+
# Get percentage of system memory if available
|
|
78
|
+
try:
|
|
79
|
+
memory_percent = process.memory_percent()
|
|
80
|
+
logger.info(
|
|
81
|
+
f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB, "
|
|
82
|
+
f"System={memory_percent:.1f}%"
|
|
83
|
+
)
|
|
84
|
+
return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": memory_percent}
|
|
85
|
+
except:
|
|
86
|
+
logger.info(f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
|
|
87
|
+
return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": None}
|
|
71
88
|
|
|
72
89
|
except Exception as e:
|
|
73
90
|
logger.debug(f"Failed to get memory info: {e}")
|
|
@@ -512,11 +529,21 @@ def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
|
|
|
512
529
|
# Log initial memory usage
|
|
513
530
|
if PSUTIL_AVAILABLE:
|
|
514
531
|
try:
|
|
532
|
+
import platform
|
|
533
|
+
|
|
515
534
|
process = psutil.Process()
|
|
516
535
|
memory_info = process.memory_info()
|
|
517
536
|
rss_mb = memory_info.rss / (1024 * 1024)
|
|
518
537
|
vms_mb = memory_info.vms / (1024 * 1024)
|
|
519
|
-
|
|
538
|
+
|
|
539
|
+
# On macOS, VMS can report misleading values (400+ TB)
|
|
540
|
+
# Skip VMS reporting if it's unreasonably large
|
|
541
|
+
if (
|
|
542
|
+
platform.system() == "Darwin" and vms_mb > 100000
|
|
543
|
+
): # > 100GB is suspicious
|
|
544
|
+
logger.info(f"Initial Memory: RSS={rss_mb:.1f}MB")
|
|
545
|
+
else:
|
|
546
|
+
logger.info(f"Initial Memory: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
|
|
520
547
|
except Exception as e:
|
|
521
548
|
logger.debug(f"Failed to get initial memory info: {e}")
|
|
522
549
|
|
claude_mpm/constants.py
CHANGED
|
@@ -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:
|
claude_mpm/core/socketio_pool.py
CHANGED
|
@@ -592,7 +592,7 @@ class SocketIOConnectionPool:
|
|
|
592
592
|
|
|
593
593
|
# 2-second timeout for connection
|
|
594
594
|
await asyncio.wait_for(
|
|
595
|
-
client.connect(self.server_url,
|
|
595
|
+
client.connect(self.server_url, wait=True),
|
|
596
596
|
timeout=2.0,
|
|
597
597
|
)
|
|
598
598
|
|
|
@@ -734,8 +734,8 @@ class SocketIOConnectionPool:
|
|
|
734
734
|
def emit(self, event: str, data: Dict[str, Any]) -> bool:
|
|
735
735
|
"""Emit an event through the connection pool.
|
|
736
736
|
|
|
737
|
-
This method provides compatibility for the legacy emit() interface
|
|
738
|
-
|
|
737
|
+
This method provides compatibility for the legacy emit() interface.
|
|
738
|
+
For critical hook events, we use direct emission to avoid batching delays.
|
|
739
739
|
|
|
740
740
|
Args:
|
|
741
741
|
event: Event name (e.g., "claude_event")
|
|
@@ -747,10 +747,42 @@ class SocketIOConnectionPool:
|
|
|
747
747
|
if not SOCKETIO_AVAILABLE or not self._running:
|
|
748
748
|
return False
|
|
749
749
|
|
|
750
|
+
# For critical claude_event, use direct emission to avoid batching delays
|
|
751
|
+
if event == "claude_event":
|
|
752
|
+
return self._emit_direct(event, data)
|
|
753
|
+
|
|
750
754
|
# Map to the modern emit_event method using default namespace
|
|
751
755
|
self.emit_event("/", event, data)
|
|
752
756
|
return True
|
|
753
757
|
|
|
758
|
+
def _emit_direct(self, event: str, data: Dict[str, Any]) -> bool:
|
|
759
|
+
"""Emit an event directly without batching.
|
|
760
|
+
|
|
761
|
+
This is used for critical events that need immediate delivery.
|
|
762
|
+
"""
|
|
763
|
+
try:
|
|
764
|
+
# Create a synchronous client for direct emission
|
|
765
|
+
import socketio
|
|
766
|
+
|
|
767
|
+
client = socketio.Client(logger=False, engineio_logger=False)
|
|
768
|
+
|
|
769
|
+
# Quick connect, emit, and disconnect
|
|
770
|
+
client.connect(self.server_url, wait=True, wait_timeout=1.0)
|
|
771
|
+
client.emit(event, data)
|
|
772
|
+
client.disconnect()
|
|
773
|
+
|
|
774
|
+
# Update stats
|
|
775
|
+
for stats in self.connection_stats.values():
|
|
776
|
+
stats.events_sent += 1
|
|
777
|
+
break
|
|
778
|
+
|
|
779
|
+
return True
|
|
780
|
+
except Exception as e:
|
|
781
|
+
self.logger.debug(f"Direct emit failed: {e}")
|
|
782
|
+
# Fall back to batched emission
|
|
783
|
+
self.emit_event("/", event, data)
|
|
784
|
+
return True
|
|
785
|
+
|
|
754
786
|
def get_stats(self) -> Dict[str, Any]:
|
|
755
787
|
"""Get connection pool statistics."""
|
|
756
788
|
with self.pool_lock:
|
|
@@ -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 = {}
|