claude-mpm 4.0.19__py3-none-any.whl → 4.0.22__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.
Files changed (61) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +4 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
  5. claude_mpm/agents/INSTRUCTIONS.md +74 -0
  6. claude_mpm/agents/OUTPUT_STYLE.md +84 -0
  7. claude_mpm/agents/WORKFLOW.md +308 -4
  8. claude_mpm/agents/agents_metadata.py +52 -0
  9. claude_mpm/agents/base_agent_loader.py +75 -19
  10. claude_mpm/agents/templates/__init__.py +4 -0
  11. claude_mpm/agents/templates/api_qa.json +206 -0
  12. claude_mpm/agents/templates/qa.json +1 -1
  13. claude_mpm/agents/templates/research.json +24 -16
  14. claude_mpm/agents/templates/ticketing.json +18 -5
  15. claude_mpm/agents/templates/vercel_ops_agent.json +281 -0
  16. claude_mpm/agents/templates/vercel_ops_instructions.md +582 -0
  17. claude_mpm/cli/__init__.py +23 -1
  18. claude_mpm/cli/__main__.py +4 -0
  19. claude_mpm/cli/commands/mcp_command_router.py +87 -1
  20. claude_mpm/cli/commands/mcp_install_commands.py +207 -26
  21. claude_mpm/cli/commands/memory.py +32 -5
  22. claude_mpm/cli/commands/run.py +33 -6
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/mcp_parser.py +23 -0
  25. claude_mpm/cli/parsers/run_parser.py +5 -0
  26. claude_mpm/cli/utils.py +17 -4
  27. claude_mpm/constants.py +1 -0
  28. claude_mpm/core/base_service.py +8 -2
  29. claude_mpm/core/config.py +122 -32
  30. claude_mpm/core/framework_loader.py +385 -34
  31. claude_mpm/core/interactive_session.py +77 -12
  32. claude_mpm/core/oneshot_session.py +7 -1
  33. claude_mpm/core/output_style_manager.py +468 -0
  34. claude_mpm/core/unified_paths.py +190 -21
  35. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
  36. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
  37. claude_mpm/init.py +1 -0
  38. claude_mpm/scripts/socketio_daemon.py +67 -7
  39. claude_mpm/scripts/socketio_daemon_hardened.py +897 -0
  40. claude_mpm/services/agents/deployment/agent_deployment.py +216 -10
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
  42. claude_mpm/services/agents/deployment/async_agent_deployment.py +65 -1
  43. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
  44. claude_mpm/services/agents/memory/__init__.py +0 -2
  45. claude_mpm/services/agents/memory/agent_memory_manager.py +577 -44
  46. claude_mpm/services/agents/memory/content_manager.py +144 -14
  47. claude_mpm/services/agents/memory/template_generator.py +7 -354
  48. claude_mpm/services/mcp_gateway/server/stdio_server.py +61 -169
  49. claude_mpm/services/memory_hook_service.py +62 -4
  50. claude_mpm/services/runner_configuration_service.py +5 -9
  51. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  52. claude_mpm/services/socketio/server/core.py +4 -0
  53. claude_mpm/services/socketio/server/main.py +23 -4
  54. claude_mpm/services/subprocess_launcher_service.py +5 -0
  55. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/METADATA +1 -1
  56. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/RECORD +60 -54
  57. claude_mpm/services/agents/memory/analyzer.py +0 -430
  58. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/WHEEL +0 -0
  59. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/entry_points.txt +0 -0
  60. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/licenses/LICENSE +0 -0
  61. {claude_mpm-4.0.19.dist-info → claude_mpm-4.0.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,441 @@
1
+ """Multi-Source Agent Deployment Service
2
+
3
+ This service implements proper version comparison across multiple agent sources,
4
+ ensuring the highest version agent is deployed regardless of source.
5
+
6
+ Key Features:
7
+ - Discovers agents from multiple sources (system templates, project agents, user agents)
8
+ - Compares versions across all sources
9
+ - Deploys the highest version for each agent
10
+ - Tracks which source provided the deployed agent
11
+ - Maintains backward compatibility with existing deployment modes
12
+ """
13
+
14
+ import json
15
+ import logging
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, Optional, Tuple
18
+
19
+ from claude_mpm.core.config import Config
20
+ from claude_mpm.core.logging_config import get_logger
21
+
22
+ from .agent_discovery_service import AgentDiscoveryService
23
+ from .agent_version_manager import AgentVersionManager
24
+
25
+
26
+ class MultiSourceAgentDeploymentService:
27
+ """Service for deploying agents from multiple sources with version comparison.
28
+
29
+ This service ensures that the highest version of each agent is deployed,
30
+ regardless of whether it comes from system templates, project agents, or
31
+ user agents.
32
+
33
+ WHY: The current system processes agents from a single source at a time,
34
+ which can result in lower version agents being deployed if they exist in
35
+ a higher priority source. This service fixes that by comparing versions
36
+ across all sources.
37
+ """
38
+
39
+ def __init__(self):
40
+ """Initialize the multi-source deployment service."""
41
+ self.logger = get_logger(__name__)
42
+ self.version_manager = AgentVersionManager()
43
+
44
+ def discover_agents_from_all_sources(
45
+ self,
46
+ system_templates_dir: Optional[Path] = None,
47
+ project_agents_dir: Optional[Path] = None,
48
+ user_agents_dir: Optional[Path] = None,
49
+ working_directory: Optional[Path] = None
50
+ ) -> Dict[str, List[Dict[str, Any]]]:
51
+ """Discover agents from all available sources.
52
+
53
+ Args:
54
+ system_templates_dir: Directory containing system agent templates
55
+ project_agents_dir: Directory containing project-specific agents
56
+ user_agents_dir: Directory containing user custom agents
57
+ working_directory: Current working directory for finding project agents
58
+
59
+ Returns:
60
+ Dictionary mapping agent names to list of agent info from different sources
61
+ """
62
+ agents_by_name = {}
63
+
64
+ # Determine directories if not provided
65
+ if not system_templates_dir:
66
+ # Use default system templates location
67
+ from claude_mpm.config.paths import paths
68
+ system_templates_dir = paths.agents_dir / "templates"
69
+
70
+ if not project_agents_dir and working_directory:
71
+ # Check for project agents in working directory
72
+ project_agents_dir = working_directory / ".claude-mpm" / "agents"
73
+ if not project_agents_dir.exists():
74
+ project_agents_dir = None
75
+
76
+ if not user_agents_dir:
77
+ # Check for user agents in home directory
78
+ user_agents_dir = Path.home() / ".claude-mpm" / "agents"
79
+ if not user_agents_dir.exists():
80
+ user_agents_dir = None
81
+
82
+ # Discover agents from each source
83
+ sources = [
84
+ ("system", system_templates_dir),
85
+ ("project", project_agents_dir),
86
+ ("user", user_agents_dir)
87
+ ]
88
+
89
+ for source_name, source_dir in sources:
90
+ if source_dir and source_dir.exists():
91
+ self.logger.debug(f"Discovering agents from {source_name} source: {source_dir}")
92
+ discovery_service = AgentDiscoveryService(source_dir)
93
+ agents = discovery_service.list_available_agents()
94
+
95
+ for agent_info in agents:
96
+ agent_name = agent_info.get("name")
97
+ if not agent_name:
98
+ continue
99
+
100
+ # Add source information
101
+ agent_info["source"] = source_name
102
+ agent_info["source_dir"] = str(source_dir)
103
+
104
+ # Initialize list if this is the first occurrence of this agent
105
+ if agent_name not in agents_by_name:
106
+ agents_by_name[agent_name] = []
107
+
108
+ agents_by_name[agent_name].append(agent_info)
109
+
110
+ self.logger.info(
111
+ f"Discovered {len(agents)} agents from {source_name} source"
112
+ )
113
+
114
+ return agents_by_name
115
+
116
+ def select_highest_version_agents(
117
+ self,
118
+ agents_by_name: Dict[str, List[Dict[str, Any]]]
119
+ ) -> Dict[str, Dict[str, Any]]:
120
+ """Select the highest version agent from multiple sources.
121
+
122
+ Args:
123
+ agents_by_name: Dictionary mapping agent names to list of agent info
124
+
125
+ Returns:
126
+ Dictionary mapping agent names to the highest version agent info
127
+ """
128
+ selected_agents = {}
129
+
130
+ for agent_name, agent_versions in agents_by_name.items():
131
+ if not agent_versions:
132
+ continue
133
+
134
+ # If only one version exists, use it
135
+ if len(agent_versions) == 1:
136
+ selected_agents[agent_name] = agent_versions[0]
137
+ self.logger.debug(
138
+ f"Agent '{agent_name}' has single source: {agent_versions[0]['source']}"
139
+ )
140
+ continue
141
+
142
+ # Compare versions to find the highest
143
+ highest_version_agent = None
144
+ highest_version_tuple = (0, 0, 0)
145
+
146
+ for agent_info in agent_versions:
147
+ version_str = agent_info.get("version", "0.0.0")
148
+ version_tuple = self.version_manager.parse_version(version_str)
149
+
150
+ self.logger.debug(
151
+ f"Agent '{agent_name}' from {agent_info['source']}: "
152
+ f"version {version_str} -> {version_tuple}"
153
+ )
154
+
155
+ # Compare with current highest
156
+ if self.version_manager.compare_versions(version_tuple, highest_version_tuple) > 0:
157
+ highest_version_agent = agent_info
158
+ highest_version_tuple = version_tuple
159
+
160
+ if highest_version_agent:
161
+ selected_agents[agent_name] = highest_version_agent
162
+ self.logger.info(
163
+ f"Selected agent '{agent_name}' version {highest_version_agent['version']} "
164
+ f"from {highest_version_agent['source']} source"
165
+ )
166
+
167
+ # Log if a higher priority source was overridden by version
168
+ for other_agent in agent_versions:
169
+ if other_agent != highest_version_agent:
170
+ other_version = self.version_manager.parse_version(
171
+ other_agent.get("version", "0.0.0")
172
+ )
173
+ if other_agent["source"] == "project" and highest_version_agent["source"] == "system":
174
+ self.logger.warning(
175
+ f"Project agent '{agent_name}' v{other_agent['version']} "
176
+ f"overridden by higher system version v{highest_version_agent['version']}"
177
+ )
178
+ elif other_agent["source"] == "user" and highest_version_agent["source"] in ["system", "project"]:
179
+ self.logger.warning(
180
+ f"User agent '{agent_name}' v{other_agent['version']} "
181
+ f"overridden by higher {highest_version_agent['source']} version v{highest_version_agent['version']}"
182
+ )
183
+
184
+ return selected_agents
185
+
186
+ def get_agents_for_deployment(
187
+ self,
188
+ system_templates_dir: Optional[Path] = None,
189
+ project_agents_dir: Optional[Path] = None,
190
+ user_agents_dir: Optional[Path] = None,
191
+ working_directory: Optional[Path] = None,
192
+ excluded_agents: Optional[List[str]] = None,
193
+ config: Optional[Config] = None
194
+ ) -> Tuple[Dict[str, Path], Dict[str, str]]:
195
+ """Get the highest version agents from all sources for deployment.
196
+
197
+ Args:
198
+ system_templates_dir: Directory containing system agent templates
199
+ project_agents_dir: Directory containing project-specific agents
200
+ user_agents_dir: Directory containing user custom agents
201
+ working_directory: Current working directory for finding project agents
202
+ excluded_agents: List of agent names to exclude from deployment
203
+ config: Configuration object for additional filtering
204
+
205
+ Returns:
206
+ Tuple of:
207
+ - Dictionary mapping agent names to template file paths
208
+ - Dictionary mapping agent names to their source
209
+ """
210
+ # Discover all available agents
211
+ agents_by_name = self.discover_agents_from_all_sources(
212
+ system_templates_dir=system_templates_dir,
213
+ project_agents_dir=project_agents_dir,
214
+ user_agents_dir=user_agents_dir,
215
+ working_directory=working_directory
216
+ )
217
+
218
+ # Select highest version for each agent
219
+ selected_agents = self.select_highest_version_agents(agents_by_name)
220
+
221
+ # Apply exclusion filters
222
+ if excluded_agents:
223
+ for agent_name in excluded_agents:
224
+ if agent_name in selected_agents:
225
+ self.logger.info(f"Excluding agent '{agent_name}' from deployment")
226
+ del selected_agents[agent_name]
227
+
228
+ # Apply config-based filtering if provided
229
+ if config:
230
+ selected_agents = self._apply_config_filters(selected_agents, config)
231
+
232
+ # Create deployment mappings
233
+ agents_to_deploy = {}
234
+ agent_sources = {}
235
+
236
+ for agent_name, agent_info in selected_agents.items():
237
+ template_path = Path(agent_info["path"])
238
+ if template_path.exists():
239
+ # Use the file stem as the key for consistency
240
+ file_stem = template_path.stem
241
+ agents_to_deploy[file_stem] = template_path
242
+ agent_sources[file_stem] = agent_info["source"]
243
+
244
+ # Also keep the display name mapping for logging
245
+ if file_stem != agent_name:
246
+ self.logger.debug(f"Mapping '{agent_name}' -> '{file_stem}'")
247
+ else:
248
+ self.logger.warning(
249
+ f"Template file not found for agent '{agent_name}': {template_path}"
250
+ )
251
+
252
+ self.logger.info(
253
+ f"Selected {len(agents_to_deploy)} agents for deployment "
254
+ f"(system: {sum(1 for s in agent_sources.values() if s == 'system')}, "
255
+ f"project: {sum(1 for s in agent_sources.values() if s == 'project')}, "
256
+ f"user: {sum(1 for s in agent_sources.values() if s == 'user')})"
257
+ )
258
+
259
+ return agents_to_deploy, agent_sources
260
+
261
+ def _apply_config_filters(
262
+ self,
263
+ selected_agents: Dict[str, Dict[str, Any]],
264
+ config: Config
265
+ ) -> Dict[str, Dict[str, Any]]:
266
+ """Apply configuration-based filtering to selected agents.
267
+
268
+ Args:
269
+ selected_agents: Dictionary of selected agents
270
+ config: Configuration object
271
+
272
+ Returns:
273
+ Filtered dictionary of agents
274
+ """
275
+ filtered_agents = {}
276
+
277
+ # Get exclusion patterns from config
278
+ exclusion_patterns = config.get("agent_deployment.exclusion_patterns", [])
279
+
280
+ # Get environment-specific exclusions
281
+ environment = config.get("environment", "development")
282
+ env_exclusions = config.get(f"agent_deployment.{environment}_exclusions", [])
283
+
284
+ for agent_name, agent_info in selected_agents.items():
285
+ # Check exclusion patterns
286
+ excluded = False
287
+
288
+ for pattern in exclusion_patterns:
289
+ if pattern in agent_name:
290
+ self.logger.debug(f"Excluding '{agent_name}' due to pattern '{pattern}'")
291
+ excluded = True
292
+ break
293
+
294
+ # Check environment exclusions
295
+ if not excluded and agent_name in env_exclusions:
296
+ self.logger.debug(f"Excluding '{agent_name}' due to {environment} environment")
297
+ excluded = True
298
+
299
+ if not excluded:
300
+ filtered_agents[agent_name] = agent_info
301
+
302
+ return filtered_agents
303
+
304
+ def compare_deployed_versions(
305
+ self,
306
+ deployed_agents_dir: Path,
307
+ agents_to_deploy: Dict[str, Path],
308
+ agent_sources: Dict[str, str]
309
+ ) -> Dict[str, Any]:
310
+ """Compare deployed agent versions with candidates for deployment.
311
+
312
+ Args:
313
+ deployed_agents_dir: Directory containing currently deployed agents
314
+ agents_to_deploy: Dictionary mapping agent names to template paths
315
+ agent_sources: Dictionary mapping agent names to their sources
316
+
317
+ Returns:
318
+ Dictionary with comparison results including which agents need updates
319
+ """
320
+ comparison_results = {
321
+ "needs_update": [],
322
+ "up_to_date": [],
323
+ "new_agents": [],
324
+ "version_upgrades": [],
325
+ "version_downgrades": [],
326
+ "source_changes": []
327
+ }
328
+
329
+ for agent_name, template_path in agents_to_deploy.items():
330
+ deployed_file = deployed_agents_dir / f"{agent_name}.md"
331
+
332
+ if not deployed_file.exists():
333
+ comparison_results["new_agents"].append({
334
+ "name": agent_name,
335
+ "source": agent_sources[agent_name],
336
+ "template": str(template_path)
337
+ })
338
+ comparison_results["needs_update"].append(agent_name)
339
+ continue
340
+
341
+ # Read template version
342
+ try:
343
+ template_data = json.loads(template_path.read_text())
344
+ template_version = self.version_manager.parse_version(
345
+ template_data.get("agent_version") or
346
+ template_data.get("version", "0.0.0")
347
+ )
348
+ except Exception as e:
349
+ self.logger.warning(f"Error reading template for '{agent_name}': {e}")
350
+ continue
351
+
352
+ # Read deployed version
353
+ try:
354
+ deployed_content = deployed_file.read_text()
355
+ deployed_version, _, _ = self.version_manager.extract_version_from_frontmatter(
356
+ deployed_content
357
+ )
358
+
359
+ # Extract source from deployed agent if available
360
+ deployed_source = "unknown"
361
+ if "source:" in deployed_content:
362
+ import re
363
+ source_match = re.search(r'^source:\s*(.+)$', deployed_content, re.MULTILINE)
364
+ if source_match:
365
+ deployed_source = source_match.group(1).strip()
366
+ except Exception as e:
367
+ self.logger.warning(f"Error reading deployed agent '{agent_name}': {e}")
368
+ comparison_results["needs_update"].append(agent_name)
369
+ continue
370
+
371
+ # Compare versions
372
+ version_comparison = self.version_manager.compare_versions(
373
+ template_version, deployed_version
374
+ )
375
+
376
+ if version_comparison > 0:
377
+ # Template version is higher
378
+ comparison_results["version_upgrades"].append({
379
+ "name": agent_name,
380
+ "deployed_version": self.version_manager.format_version_display(deployed_version),
381
+ "new_version": self.version_manager.format_version_display(template_version),
382
+ "source": agent_sources[agent_name],
383
+ "previous_source": deployed_source
384
+ })
385
+ comparison_results["needs_update"].append(agent_name)
386
+
387
+ if deployed_source != agent_sources[agent_name]:
388
+ comparison_results["source_changes"].append({
389
+ "name": agent_name,
390
+ "from_source": deployed_source,
391
+ "to_source": agent_sources[agent_name]
392
+ })
393
+ elif version_comparison < 0:
394
+ # Deployed version is higher (shouldn't happen with proper version management)
395
+ comparison_results["version_downgrades"].append({
396
+ "name": agent_name,
397
+ "deployed_version": self.version_manager.format_version_display(deployed_version),
398
+ "template_version": self.version_manager.format_version_display(template_version),
399
+ "warning": "Deployed version is higher than template"
400
+ })
401
+ # Don't add to needs_update - keep the higher version
402
+ else:
403
+ # Versions are equal
404
+ comparison_results["up_to_date"].append({
405
+ "name": agent_name,
406
+ "version": self.version_manager.format_version_display(deployed_version),
407
+ "source": agent_sources[agent_name]
408
+ })
409
+
410
+ # Log summary
411
+ self.logger.info(
412
+ f"Version comparison complete: "
413
+ f"{len(comparison_results['needs_update'])} need updates, "
414
+ f"{len(comparison_results['up_to_date'])} up to date, "
415
+ f"{len(comparison_results['new_agents'])} new agents"
416
+ )
417
+
418
+ if comparison_results["version_upgrades"]:
419
+ for upgrade in comparison_results["version_upgrades"]:
420
+ self.logger.info(
421
+ f" Upgrade: {upgrade['name']} "
422
+ f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
423
+ f"(from {upgrade['source']})"
424
+ )
425
+
426
+ if comparison_results["source_changes"]:
427
+ for change in comparison_results["source_changes"]:
428
+ self.logger.info(
429
+ f" Source change: {change['name']} "
430
+ f"from {change['from_source']} to {change['to_source']}"
431
+ )
432
+
433
+ if comparison_results["version_downgrades"]:
434
+ for downgrade in comparison_results["version_downgrades"]:
435
+ self.logger.warning(
436
+ f" Warning: {downgrade['name']} deployed version "
437
+ f"{downgrade['deployed_version']} is higher than template "
438
+ f"{downgrade['template_version']}"
439
+ )
440
+
441
+ return comparison_results
@@ -7,7 +7,6 @@ from .agent_persistence_service import (
7
7
  PersistenceRecord,
8
8
  PersistenceStrategy,
9
9
  )
10
- from .analyzer import MemoryAnalyzer
11
10
  from .content_manager import MemoryContentManager
12
11
  from .template_generator import MemoryTemplateGenerator
13
12
 
@@ -20,5 +19,4 @@ __all__ = [
20
19
  "PersistenceRecord",
21
20
  "MemoryTemplateGenerator",
22
21
  "MemoryContentManager",
23
- "MemoryAnalyzer",
24
22
  ]