claude-mpm 5.4.22__py3-none-any.whl → 5.4.48__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/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +739 -1052
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +1 -1
- claude_mpm/agents/base_agent.json +31 -0
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/cli/commands/agent_state_manager.py +10 -10
- claude_mpm/cli/commands/agents.py +9 -9
- claude_mpm/cli/commands/auto_configure.py +4 -4
- claude_mpm/cli/commands/configure.py +1 -1
- claude_mpm/cli/commands/configure_agent_display.py +10 -0
- claude_mpm/cli/commands/mpm_init/core.py +65 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +14 -18
- claude_mpm/cli/executor.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -6
- claude_mpm/cli/startup.py +346 -75
- claude_mpm/commands/mpm-config.md +13 -250
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -206
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +61 -441
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/loaders/agent_loader.py +1 -1
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/init.py +63 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +29 -19
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
- claude_mpm/services/agents/git_source_manager.py +19 -4
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +112 -6
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +473 -3
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/skills/git_skill_source_manager.py +101 -3
- claude_mpm/services/skills_deployer.py +4 -3
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +37 -6
- claude_mpm/services/socketio/server/core.py +262 -123
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +1 -1
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +7 -4
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +118 -79
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.22.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
|
@@ -135,8 +135,8 @@ class AgentTemplateBuilder:
|
|
|
135
135
|
break
|
|
136
136
|
|
|
137
137
|
# Stop at common repository root indicators (check AFTER finding BASE-AGENT.md)
|
|
138
|
-
#
|
|
139
|
-
if current_dir.name in [".claude-mpm", "
|
|
138
|
+
# Stop at cache root or .claude-mpm directory
|
|
139
|
+
if current_dir.name in [".claude-mpm", "cache"]:
|
|
140
140
|
self.logger.debug(
|
|
141
141
|
f"Reached repository root indicator at: {current_dir}"
|
|
142
142
|
)
|
|
@@ -419,7 +419,7 @@ class AgentTemplateBuilder:
|
|
|
419
419
|
if non_standard:
|
|
420
420
|
self.logger.info(f"Using non-standard tools: {non_standard}")
|
|
421
421
|
|
|
422
|
-
# Extract model from template
|
|
422
|
+
# Extract model from template (no fallback - preserve None if not specified)
|
|
423
423
|
capabilities_model = (
|
|
424
424
|
capabilities.get("model") if isinstance(capabilities, dict) else None
|
|
425
425
|
)
|
|
@@ -428,7 +428,7 @@ class AgentTemplateBuilder:
|
|
|
428
428
|
template_data.get("model")
|
|
429
429
|
or capabilities_model
|
|
430
430
|
or template_data.get("configuration_fields", {}).get("model")
|
|
431
|
-
|
|
431
|
+
# No default fallback - preserve None if not set
|
|
432
432
|
)
|
|
433
433
|
|
|
434
434
|
# Convert tools list to comma-separated string (without spaces for compatibility)
|
|
@@ -448,11 +448,11 @@ class AgentTemplateBuilder:
|
|
|
448
448
|
"opus": "opus",
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
model
|
|
451
|
+
# Only map model if it's not None
|
|
452
|
+
if model is not None:
|
|
453
|
+
if model in model_map:
|
|
454
|
+
model = model_map[model]
|
|
455
|
+
# If model is specified but not in map, keep as-is (no default)
|
|
456
456
|
|
|
457
457
|
# Get response format from template or use base agent default
|
|
458
458
|
template_data.get("response", {}).get("format", "structured")
|
|
@@ -559,8 +559,9 @@ class AgentTemplateBuilder:
|
|
|
559
559
|
f"description: {self._format_description_for_yaml(description)}"
|
|
560
560
|
)
|
|
561
561
|
|
|
562
|
-
# Add model field (required for Claude Code)
|
|
563
|
-
|
|
562
|
+
# Add model field only if explicitly set (not required for Claude Code)
|
|
563
|
+
if model is not None:
|
|
564
|
+
frontmatter_lines.append(f"model: {model}")
|
|
564
565
|
|
|
565
566
|
# Add type field (important for agent categorization)
|
|
566
567
|
if agent_type and agent_type != "general":
|
|
@@ -718,21 +719,30 @@ Only include memories that are:
|
|
|
718
719
|
"description", f"{name} agent for specialized tasks"
|
|
719
720
|
)
|
|
720
721
|
|
|
721
|
-
# Get tools and model
|
|
722
|
+
# Get tools and model (no fallback for model)
|
|
722
723
|
raw_tools = merged_config.get("tools")
|
|
723
724
|
tools = self.normalize_tools_input(raw_tools)
|
|
724
|
-
model = merged_config.get("model"
|
|
725
|
+
model = merged_config.get("model") # No default - preserve None
|
|
725
726
|
|
|
726
727
|
# Format tools as YAML list
|
|
727
728
|
tools_yaml = self.format_yaml_list(tools, 2)
|
|
728
729
|
|
|
729
730
|
# Build YAML content with only essential fields
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
731
|
+
yaml_lines = [
|
|
732
|
+
f"name: {name}",
|
|
733
|
+
f"description: {description}",
|
|
734
|
+
]
|
|
735
|
+
|
|
736
|
+
# Only include model if explicitly set
|
|
737
|
+
if model is not None:
|
|
738
|
+
yaml_lines.append(f"model: {model}")
|
|
739
|
+
|
|
740
|
+
yaml_lines.extend([
|
|
741
|
+
"tools:",
|
|
742
|
+
tools_yaml,
|
|
743
|
+
])
|
|
744
|
+
|
|
745
|
+
return "\n".join(yaml_lines) + "\n"
|
|
736
746
|
|
|
737
747
|
def merge_narrative_fields(self, base_data: dict, template_data: dict) -> dict:
|
|
738
748
|
"""
|
|
@@ -8,7 +8,7 @@ DEPLOYMENT ARCHITECTURE:
|
|
|
8
8
|
|
|
9
9
|
Agent Source Locations (Discovery):
|
|
10
10
|
-----------------------------------
|
|
11
|
-
1. System Agents: ~/.claude-mpm/cache/
|
|
11
|
+
1. System Agents: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/
|
|
12
12
|
- Synced from GitHub repository
|
|
13
13
|
- Read-only (managed by git pull)
|
|
14
14
|
- 44+ agents organized by category
|
|
@@ -39,7 +39,7 @@ Why Project-Level Deployment?
|
|
|
39
39
|
Example Flow:
|
|
40
40
|
-------------
|
|
41
41
|
1. User runs: claude-mpm agents deploy
|
|
42
|
-
2. Agents synced from GitHub → ~/.claude-mpm/cache/
|
|
42
|
+
2. Agents synced from GitHub → ~/.claude-mpm/cache/agents/
|
|
43
43
|
3. Agents deployed FROM cache → .claude/agents/
|
|
44
44
|
4. Claude Code discovers agents FROM .claude/agents/
|
|
45
45
|
|
|
@@ -551,38 +551,39 @@ class AsyncAgentDeploymentService:
|
|
|
551
551
|
or ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
|
|
552
552
|
)
|
|
553
553
|
|
|
554
|
-
# Get model from capabilities.model in new format
|
|
554
|
+
# Get model from capabilities.model in new format (no default fallback)
|
|
555
555
|
model = (
|
|
556
556
|
agent_data.get("capabilities", {}).get("model")
|
|
557
557
|
or agent_data.get("configuration_fields", {}).get("model")
|
|
558
|
-
|
|
558
|
+
# No default fallback - preserve None if not set
|
|
559
559
|
)
|
|
560
560
|
|
|
561
|
-
# Simplify model name for Claude Code
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
561
|
+
# Simplify model name for Claude Code (only if model is specified)
|
|
562
|
+
if model is not None:
|
|
563
|
+
model_map = {
|
|
564
|
+
"claude-4-sonnet-20250514": "sonnet",
|
|
565
|
+
"claude-sonnet-4-20250514": "sonnet",
|
|
566
|
+
"claude-opus-4-20250514": "opus",
|
|
567
|
+
"claude-3-opus-20240229": "opus",
|
|
568
|
+
"claude-3-haiku-20240307": "haiku",
|
|
569
|
+
"claude-3.5-sonnet": "sonnet",
|
|
570
|
+
"claude-3-sonnet": "sonnet",
|
|
571
|
+
}
|
|
572
|
+
# Better fallback: extract the model type (opus/sonnet/haiku) from the string
|
|
573
|
+
if model not in model_map:
|
|
574
|
+
if "opus" in model.lower():
|
|
575
|
+
model = "opus"
|
|
576
|
+
elif "sonnet" in model.lower():
|
|
577
|
+
model = "sonnet"
|
|
578
|
+
elif "haiku" in model.lower():
|
|
579
|
+
model = "haiku"
|
|
580
|
+
else:
|
|
581
|
+
# Last resort: try to extract from hyphenated format
|
|
582
|
+
model = model_map.get(
|
|
583
|
+
model, model.split("-")[-1] if "-" in model else model
|
|
584
|
+
)
|
|
579
585
|
else:
|
|
580
|
-
|
|
581
|
-
model = model_map.get(
|
|
582
|
-
model, model.split("-")[-1] if "-" in model else model
|
|
583
|
-
)
|
|
584
|
-
else:
|
|
585
|
-
model = model_map[model]
|
|
586
|
+
model = model_map[model]
|
|
586
587
|
|
|
587
588
|
# Convert tools list to comma-separated string for Claude Code compatibility
|
|
588
589
|
# IMPORTANT: No spaces after commas - Claude Code requires exact format
|
|
@@ -601,9 +602,12 @@ class AsyncAgentDeploymentService:
|
|
|
601
602
|
f"base_version: {self._format_version_display(base_version)}",
|
|
602
603
|
"author: claude-mpm", # Identify as system agent for deployment
|
|
603
604
|
f"tools: {tools_str}",
|
|
604
|
-
f"model: {model}",
|
|
605
605
|
]
|
|
606
606
|
|
|
607
|
+
# Only include model field if explicitly set
|
|
608
|
+
if model is not None:
|
|
609
|
+
frontmatter_lines.append(f"model: {model}")
|
|
610
|
+
|
|
607
611
|
# Add optional fields if present
|
|
608
612
|
# Check for color in metadata section (new format) or root (old format)
|
|
609
613
|
color = agent_data.get("metadata", {}).get("color") or agent_data.get("color")
|
|
@@ -157,7 +157,9 @@ class LocalTemplateDeploymentService:
|
|
|
157
157
|
|
|
158
158
|
# Add capabilities
|
|
159
159
|
if template.capabilities:
|
|
160
|
-
|
|
160
|
+
# Only include model if explicitly set (no default)
|
|
161
|
+
if "model" in template.capabilities:
|
|
162
|
+
frontmatter["model"] = template.capabilities["model"]
|
|
161
163
|
tools = template.capabilities.get("tools", "*")
|
|
162
164
|
if tools == "*":
|
|
163
165
|
frontmatter["tools"] = "all"
|
|
@@ -26,6 +26,18 @@ from .agent_version_manager import AgentVersionManager
|
|
|
26
26
|
from .remote_agent_discovery_service import RemoteAgentDiscoveryService
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _normalize_agent_name(name: str) -> str:
|
|
30
|
+
"""Normalize agent name for consistent comparison.
|
|
31
|
+
|
|
32
|
+
Converts spaces, underscores to hyphens and lowercases.
|
|
33
|
+
Examples:
|
|
34
|
+
"Dart Engineer" -> "dart-engineer"
|
|
35
|
+
"dart_engineer" -> "dart-engineer"
|
|
36
|
+
"DART-ENGINEER" -> "dart-engineer"
|
|
37
|
+
"""
|
|
38
|
+
return name.lower().replace(" ", "-").replace("_", "-")
|
|
39
|
+
|
|
40
|
+
|
|
29
41
|
class MultiSourceAgentDeploymentService:
|
|
30
42
|
"""Service for deploying agents from multiple sources with version comparison.
|
|
31
43
|
|
|
@@ -179,14 +191,14 @@ class MultiSourceAgentDeploymentService:
|
|
|
179
191
|
system_templates_dir: Optional[Path] = None,
|
|
180
192
|
project_agents_dir: Optional[Path] = None,
|
|
181
193
|
user_agents_dir: Optional[Path] = None,
|
|
182
|
-
|
|
194
|
+
agents_cache_dir: Optional[Path] = None,
|
|
183
195
|
working_directory: Optional[Path] = None,
|
|
184
196
|
) -> Dict[str, List[Dict[str, Any]]]:
|
|
185
|
-
"""Discover agents from all 4 tiers (system, user,
|
|
197
|
+
"""Discover agents from all 4 tiers (system, user, cache, project).
|
|
186
198
|
|
|
187
199
|
Priority hierarchy (highest to lowest):
|
|
188
200
|
4. Project agents - Highest priority, project-specific customizations
|
|
189
|
-
3.
|
|
201
|
+
3. Cached agents - GitHub-synced agents from cache
|
|
190
202
|
2. User agents - DEPRECATED, user-level customizations
|
|
191
203
|
1. System templates - Lowest priority, built-in agents
|
|
192
204
|
|
|
@@ -194,7 +206,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
194
206
|
system_templates_dir: Directory containing system agent templates
|
|
195
207
|
project_agents_dir: Directory containing project-specific agents
|
|
196
208
|
user_agents_dir: Directory containing user custom agents (DEPRECATED)
|
|
197
|
-
|
|
209
|
+
agents_cache_dir: Directory containing cached agents from Git sources
|
|
198
210
|
working_directory: Current working directory for finding project agents
|
|
199
211
|
|
|
200
212
|
Returns:
|
|
@@ -225,12 +237,12 @@ class MultiSourceAgentDeploymentService:
|
|
|
225
237
|
if not user_agents_dir.exists():
|
|
226
238
|
user_agents_dir = None
|
|
227
239
|
|
|
228
|
-
if not
|
|
229
|
-
# Check for
|
|
240
|
+
if not agents_cache_dir:
|
|
241
|
+
# Check for agents in cache directory
|
|
230
242
|
cache_dir = Path.home() / ".claude-mpm" / "cache"
|
|
231
|
-
|
|
232
|
-
if not
|
|
233
|
-
|
|
243
|
+
agents_cache_dir = cache_dir / "agents"
|
|
244
|
+
if not agents_cache_dir.exists():
|
|
245
|
+
agents_cache_dir = None
|
|
234
246
|
|
|
235
247
|
# Discover agents from each source in priority order
|
|
236
248
|
# Note: We process in reverse priority order (system first) and build up the dictionary
|
|
@@ -238,7 +250,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
238
250
|
sources = [
|
|
239
251
|
("system", system_templates_dir),
|
|
240
252
|
("user", user_agents_dir),
|
|
241
|
-
("remote",
|
|
253
|
+
("remote", agents_cache_dir),
|
|
242
254
|
("project", project_agents_dir),
|
|
243
255
|
]
|
|
244
256
|
|
|
@@ -323,7 +335,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
323
335
|
def get_agents_by_collection(
|
|
324
336
|
self,
|
|
325
337
|
collection_id: str,
|
|
326
|
-
|
|
338
|
+
agents_cache_dir: Optional[Path] = None,
|
|
327
339
|
) -> List[Dict[str, Any]]:
|
|
328
340
|
"""Get all agents from a specific collection.
|
|
329
341
|
|
|
@@ -331,7 +343,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
331
343
|
|
|
332
344
|
Args:
|
|
333
345
|
collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
|
|
334
|
-
|
|
346
|
+
agents_cache_dir: Directory containing agents cache
|
|
335
347
|
|
|
336
348
|
Returns:
|
|
337
349
|
List of agent dictionaries from the specified collection
|
|
@@ -342,18 +354,16 @@ class MultiSourceAgentDeploymentService:
|
|
|
342
354
|
>>> len(agents)
|
|
343
355
|
45
|
|
344
356
|
"""
|
|
345
|
-
if not
|
|
357
|
+
if not agents_cache_dir:
|
|
346
358
|
cache_dir = Path.home() / ".claude-mpm" / "cache"
|
|
347
|
-
|
|
359
|
+
agents_cache_dir = cache_dir / "agents"
|
|
348
360
|
|
|
349
|
-
if not
|
|
350
|
-
self.logger.warning(
|
|
351
|
-
f"Remote agents directory not found: {remote_agents_dir}"
|
|
352
|
-
)
|
|
361
|
+
if not agents_cache_dir.exists():
|
|
362
|
+
self.logger.warning(f"Agents cache directory not found: {agents_cache_dir}")
|
|
353
363
|
return []
|
|
354
364
|
|
|
355
365
|
# Use RemoteAgentDiscoveryService to get collection agents
|
|
356
|
-
remote_service = RemoteAgentDiscoveryService(
|
|
366
|
+
remote_service = RemoteAgentDiscoveryService(agents_cache_dir)
|
|
357
367
|
collection_agents = remote_service.get_agents_by_collection(collection_id)
|
|
358
368
|
|
|
359
369
|
self.logger.info(
|
|
@@ -470,7 +480,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
470
480
|
system_templates_dir: Optional[Path] = None,
|
|
471
481
|
project_agents_dir: Optional[Path] = None,
|
|
472
482
|
user_agents_dir: Optional[Path] = None,
|
|
473
|
-
|
|
483
|
+
agents_cache_dir: Optional[Path] = None,
|
|
474
484
|
working_directory: Optional[Path] = None,
|
|
475
485
|
excluded_agents: Optional[List[str]] = None,
|
|
476
486
|
config: Optional[Config] = None,
|
|
@@ -482,7 +492,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
482
492
|
system_templates_dir: Directory containing system agent templates
|
|
483
493
|
project_agents_dir: Directory containing project-specific agents
|
|
484
494
|
user_agents_dir: Directory containing user custom agents (DEPRECATED)
|
|
485
|
-
|
|
495
|
+
agents_cache_dir: Directory containing cached agents from Git sources
|
|
486
496
|
working_directory: Current working directory for finding project agents
|
|
487
497
|
excluded_agents: List of agent names to exclude from deployment
|
|
488
498
|
config: Configuration object for additional filtering
|
|
@@ -499,7 +509,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
499
509
|
system_templates_dir=system_templates_dir,
|
|
500
510
|
project_agents_dir=project_agents_dir,
|
|
501
511
|
user_agents_dir=user_agents_dir,
|
|
502
|
-
|
|
512
|
+
agents_cache_dir=agents_cache_dir,
|
|
503
513
|
working_directory=working_directory,
|
|
504
514
|
)
|
|
505
515
|
|
|
@@ -533,10 +543,44 @@ class MultiSourceAgentDeploymentService:
|
|
|
533
543
|
|
|
534
544
|
# Apply exclusion filters
|
|
535
545
|
if excluded_agents:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
546
|
+
# Find agents to remove by matching normalized names
|
|
547
|
+
# Normalization handles: "Dart Engineer", "dart_engineer", "dart-engineer"
|
|
548
|
+
agents_to_remove = []
|
|
549
|
+
excluded_set = {_normalize_agent_name(name) for name in excluded_agents}
|
|
550
|
+
|
|
551
|
+
for canonical_id, agent_info in list(selected_agents.items()):
|
|
552
|
+
# Check agent name field (normalized)
|
|
553
|
+
agent_name = _normalize_agent_name(agent_info.get("name", ""))
|
|
554
|
+
|
|
555
|
+
# Also check the agent_id portion of canonical_id (after the colon)
|
|
556
|
+
# Example: "bobmatnyc/claude-mpm-agents:pm" -> "pm"
|
|
557
|
+
raw_agent_id = (
|
|
558
|
+
canonical_id.split(":")[-1]
|
|
559
|
+
if ":" in canonical_id
|
|
560
|
+
else canonical_id
|
|
561
|
+
)
|
|
562
|
+
agent_id = _normalize_agent_name(raw_agent_id)
|
|
563
|
+
|
|
564
|
+
# Check file stem from path (most reliable match)
|
|
565
|
+
file_stem = ""
|
|
566
|
+
path_str = agent_info.get("path") or agent_info.get("file_path")
|
|
567
|
+
if path_str:
|
|
568
|
+
file_stem = _normalize_agent_name(Path(path_str).stem)
|
|
569
|
+
|
|
570
|
+
if (
|
|
571
|
+
agent_name in excluded_set
|
|
572
|
+
or agent_id in excluded_set
|
|
573
|
+
or file_stem in excluded_set
|
|
574
|
+
):
|
|
575
|
+
agents_to_remove.append(canonical_id)
|
|
576
|
+
self.logger.info(
|
|
577
|
+
f"Excluding agent '{agent_info.get('name', raw_agent_id)}' "
|
|
578
|
+
f"(canonical_id: {canonical_id}) from deployment"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Remove matched agents
|
|
582
|
+
for canonical_id in agents_to_remove:
|
|
583
|
+
del selected_agents[canonical_id]
|
|
540
584
|
|
|
541
585
|
# Apply config-based filtering if provided
|
|
542
586
|
if config:
|
|
@@ -585,6 +629,105 @@ class MultiSourceAgentDeploymentService:
|
|
|
585
629
|
|
|
586
630
|
return agents_to_deploy, agent_sources, cleanup_results
|
|
587
631
|
|
|
632
|
+
def cleanup_excluded_agents(
|
|
633
|
+
self,
|
|
634
|
+
deployed_agents_dir: Path,
|
|
635
|
+
agents_to_deploy: Dict[str, Path],
|
|
636
|
+
) -> Dict[str, Any]:
|
|
637
|
+
"""Remove agents from deployed directory that aren't in the deployment list.
|
|
638
|
+
|
|
639
|
+
Similar to skill cleanup logic, this removes agents that were previously
|
|
640
|
+
deployed but are no longer in the enabled agents list (e.g., filtered out
|
|
641
|
+
by profile configuration).
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
deployed_agents_dir: Directory containing deployed agents (~/.claude/agents)
|
|
645
|
+
agents_to_deploy: Dictionary mapping agent file stems to template paths
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
Dictionary with cleanup results:
|
|
649
|
+
- removed: List of removed agent names
|
|
650
|
+
- errors: List of errors during cleanup
|
|
651
|
+
"""
|
|
652
|
+
cleanup_results = {"removed": [], "errors": []}
|
|
653
|
+
|
|
654
|
+
# Safety check - only operate on deployed agents directory
|
|
655
|
+
if not deployed_agents_dir.exists():
|
|
656
|
+
self.logger.debug("Deployed agents directory does not exist, no cleanup needed")
|
|
657
|
+
return cleanup_results
|
|
658
|
+
|
|
659
|
+
# Build set of agent names that should exist (file stems without .md extension)
|
|
660
|
+
expected_agents = set(agents_to_deploy.keys())
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
# Check each file in deployed_agents_dir
|
|
664
|
+
for item in deployed_agents_dir.iterdir():
|
|
665
|
+
# Only process .md files
|
|
666
|
+
if not item.is_file() or item.suffix != ".md":
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
# Skip hidden files
|
|
670
|
+
if item.name.startswith("."):
|
|
671
|
+
continue
|
|
672
|
+
|
|
673
|
+
# Get agent name (file stem)
|
|
674
|
+
agent_name = item.stem
|
|
675
|
+
|
|
676
|
+
# Check if this agent should be kept
|
|
677
|
+
if agent_name not in expected_agents:
|
|
678
|
+
try:
|
|
679
|
+
# Security: Validate path is within deployed_agents_dir
|
|
680
|
+
resolved_item = item.resolve()
|
|
681
|
+
resolved_target = deployed_agents_dir.resolve()
|
|
682
|
+
|
|
683
|
+
if not str(resolved_item).startswith(str(resolved_target)):
|
|
684
|
+
self.logger.error(
|
|
685
|
+
f"Refusing to remove path outside target directory: {item}"
|
|
686
|
+
)
|
|
687
|
+
cleanup_results["errors"].append(
|
|
688
|
+
{
|
|
689
|
+
"agent": agent_name,
|
|
690
|
+
"error": "Path outside target directory",
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
continue
|
|
694
|
+
|
|
695
|
+
# Remove the agent file
|
|
696
|
+
item.unlink()
|
|
697
|
+
cleanup_results["removed"].append(agent_name)
|
|
698
|
+
self.logger.info(f"Removed excluded agent: {agent_name}")
|
|
699
|
+
|
|
700
|
+
except PermissionError as e:
|
|
701
|
+
error_msg = f"Permission denied removing {agent_name}: {e}"
|
|
702
|
+
self.logger.error(error_msg)
|
|
703
|
+
cleanup_results["errors"].append(
|
|
704
|
+
{"agent": agent_name, "error": error_msg}
|
|
705
|
+
)
|
|
706
|
+
except Exception as e:
|
|
707
|
+
error_msg = f"Error removing {agent_name}: {e}"
|
|
708
|
+
self.logger.error(error_msg)
|
|
709
|
+
cleanup_results["errors"].append(
|
|
710
|
+
{"agent": agent_name, "error": error_msg}
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
except Exception as e:
|
|
714
|
+
self.logger.error(f"Error during agent cleanup: {e}")
|
|
715
|
+
cleanup_results["errors"].append(
|
|
716
|
+
{"agent": "cleanup_process", "error": str(e)}
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
# Log cleanup summary
|
|
720
|
+
if cleanup_results["removed"]:
|
|
721
|
+
self.logger.info(
|
|
722
|
+
f"Cleanup complete: removed {len(cleanup_results['removed'])} excluded agents"
|
|
723
|
+
)
|
|
724
|
+
if cleanup_results["errors"]:
|
|
725
|
+
self.logger.warning(
|
|
726
|
+
f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
return cleanup_results
|
|
730
|
+
|
|
588
731
|
def cleanup_outdated_user_agents(
|
|
589
732
|
self,
|
|
590
733
|
agents_by_name: Dict[str, List[Dict[str, Any]]],
|