claude-mpm 5.4.21__py3-none-any.whl → 5.4.59__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.

Files changed (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/MEMORY.md +1 -1
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
  6. claude_mpm/agents/WORKFLOW.md +5 -254
  7. claude_mpm/agents/agent_loader.py +1 -1
  8. claude_mpm/agents/base_agent.json +31 -0
  9. claude_mpm/agents/frontmatter_validator.py +2 -2
  10. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  11. claude_mpm/cli/commands/agents.py +9 -9
  12. claude_mpm/cli/commands/auto_configure.py +4 -4
  13. claude_mpm/cli/commands/configure.py +1 -1
  14. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  15. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  16. claude_mpm/cli/commands/postmortem.py +1 -1
  17. claude_mpm/cli/commands/profile.py +276 -0
  18. claude_mpm/cli/commands/skills.py +14 -18
  19. claude_mpm/cli/executor.py +10 -0
  20. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  21. claude_mpm/cli/parsers/base_parser.py +7 -0
  22. claude_mpm/cli/parsers/profile_parser.py +147 -0
  23. claude_mpm/cli/parsers/skills_parser.py +0 -6
  24. claude_mpm/cli/startup.py +506 -180
  25. claude_mpm/commands/mpm-config.md +13 -250
  26. claude_mpm/commands/mpm-doctor.md +9 -22
  27. claude_mpm/commands/mpm-help.md +5 -206
  28. claude_mpm/commands/mpm-init.md +81 -507
  29. claude_mpm/commands/mpm-monitor.md +15 -402
  30. claude_mpm/commands/mpm-organize.md +61 -441
  31. claude_mpm/commands/mpm-postmortem.md +6 -108
  32. claude_mpm/commands/mpm-session-resume.md +12 -363
  33. claude_mpm/commands/mpm-status.md +5 -69
  34. claude_mpm/commands/mpm-ticket-view.md +52 -495
  35. claude_mpm/commands/mpm-version.md +5 -107
  36. claude_mpm/core/config.py +2 -4
  37. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  38. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  39. claude_mpm/core/optimized_startup.py +61 -0
  40. claude_mpm/core/shared/config_loader.py +3 -1
  41. claude_mpm/core/unified_agent_registry.py +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  96. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  97. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  98. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  99. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  100. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  101. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  102. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  103. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  104. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  105. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  106. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  107. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  108. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  109. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  110. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  111. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  112. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  113. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  114. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
  124. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
  130. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  131. claude_mpm/init.py +276 -0
  132. claude_mpm/models/git_repository.py +3 -3
  133. claude_mpm/scripts/start_activity_logging.py +0 -0
  134. claude_mpm/services/agents/agent_builder.py +3 -3
  135. claude_mpm/services/agents/cache_git_manager.py +6 -6
  136. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  137. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  138. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  139. claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
  140. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  141. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  142. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  143. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  144. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
  145. claude_mpm/services/agents/git_source_manager.py +23 -4
  146. claude_mpm/services/agents/recommender.py +5 -3
  147. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  148. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  149. claude_mpm/services/agents/startup_sync.py +22 -2
  150. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  151. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  152. claude_mpm/services/git/git_operations_service.py +8 -8
  153. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  154. claude_mpm/services/monitor/server.py +473 -3
  155. claude_mpm/services/pm_skills_deployer.py +711 -0
  156. claude_mpm/services/profile_manager.py +337 -0
  157. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  158. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  159. claude_mpm/services/skills_deployer.py +161 -65
  160. claude_mpm/services/socketio/dashboard_server.py +1 -0
  161. claude_mpm/services/socketio/event_normalizer.py +37 -6
  162. claude_mpm/services/socketio/server/core.py +262 -123
  163. claude_mpm/skills/bundled/security-scanning.md +112 -0
  164. claude_mpm/skills/skill_manager.py +98 -3
  165. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  166. claude_mpm/utils/agent_dependency_loader.py +14 -2
  167. claude_mpm/utils/agent_filters.py +1 -1
  168. claude_mpm/utils/migration.py +4 -4
  169. claude_mpm/utils/robust_installer.py +47 -3
  170. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
  171. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
  172. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  173. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  174. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  175. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  176. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/top_level.txt +0 -0
@@ -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
- or "sonnet" # Default fallback
558
+ # No default fallback - preserve None if not set
559
559
  )
560
560
 
561
- # Simplify model name for Claude Code
562
- model_map = {
563
- "claude-4-sonnet-20250514": "sonnet",
564
- "claude-sonnet-4-20250514": "sonnet",
565
- "claude-opus-4-20250514": "opus",
566
- "claude-3-opus-20240229": "opus",
567
- "claude-3-haiku-20240307": "haiku",
568
- "claude-3.5-sonnet": "sonnet",
569
- "claude-3-sonnet": "sonnet",
570
- }
571
- # Better fallback: extract the model type (opus/sonnet/haiku) from the string
572
- if model not in model_map:
573
- if "opus" in model.lower():
574
- model = "opus"
575
- elif "sonnet" in model.lower():
576
- model = "sonnet"
577
- elif "haiku" in model.lower():
578
- model = "haiku"
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
- # Last resort: try to extract from hyphenated format
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
- frontmatter["model"] = template.capabilities.get("model", "sonnet")
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
- remote_agents_dir: Optional[Path] = None,
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, remote, project).
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. Remote agents - GitHub-synced agents from cache
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
- remote_agents_dir: Directory containing cached remote agents
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 remote_agents_dir:
229
- # Check for remote agents in cache directory
240
+ if not agents_cache_dir:
241
+ # Check for agents in cache directory
230
242
  cache_dir = Path.home() / ".claude-mpm" / "cache"
231
- remote_agents_dir = cache_dir / "remote-agents"
232
- if not remote_agents_dir.exists():
233
- remote_agents_dir = None
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", remote_agents_dir),
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
- remote_agents_dir: Optional[Path] = None,
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
- remote_agents_dir: Directory containing remote agents cache
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 remote_agents_dir:
357
+ if not agents_cache_dir:
346
358
  cache_dir = Path.home() / ".claude-mpm" / "cache"
347
- remote_agents_dir = cache_dir / "remote-agents"
359
+ agents_cache_dir = cache_dir / "agents"
348
360
 
349
- if not remote_agents_dir.exists():
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(remote_agents_dir)
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
- remote_agents_dir: Optional[Path] = None,
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
- remote_agents_dir: Directory containing cached remote agents
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
- remote_agents_dir=remote_agents_dir,
512
+ agents_cache_dir=agents_cache_dir,
503
513
  working_directory=working_directory,
504
514
  )
505
515
 
@@ -533,10 +543,42 @@ class MultiSourceAgentDeploymentService:
533
543
 
534
544
  # Apply exclusion filters
535
545
  if excluded_agents:
536
- for agent_name in excluded_agents:
537
- if agent_name in selected_agents:
538
- self.logger.info(f"Excluding agent '{agent_name}' from deployment")
539
- del selected_agents[agent_name]
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] if ":" in canonical_id else canonical_id
559
+ )
560
+ agent_id = _normalize_agent_name(raw_agent_id)
561
+
562
+ # Check file stem from path (most reliable match)
563
+ file_stem = ""
564
+ path_str = agent_info.get("path") or agent_info.get("file_path")
565
+ if path_str:
566
+ file_stem = _normalize_agent_name(Path(path_str).stem)
567
+
568
+ if (
569
+ agent_name in excluded_set
570
+ or agent_id in excluded_set
571
+ or file_stem in excluded_set
572
+ ):
573
+ agents_to_remove.append(canonical_id)
574
+ self.logger.info(
575
+ f"Excluding agent '{agent_info.get('name', raw_agent_id)}' "
576
+ f"(canonical_id: {canonical_id}) from deployment"
577
+ )
578
+
579
+ # Remove matched agents
580
+ for canonical_id in agents_to_remove:
581
+ del selected_agents[canonical_id]
540
582
 
541
583
  # Apply config-based filtering if provided
542
584
  if config:
@@ -585,6 +627,107 @@ class MultiSourceAgentDeploymentService:
585
627
 
586
628
  return agents_to_deploy, agent_sources, cleanup_results
587
629
 
630
+ def cleanup_excluded_agents(
631
+ self,
632
+ deployed_agents_dir: Path,
633
+ agents_to_deploy: Dict[str, Path],
634
+ ) -> Dict[str, Any]:
635
+ """Remove agents from deployed directory that aren't in the deployment list.
636
+
637
+ Similar to skill cleanup logic, this removes agents that were previously
638
+ deployed but are no longer in the enabled agents list (e.g., filtered out
639
+ by profile configuration).
640
+
641
+ Args:
642
+ deployed_agents_dir: Directory containing deployed agents (~/.claude/agents)
643
+ agents_to_deploy: Dictionary mapping agent file stems to template paths
644
+
645
+ Returns:
646
+ Dictionary with cleanup results:
647
+ - removed: List of removed agent names
648
+ - errors: List of errors during cleanup
649
+ """
650
+ cleanup_results = {"removed": [], "errors": []}
651
+
652
+ # Safety check - only operate on deployed agents directory
653
+ if not deployed_agents_dir.exists():
654
+ self.logger.debug(
655
+ "Deployed agents directory does not exist, no cleanup needed"
656
+ )
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]]],