claude-mpm 4.0.32__py3-none-any.whl → 4.1.0__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 (82) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +70 -2
  3. claude_mpm/agents/OUTPUT_STYLE.md +0 -11
  4. claude_mpm/agents/WORKFLOW.md +14 -2
  5. claude_mpm/agents/templates/documentation.json +51 -34
  6. claude_mpm/agents/templates/research.json +0 -11
  7. claude_mpm/cli/__init__.py +111 -33
  8. claude_mpm/cli/commands/agent_manager.py +10 -8
  9. claude_mpm/cli/commands/agents.py +82 -0
  10. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  11. claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
  12. claude_mpm/cli/parsers/agents_parser.py +27 -0
  13. claude_mpm/cli/parsers/base_parser.py +6 -0
  14. claude_mpm/cli/startup_logging.py +75 -0
  15. claude_mpm/core/framework_loader.py +173 -84
  16. claude_mpm/dashboard/static/css/dashboard.css +449 -0
  17. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  18. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  19. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  20. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  21. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  22. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  23. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  24. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
  25. claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
  26. claude_mpm/dashboard/static/js/components/build-tracker.js +323 -0
  27. claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
  28. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
  29. claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
  30. claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
  31. claude_mpm/dashboard/static/js/dashboard.js +207 -31
  32. claude_mpm/dashboard/static/js/socket-client.js +92 -11
  33. claude_mpm/dashboard/templates/index.html +1 -0
  34. claude_mpm/hooks/claude_hooks/connection_pool.py +25 -4
  35. claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
  36. claude_mpm/hooks/claude_hooks/hook_handler.py +125 -163
  37. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
  38. claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
  39. claude_mpm/services/agents/deployment/agent_deployment.py +34 -48
  40. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +20 -11
  42. claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
  43. claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
  44. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +396 -13
  45. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
  46. claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
  47. claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
  48. claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -85
  49. claude_mpm/services/agents/memory/content_manager.py +98 -105
  50. claude_mpm/services/event_bus/__init__.py +18 -0
  51. claude_mpm/services/event_bus/config.py +165 -0
  52. claude_mpm/services/event_bus/event_bus.py +349 -0
  53. claude_mpm/services/event_bus/relay.py +297 -0
  54. claude_mpm/services/events/__init__.py +44 -0
  55. claude_mpm/services/events/consumers/__init__.py +18 -0
  56. claude_mpm/services/events/consumers/dead_letter.py +296 -0
  57. claude_mpm/services/events/consumers/logging.py +183 -0
  58. claude_mpm/services/events/consumers/metrics.py +242 -0
  59. claude_mpm/services/events/consumers/socketio.py +376 -0
  60. claude_mpm/services/events/core.py +470 -0
  61. claude_mpm/services/events/interfaces.py +230 -0
  62. claude_mpm/services/events/producers/__init__.py +14 -0
  63. claude_mpm/services/events/producers/hook.py +269 -0
  64. claude_mpm/services/events/producers/system.py +327 -0
  65. claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
  67. claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
  68. claude_mpm/services/monitor_build_service.py +345 -0
  69. claude_mpm/services/socketio/event_normalizer.py +667 -0
  70. claude_mpm/services/socketio/handlers/connection.py +81 -23
  71. claude_mpm/services/socketio/handlers/hook.py +14 -5
  72. claude_mpm/services/socketio/migration_utils.py +329 -0
  73. claude_mpm/services/socketio/server/broadcaster.py +26 -33
  74. claude_mpm/services/socketio/server/core.py +29 -5
  75. claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
  76. claude_mpm/services/socketio/server/main.py +25 -0
  77. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +28 -9
  78. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +82 -56
  79. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
  80. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
  81. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
  82. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,345 @@
1
+ """Monitor UI Build Tracking Service.
2
+
3
+ WHY: The Monitor UI needs its own build tracking separate from the main MPM build
4
+ number to track UI-specific changes and deployments independently.
5
+
6
+ DESIGN DECISION:
7
+ - Uses atomic file operations for thread-safe build number management
8
+ - Stores build number in MONITOR_BUILD file at project root
9
+ - Formats as 4-digit zero-padded strings (0001, 0002, etc.)
10
+ - Provides both sync and async interfaces for flexibility
11
+ """
12
+
13
+ import asyncio
14
+ import fcntl
15
+ import json
16
+ import os
17
+ import tempfile
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Optional
20
+
21
+ from claude_mpm.core.base_service import BaseService
22
+ from claude_mpm.core.logger import get_logger
23
+
24
+
25
+ class MonitorBuildService(BaseService):
26
+ """Service for managing Monitor UI build numbers.
27
+
28
+ WHY: Separate build tracking allows the Monitor UI to evolve independently
29
+ of the main MPM framework, enabling rapid UI iterations without affecting
30
+ core framework versioning.
31
+ """
32
+
33
+ # Default values
34
+ DEFAULT_BUILD_NUMBER = 1
35
+ DEFAULT_VERSION = "1.0.0"
36
+ BUILD_FILE_NAME = "MONITOR_BUILD"
37
+
38
+ def __init__(self):
39
+ """Initialize the monitor build service."""
40
+ super().__init__(name="monitor_build_service")
41
+ self.logger = get_logger(self.__class__.__name__)
42
+
43
+ # Determine build file location
44
+ self._build_file_path = self._get_build_file_path()
45
+
46
+ # Cache for build info to reduce file I/O
47
+ self._cached_build_info: Optional[Dict[str, Any]] = None
48
+ self._cache_lock = asyncio.Lock()
49
+
50
+ def _get_build_file_path(self) -> Path:
51
+ """Get the path to the MONITOR_BUILD file.
52
+
53
+ WHY: Centralizes build file location logic, checking multiple
54
+ possible locations to support different installation scenarios.
55
+
56
+ Returns:
57
+ Path to the MONITOR_BUILD file
58
+ """
59
+ # Try project root first (development)
60
+ try:
61
+ from claude_mpm.config.paths import paths
62
+ build_file = paths.project_root / self.BUILD_FILE_NAME
63
+ if build_file.parent.exists():
64
+ return build_file
65
+ except ImportError:
66
+ pass
67
+
68
+ # Fallback to package root
69
+ package_root = Path(__file__).parent.parent.parent
70
+ return package_root / self.BUILD_FILE_NAME
71
+
72
+ async def _initialize(self) -> None:
73
+ """Initialize the service and ensure build file exists.
74
+
75
+ WHY: Ensures the build file exists with default values on first run,
76
+ preventing errors and providing a clean starting point.
77
+ """
78
+ await self._ensure_build_file_exists()
79
+ await self._load_build_info()
80
+
81
+ async def _cleanup(self) -> None:
82
+ """Cleanup service resources."""
83
+ self._cached_build_info = None
84
+
85
+ async def _ensure_build_file_exists(self) -> None:
86
+ """Ensure the MONITOR_BUILD file exists with default values.
87
+
88
+ WHY: Atomic file creation prevents race conditions when multiple
89
+ processes might try to create the file simultaneously.
90
+ """
91
+ if not self._build_file_path.exists():
92
+ default_info = {
93
+ "build_number": self.DEFAULT_BUILD_NUMBER,
94
+ "version": self.DEFAULT_VERSION,
95
+ "last_updated": None
96
+ }
97
+ await self._write_build_info(default_info)
98
+ self.logger.info(f"Created MONITOR_BUILD file at {self._build_file_path}")
99
+
100
+ async def _load_build_info(self) -> Dict[str, Any]:
101
+ """Load build information from file.
102
+
103
+ WHY: Centralizes file reading with error handling and caching
104
+ to improve performance and reliability.
105
+
106
+ Returns:
107
+ Dictionary with build information
108
+ """
109
+ async with self._cache_lock:
110
+ try:
111
+ if self._build_file_path.exists():
112
+ content = self._build_file_path.read_text().strip()
113
+
114
+ # Try to parse as JSON first
115
+ try:
116
+ self._cached_build_info = json.loads(content)
117
+ except json.JSONDecodeError:
118
+ # Fallback: treat as plain build number
119
+ try:
120
+ build_num = int(content)
121
+ self._cached_build_info = {
122
+ "build_number": build_num,
123
+ "version": self.DEFAULT_VERSION,
124
+ "last_updated": None
125
+ }
126
+ except ValueError:
127
+ # Invalid content, use defaults
128
+ self._cached_build_info = {
129
+ "build_number": self.DEFAULT_BUILD_NUMBER,
130
+ "version": self.DEFAULT_VERSION,
131
+ "last_updated": None
132
+ }
133
+ else:
134
+ self._cached_build_info = {
135
+ "build_number": self.DEFAULT_BUILD_NUMBER,
136
+ "version": self.DEFAULT_VERSION,
137
+ "last_updated": None
138
+ }
139
+ except Exception as e:
140
+ self.logger.error(f"Error loading build info: {e}")
141
+ self._cached_build_info = {
142
+ "build_number": self.DEFAULT_BUILD_NUMBER,
143
+ "version": self.DEFAULT_VERSION,
144
+ "last_updated": None
145
+ }
146
+
147
+ return self._cached_build_info
148
+
149
+ async def _write_build_info(self, info: Dict[str, Any]) -> None:
150
+ """Write build information to file atomically.
151
+
152
+ WHY: Atomic writes prevent file corruption if the process is
153
+ interrupted during the write operation.
154
+
155
+ Args:
156
+ info: Build information dictionary
157
+ """
158
+ # Add timestamp
159
+ from datetime import datetime
160
+ info["last_updated"] = datetime.utcnow().isoformat()
161
+
162
+ # Write atomically using temp file and rename
163
+ temp_fd, temp_path = tempfile.mkstemp(
164
+ dir=self._build_file_path.parent,
165
+ prefix=".monitor_build_",
166
+ suffix=".tmp"
167
+ )
168
+
169
+ try:
170
+ # Write JSON content
171
+ with os.fdopen(temp_fd, 'w') as f:
172
+ json.dump(info, f, indent=2)
173
+
174
+ # Atomic rename
175
+ Path(temp_path).replace(self._build_file_path)
176
+
177
+ # Update cache
178
+ async with self._cache_lock:
179
+ self._cached_build_info = info
180
+
181
+ except Exception as e:
182
+ # Clean up temp file on error
183
+ Path(temp_path).unlink(missing_ok=True)
184
+ raise e
185
+
186
+ async def get_build_number(self) -> int:
187
+ """Get the current monitor build number.
188
+
189
+ Returns:
190
+ Current build number as integer
191
+ """
192
+ info = await self._load_build_info()
193
+ return info.get("build_number", self.DEFAULT_BUILD_NUMBER)
194
+
195
+ async def get_formatted_build_number(self) -> str:
196
+ """Get the current build number as a 4-digit string.
197
+
198
+ WHY: Consistent 4-digit formatting ensures proper sorting
199
+ and display alignment in the UI.
200
+
201
+ Returns:
202
+ Build number as 4-digit zero-padded string
203
+ """
204
+ build_num = await self.get_build_number()
205
+ return f"{build_num:04d}"
206
+
207
+ async def increment_build_number(self) -> int:
208
+ """Increment and return the new build number.
209
+
210
+ WHY: Atomic increment operation ensures no build numbers are
211
+ skipped or duplicated even with concurrent access.
212
+
213
+ Returns:
214
+ New build number after incrementing
215
+ """
216
+ info = await self._load_build_info()
217
+ new_build = info.get("build_number", self.DEFAULT_BUILD_NUMBER) + 1
218
+ info["build_number"] = new_build
219
+ await self._write_build_info(info)
220
+
221
+ self.logger.info(f"Monitor build number incremented to {new_build:04d}")
222
+ return new_build
223
+
224
+ async def get_monitor_version(self) -> str:
225
+ """Get the monitor UI version.
226
+
227
+ Returns:
228
+ Monitor UI semantic version string
229
+ """
230
+ info = await self._load_build_info()
231
+ return info.get("version", self.DEFAULT_VERSION)
232
+
233
+ async def set_monitor_version(self, version: str) -> None:
234
+ """Set the monitor UI version.
235
+
236
+ Args:
237
+ version: New semantic version string
238
+ """
239
+ info = await self._load_build_info()
240
+ info["version"] = version
241
+ await self._write_build_info(info)
242
+ self.logger.info(f"Monitor version updated to {version}")
243
+
244
+ async def get_full_version_string(self) -> str:
245
+ """Get the full version string for display.
246
+
247
+ WHY: Combines semantic version with build number for complete
248
+ version identification in the UI.
249
+
250
+ Returns:
251
+ Full version string (e.g., "v1.0.0-0001")
252
+ """
253
+ version = await self.get_monitor_version()
254
+ build = await self.get_formatted_build_number()
255
+ return f"v{version}-{build}"
256
+
257
+ async def get_build_info(self) -> Dict[str, Any]:
258
+ """Get complete build information.
259
+
260
+ WHY: Provides all build metadata in a single call for
261
+ efficient transmission to the UI via SocketIO.
262
+
263
+ Returns:
264
+ Dictionary with all build information
265
+ """
266
+ info = await self._load_build_info()
267
+
268
+ # Get MPM version info
269
+ mpm_version = "unknown"
270
+ mpm_build = "unknown"
271
+
272
+ try:
273
+ from claude_mpm.services.version_service import VersionService
274
+ version_service = VersionService()
275
+ version_info = version_service.get_version_info()
276
+ mpm_version = version_info.get("base_version", "unknown")
277
+ mpm_build = version_info.get("build_number", "unknown")
278
+ except Exception as e:
279
+ self.logger.debug(f"Could not get MPM version info: {e}")
280
+
281
+ return {
282
+ "monitor": {
283
+ "version": info.get("version", self.DEFAULT_VERSION),
284
+ "build": info.get("build_number", self.DEFAULT_BUILD_NUMBER),
285
+ "formatted_build": f"{info.get('build_number', self.DEFAULT_BUILD_NUMBER):04d}",
286
+ "full_version": await self.get_full_version_string(),
287
+ "last_updated": info.get("last_updated")
288
+ },
289
+ "mpm": {
290
+ "version": mpm_version,
291
+ "build": mpm_build,
292
+ "full_version": f"v{mpm_version}-build.{mpm_build}" if mpm_build != "unknown" else f"v{mpm_version}"
293
+ }
294
+ }
295
+
296
+ # Synchronous convenience methods for non-async contexts
297
+
298
+ def get_build_number_sync(self) -> int:
299
+ """Synchronous version of get_build_number.
300
+
301
+ WHY: Some contexts (like SocketIO handlers) may not support
302
+ async operations directly.
303
+
304
+ Returns:
305
+ Current build number as integer
306
+ """
307
+ loop = asyncio.new_event_loop()
308
+ try:
309
+ return loop.run_until_complete(self.get_build_number())
310
+ finally:
311
+ loop.close()
312
+
313
+ def get_build_info_sync(self) -> Dict[str, Any]:
314
+ """Synchronous version of get_build_info.
315
+
316
+ WHY: SocketIO connection handlers often need synchronous
317
+ access to build information.
318
+
319
+ Returns:
320
+ Dictionary with all build information
321
+ """
322
+ loop = asyncio.new_event_loop()
323
+ try:
324
+ return loop.run_until_complete(self.get_build_info())
325
+ finally:
326
+ loop.close()
327
+
328
+
329
+ # Global instance for singleton pattern
330
+ _monitor_build_service: Optional[MonitorBuildService] = None
331
+
332
+
333
+ def get_monitor_build_service() -> MonitorBuildService:
334
+ """Get or create the global monitor build service instance.
335
+
336
+ WHY: Singleton pattern ensures consistent build number management
337
+ across the application.
338
+
339
+ Returns:
340
+ The global MonitorBuildService instance
341
+ """
342
+ global _monitor_build_service
343
+ if _monitor_build_service is None:
344
+ _monitor_build_service = MonitorBuildService()
345
+ return _monitor_build_service