claude-mpm 4.0.31__py3-none-any.whl → 4.0.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +33 -25
- claude_mpm/agents/INSTRUCTIONS.md +14 -10
- claude_mpm/agents/templates/documentation.json +51 -34
- claude_mpm/agents/templates/research.json +0 -11
- claude_mpm/cli/__init__.py +63 -26
- claude_mpm/cli/commands/agent_manager.py +10 -8
- claude_mpm/core/framework_loader.py +272 -113
- claude_mpm/dashboard/static/css/dashboard.css +449 -0
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
- claude_mpm/dashboard/static/js/components/build-tracker.js +289 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
- claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/js/dashboard.js +207 -31
- claude_mpm/dashboard/static/js/socket-client.js +85 -6
- claude_mpm/dashboard/templates/index.html +1 -0
- claude_mpm/hooks/claude_hooks/connection_pool.py +12 -2
- claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
- claude_mpm/hooks/claude_hooks/hook_handler.py +72 -10
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +86 -37
- claude_mpm/services/agents/deployment/agent_template_builder.py +18 -10
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +189 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -13
- claude_mpm/services/agents/memory/agent_memory_manager.py +141 -184
- claude_mpm/services/agents/memory/content_manager.py +182 -232
- claude_mpm/services/agents/memory/template_generator.py +4 -40
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/event_bus.py +334 -0
- claude_mpm/services/event_bus/relay.py +301 -0
- claude_mpm/services/events/__init__.py +44 -0
- claude_mpm/services/events/consumers/__init__.py +18 -0
- claude_mpm/services/events/consumers/dead_letter.py +296 -0
- claude_mpm/services/events/consumers/logging.py +183 -0
- claude_mpm/services/events/consumers/metrics.py +242 -0
- claude_mpm/services/events/consumers/socketio.py +376 -0
- claude_mpm/services/events/core.py +470 -0
- claude_mpm/services/events/interfaces.py +230 -0
- claude_mpm/services/events/producers/__init__.py +14 -0
- claude_mpm/services/events/producers/hook.py +269 -0
- claude_mpm/services/events/producers/system.py +327 -0
- claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
- claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
- claude_mpm/services/monitor_build_service.py +345 -0
- claude_mpm/services/socketio/event_normalizer.py +667 -0
- claude_mpm/services/socketio/handlers/connection.py +78 -20
- claude_mpm/services/socketio/handlers/hook.py +14 -5
- claude_mpm/services/socketio/migration_utils.py +329 -0
- claude_mpm/services/socketio/server/broadcaster.py +26 -33
- claude_mpm/services/socketio/server/core.py +4 -3
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/METADATA +4 -3
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/RECORD +71 -50
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.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
|