claude-mpm 5.0.2__py3-none-any.whl → 5.4.3__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/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
- claude_mpm/agents/agent_loader.py +10 -17
- claude_mpm/agents/base_agent_loader.py +10 -35
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_state_manager.py +67 -23
- claude_mpm/cli/commands/agents.py +446 -25
- claude_mpm/cli/commands/auto_configure.py +535 -233
- claude_mpm/cli/commands/configure.py +1500 -147
- claude_mpm/cli/commands/configure_agent_display.py +13 -6
- claude_mpm/cli/commands/mpm_init/core.py +158 -1
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/run.py +1 -39
- claude_mpm/cli/commands/skills.py +322 -19
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +8 -0
- claude_mpm/cli/interactive/agent_wizard.py +302 -195
- claude_mpm/cli/parsers/agents_parser.py +137 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
- claude_mpm/cli/parsers/base_parser.py +9 -0
- claude_mpm/cli/parsers/skills_parser.py +7 -0
- claude_mpm/cli/startup.py +133 -85
- claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
- claude_mpm/commands/mpm-agents-list.md +2 -2
- claude_mpm/commands/mpm-config-view.md +2 -2
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
- claude_mpm/commands/mpm-postmortem.md +123 -0
- claude_mpm/commands/mpm-session-resume.md +2 -2
- claude_mpm/commands/mpm-ticket-view.md +2 -2
- claude_mpm/config/agent_presets.py +312 -82
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -25
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/file_loader.py +54 -101
- claude_mpm/core/interactive_session.py +19 -5
- claude_mpm/core/oneshot_session.py +16 -4
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/shared/singleton_manager.py +11 -4
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +279 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
- claude_mpm/services/agents/git_source_manager.py +20 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
- claude_mpm/services/agents/toolchain_detector.py +6 -5
- claude_mpm/services/analysis/__init__.py +35 -0
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/command_deployment_service.py +106 -5
- claude_mpm/services/core/base.py +7 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/mcp_config_manager.py +75 -145
- claude_mpm/services/mcp_service_verifier.py +6 -3
- claude_mpm/services/monitor/daemon.py +37 -10
- claude_mpm/services/monitor/daemon_manager.py +134 -21
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/runner_configuration_service.py +16 -3
- claude_mpm/services/session_management_service.py +16 -4
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/progress.py +5 -1
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,6 @@ DESIGN DECISIONS:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
-
import contextlib
|
|
19
18
|
import os
|
|
20
19
|
import threading
|
|
21
20
|
import time
|
|
@@ -25,6 +24,8 @@ from typing import Dict, Optional
|
|
|
25
24
|
|
|
26
25
|
import socketio
|
|
27
26
|
from aiohttp import web
|
|
27
|
+
from watchdog.events import FileSystemEventHandler
|
|
28
|
+
from watchdog.observers import Observer
|
|
28
29
|
|
|
29
30
|
from ...core.enums import ServiceState
|
|
30
31
|
from ...core.logging_config import get_logger
|
|
@@ -45,6 +46,91 @@ except ImportError:
|
|
|
45
46
|
EVENTBUS_AVAILABLE = False
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
class SvelteBuildWatcher(FileSystemEventHandler):
|
|
50
|
+
"""File watcher for Svelte build directory changes.
|
|
51
|
+
|
|
52
|
+
Watches for file changes in svelte-build directory and triggers
|
|
53
|
+
hot reload via Socket.IO event emission.
|
|
54
|
+
|
|
55
|
+
STABILITY FIX: Added thread lock and stop() method to prevent timer leaks.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self, sio: socketio.AsyncServer, loop: asyncio.AbstractEventLoop, logger
|
|
60
|
+
):
|
|
61
|
+
"""Initialize the file watcher.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
sio: Socket.IO server instance for emitting events
|
|
65
|
+
loop: Event loop for async operations
|
|
66
|
+
logger: Logger instance
|
|
67
|
+
"""
|
|
68
|
+
super().__init__()
|
|
69
|
+
self.sio = sio
|
|
70
|
+
self.loop = loop
|
|
71
|
+
self.logger = logger
|
|
72
|
+
self.debounce_timer = None
|
|
73
|
+
self.debounce_delay = 0.5 # Wait 500ms after last change
|
|
74
|
+
self._timer_lock = threading.Lock() # STABILITY FIX: Prevent race condition
|
|
75
|
+
|
|
76
|
+
def stop(self):
|
|
77
|
+
"""Stop the watcher and cancel any pending timers.
|
|
78
|
+
|
|
79
|
+
STABILITY FIX: Ensures timer is cancelled on shutdown.
|
|
80
|
+
"""
|
|
81
|
+
with self._timer_lock:
|
|
82
|
+
if self.debounce_timer:
|
|
83
|
+
self.debounce_timer.cancel()
|
|
84
|
+
self.debounce_timer = None
|
|
85
|
+
|
|
86
|
+
def on_any_event(self, event):
|
|
87
|
+
"""Handle any file system event.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
event: File system event from watchdog
|
|
91
|
+
"""
|
|
92
|
+
# Ignore directory events and temporary files
|
|
93
|
+
if event.is_directory or event.src_path.endswith((".tmp", ".swp", "~")):
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
self.logger.debug(
|
|
97
|
+
f"File change detected: {event.event_type} - {event.src_path}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# STABILITY FIX: Use lock to prevent timer race condition
|
|
101
|
+
with self._timer_lock:
|
|
102
|
+
# Cancel existing timer
|
|
103
|
+
if self.debounce_timer:
|
|
104
|
+
self.debounce_timer.cancel()
|
|
105
|
+
|
|
106
|
+
# Schedule reload after debounce delay
|
|
107
|
+
self.debounce_timer = threading.Timer(
|
|
108
|
+
self.debounce_delay, self._trigger_reload
|
|
109
|
+
)
|
|
110
|
+
self.debounce_timer.start()
|
|
111
|
+
|
|
112
|
+
def _trigger_reload(self):
|
|
113
|
+
"""Trigger hot reload by emitting Socket.IO event."""
|
|
114
|
+
try:
|
|
115
|
+
# Schedule the async emit in the event loop
|
|
116
|
+
asyncio.run_coroutine_threadsafe(self._emit_reload_event(), self.loop)
|
|
117
|
+
self.logger.info("Hot reload triggered - Svelte build changed")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.error(f"Error triggering reload: {e}")
|
|
120
|
+
|
|
121
|
+
async def _emit_reload_event(self):
|
|
122
|
+
"""Emit the reload event to all connected clients."""
|
|
123
|
+
if self.sio:
|
|
124
|
+
await self.sio.emit(
|
|
125
|
+
"reload",
|
|
126
|
+
{
|
|
127
|
+
"type": "reload",
|
|
128
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
129
|
+
"reason": "svelte-build-updated",
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
48
134
|
class UnifiedMonitorServer:
|
|
49
135
|
"""Unified server that combines HTTP dashboard and Socket.IO functionality.
|
|
50
136
|
|
|
@@ -52,15 +138,19 @@ class UnifiedMonitorServer:
|
|
|
52
138
|
Replaces multiple competing server implementations with one stable solution.
|
|
53
139
|
"""
|
|
54
140
|
|
|
55
|
-
def __init__(
|
|
141
|
+
def __init__(
|
|
142
|
+
self, host: str = "localhost", port: int = 8765, enable_hot_reload: bool = False
|
|
143
|
+
):
|
|
56
144
|
"""Initialize the unified monitor server.
|
|
57
145
|
|
|
58
146
|
Args:
|
|
59
147
|
host: Host to bind to
|
|
60
148
|
port: Port to bind to
|
|
149
|
+
enable_hot_reload: Enable file watching and hot reload for development
|
|
61
150
|
"""
|
|
62
151
|
self.host = host
|
|
63
152
|
self.port = port
|
|
153
|
+
self.enable_hot_reload = enable_hot_reload
|
|
64
154
|
self.logger = get_logger(__name__)
|
|
65
155
|
|
|
66
156
|
# Core components
|
|
@@ -78,6 +168,10 @@ class UnifiedMonitorServer:
|
|
|
78
168
|
# High-performance event emitter
|
|
79
169
|
self.event_emitter = None
|
|
80
170
|
|
|
171
|
+
# File watching (optional for dev mode)
|
|
172
|
+
self.file_observer: Optional[Observer] = None
|
|
173
|
+
self.file_watcher: Optional[SvelteBuildWatcher] = None
|
|
174
|
+
|
|
81
175
|
# State
|
|
82
176
|
self.running = False
|
|
83
177
|
self.loop = None
|
|
@@ -184,6 +278,9 @@ class UnifiedMonitorServer:
|
|
|
184
278
|
|
|
185
279
|
time.sleep(0.1)
|
|
186
280
|
|
|
281
|
+
# STABILITY FIX: Give tasks more time to clean up before closing
|
|
282
|
+
time.sleep(0.5)
|
|
283
|
+
|
|
187
284
|
# Clear the event loop from the thread BEFORE closing
|
|
188
285
|
# This prevents other code from accidentally using it
|
|
189
286
|
asyncio.set_event_loop(None)
|
|
@@ -229,6 +326,10 @@ class UnifiedMonitorServer:
|
|
|
229
326
|
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
230
327
|
self.logger.info("Heartbeat task started (3-minute interval)")
|
|
231
328
|
|
|
329
|
+
# Setup file watching for hot reload (if enabled)
|
|
330
|
+
if self.enable_hot_reload:
|
|
331
|
+
self._setup_file_watcher()
|
|
332
|
+
|
|
232
333
|
# Setup HTTP routes
|
|
233
334
|
self._setup_http_routes()
|
|
234
335
|
|
|
@@ -304,20 +405,64 @@ class UnifiedMonitorServer:
|
|
|
304
405
|
self.logger.error(f"Error setting up event emitter: {e}")
|
|
305
406
|
raise
|
|
306
407
|
|
|
408
|
+
def _setup_file_watcher(self):
|
|
409
|
+
"""Setup file watcher for Svelte build directory.
|
|
410
|
+
|
|
411
|
+
Watches for changes in svelte-build and triggers hot reload.
|
|
412
|
+
Only enabled when enable_hot_reload is True.
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
416
|
+
svelte_build_dir = dashboard_dir / "static" / "svelte-build"
|
|
417
|
+
|
|
418
|
+
if not svelte_build_dir.exists():
|
|
419
|
+
self.logger.warning(
|
|
420
|
+
f"Svelte build directory not found: {svelte_build_dir}. "
|
|
421
|
+
"Hot reload disabled."
|
|
422
|
+
)
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# Create file watcher with Socket.IO reference
|
|
426
|
+
self.file_watcher = SvelteBuildWatcher(
|
|
427
|
+
sio=self.sio, loop=self.loop, logger=self.logger
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Create observer and schedule watching
|
|
431
|
+
self.file_observer = Observer()
|
|
432
|
+
self.file_observer.schedule(
|
|
433
|
+
self.file_watcher, str(svelte_build_dir), recursive=True
|
|
434
|
+
)
|
|
435
|
+
self.file_observer.start()
|
|
436
|
+
|
|
437
|
+
self.logger.info(f"🔥 Hot reload enabled - watching {svelte_build_dir}")
|
|
438
|
+
|
|
439
|
+
except Exception as e:
|
|
440
|
+
self.logger.error(f"Error setting up file watcher: {e}")
|
|
441
|
+
# Don't raise - hot reload is optional
|
|
442
|
+
|
|
307
443
|
def _setup_http_routes(self):
|
|
308
444
|
"""Setup HTTP routes for the dashboard."""
|
|
309
445
|
try:
|
|
310
|
-
# Dashboard static files
|
|
311
|
-
dashboard_dir = Path(__file__).parent.parent.parent / "dashboard"
|
|
446
|
+
# Dashboard static files - use .resolve() for absolute path
|
|
447
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
448
|
+
static_dir = dashboard_dir / "static"
|
|
312
449
|
|
|
313
|
-
# Main dashboard route
|
|
450
|
+
# Main dashboard route - serve Svelte dashboard
|
|
314
451
|
async def dashboard_index(request):
|
|
315
|
-
|
|
316
|
-
if
|
|
317
|
-
with
|
|
452
|
+
svelte_index = static_dir / "svelte-build" / "index.html"
|
|
453
|
+
if svelte_index.exists():
|
|
454
|
+
with svelte_index.open(encoding="utf-8") as f:
|
|
318
455
|
content = f.read()
|
|
319
456
|
return web.Response(text=content, content_type="text/html")
|
|
320
|
-
|
|
457
|
+
|
|
458
|
+
# Log error with path details for debugging
|
|
459
|
+
self.logger.error(
|
|
460
|
+
f"Dashboard index.html not found at: {svelte_index.resolve()}"
|
|
461
|
+
)
|
|
462
|
+
return web.Response(
|
|
463
|
+
text=f"Dashboard not found. Expected location: {svelte_index.resolve()}",
|
|
464
|
+
status=404,
|
|
465
|
+
)
|
|
321
466
|
|
|
322
467
|
# Health check
|
|
323
468
|
async def health_check(request):
|
|
@@ -325,7 +470,8 @@ class UnifiedMonitorServer:
|
|
|
325
470
|
version = "1.0.0"
|
|
326
471
|
try:
|
|
327
472
|
version_file = (
|
|
328
|
-
Path(__file__).parent.parent.parent.parent.parent
|
|
473
|
+
Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
474
|
+
/ "VERSION"
|
|
329
475
|
)
|
|
330
476
|
if version_file.exists():
|
|
331
477
|
version = version_file.read_text().strip()
|
|
@@ -546,12 +692,43 @@ class UnifiedMonitorServer:
|
|
|
546
692
|
"/monitor/events", lambda r: monitor_page_handler(r)
|
|
547
693
|
)
|
|
548
694
|
|
|
549
|
-
#
|
|
550
|
-
|
|
695
|
+
# Serve Svelte _app assets (compiled JS/CSS)
|
|
696
|
+
svelte_build_dir = static_dir / "svelte-build"
|
|
697
|
+
if svelte_build_dir.exists():
|
|
698
|
+
svelte_app_dir = svelte_build_dir / "_app"
|
|
699
|
+
if svelte_app_dir.exists():
|
|
700
|
+
# Serve _app assets with proper caching
|
|
701
|
+
async def app_assets_handler(request):
|
|
702
|
+
"""Serve Svelte _app assets."""
|
|
703
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
704
|
+
|
|
705
|
+
rel_path = request.match_info["filepath"]
|
|
706
|
+
file_path = svelte_app_dir / rel_path
|
|
707
|
+
|
|
708
|
+
if not file_path.exists() or not file_path.is_file():
|
|
709
|
+
raise web.HTTPNotFound()
|
|
710
|
+
|
|
711
|
+
response = FileResponse(file_path)
|
|
712
|
+
|
|
713
|
+
# Add cache headers for immutable assets
|
|
714
|
+
if "/immutable/" in str(rel_path):
|
|
715
|
+
response.headers["Cache-Control"] = (
|
|
716
|
+
"public, max-age=31536000, immutable"
|
|
717
|
+
)
|
|
718
|
+
else:
|
|
719
|
+
response.headers["Cache-Control"] = (
|
|
720
|
+
"no-cache, no-store, must-revalidate"
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
return response
|
|
724
|
+
|
|
725
|
+
self.app.router.add_get("/_app/{filepath:.*}", app_assets_handler)
|
|
726
|
+
|
|
727
|
+
# Legacy static files (for backward compatibility)
|
|
551
728
|
if static_dir.exists():
|
|
552
729
|
|
|
553
730
|
async def static_handler(request):
|
|
554
|
-
"""Serve static files with cache-control headers for development."""
|
|
731
|
+
"""Serve legacy static files with cache-control headers for development."""
|
|
555
732
|
|
|
556
733
|
from aiohttp.web_fileresponse import FileResponse
|
|
557
734
|
|
|
@@ -576,10 +753,13 @@ class UnifiedMonitorServer:
|
|
|
576
753
|
|
|
577
754
|
self.app.router.add_get("/static/{filepath:.*}", static_handler)
|
|
578
755
|
|
|
579
|
-
#
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
756
|
+
# Log dashboard availability
|
|
757
|
+
if svelte_build_dir.exists():
|
|
758
|
+
self.logger.info(
|
|
759
|
+
f"✅ Svelte dashboard available at / (root) (build: {svelte_build_dir})"
|
|
760
|
+
)
|
|
761
|
+
else:
|
|
762
|
+
self.logger.warning(f"Svelte build not found at: {svelte_build_dir}")
|
|
583
763
|
|
|
584
764
|
self.logger.info("HTTP routes registered successfully")
|
|
585
765
|
|
|
@@ -691,11 +871,37 @@ class UnifiedMonitorServer:
|
|
|
691
871
|
async def _cleanup_async(self):
|
|
692
872
|
"""Cleanup async resources."""
|
|
693
873
|
try:
|
|
874
|
+
# Stop file observer if running
|
|
875
|
+
# STABILITY FIX: Ensure watcher is stopped and verify observer termination
|
|
876
|
+
if self.file_observer:
|
|
877
|
+
try:
|
|
878
|
+
# Stop the watcher first to cancel pending timers
|
|
879
|
+
if self.file_watcher:
|
|
880
|
+
self.file_watcher.stop()
|
|
881
|
+
|
|
882
|
+
# Stop the observer
|
|
883
|
+
self.file_observer.stop()
|
|
884
|
+
self.file_observer.join(timeout=2)
|
|
885
|
+
|
|
886
|
+
# Verify observer actually stopped
|
|
887
|
+
if self.file_observer.is_alive():
|
|
888
|
+
self.logger.warning("File observer did not stop cleanly")
|
|
889
|
+
|
|
890
|
+
self.logger.debug("File observer stopped")
|
|
891
|
+
except Exception as e:
|
|
892
|
+
self.logger.debug(f"Error stopping file observer: {e}")
|
|
893
|
+
finally:
|
|
894
|
+
self.file_observer = None
|
|
895
|
+
self.file_watcher = None
|
|
896
|
+
|
|
694
897
|
# Cancel heartbeat task if running
|
|
898
|
+
# STABILITY FIX: Add timeout to prevent infinite wait on cancellation
|
|
695
899
|
if self.heartbeat_task and not self.heartbeat_task.done():
|
|
696
900
|
self.heartbeat_task.cancel()
|
|
697
|
-
|
|
698
|
-
await self.heartbeat_task
|
|
901
|
+
try:
|
|
902
|
+
await asyncio.wait_for(self.heartbeat_task, timeout=2.0)
|
|
903
|
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
|
904
|
+
pass
|
|
699
905
|
self.logger.debug("Heartbeat task cancelled")
|
|
700
906
|
|
|
701
907
|
# Close the Socket.IO server first to stop accepting new connections
|
|
@@ -58,6 +58,10 @@ class ProjectOrganizer:
|
|
|
58
58
|
".mcp-vector-search/",
|
|
59
59
|
".kuzu-memory/",
|
|
60
60
|
"kuzu-memories/", # kuzu-memory database directory
|
|
61
|
+
# User-specific config files (should NOT be committed)
|
|
62
|
+
".mcp.json",
|
|
63
|
+
".claude.json",
|
|
64
|
+
".claude/",
|
|
61
65
|
# Python artifacts
|
|
62
66
|
"__pycache__/",
|
|
63
67
|
"*.py[cod]",
|
|
@@ -10,10 +10,14 @@ This service handles:
|
|
|
10
10
|
5. Hook service registration
|
|
11
11
|
|
|
12
12
|
Extracted from ClaudeRunner to follow Single Responsibility Principle.
|
|
13
|
+
|
|
14
|
+
DEPENDENCY INJECTION:
|
|
15
|
+
This service uses protocol-based dependency injection to avoid circular imports
|
|
16
|
+
when registering the SessionManagementService.
|
|
13
17
|
"""
|
|
14
18
|
|
|
15
19
|
import os
|
|
16
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
17
21
|
|
|
18
22
|
from claude_mpm.core.base_service import BaseService
|
|
19
23
|
from claude_mpm.core.config import Config
|
|
@@ -22,6 +26,13 @@ from claude_mpm.core.logger import get_project_logger
|
|
|
22
26
|
from claude_mpm.core.shared.config_loader import ConfigLoader
|
|
23
27
|
from claude_mpm.services.core.interfaces import RunnerConfigurationInterface
|
|
24
28
|
|
|
29
|
+
# Protocol imports for type checking without circular dependencies
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from claude_mpm.core.protocols import ClaudeRunnerProtocol
|
|
32
|
+
else:
|
|
33
|
+
# At runtime, accept any object with matching interface
|
|
34
|
+
ClaudeRunnerProtocol = Any
|
|
35
|
+
|
|
25
36
|
|
|
26
37
|
class RunnerConfigurationService(BaseService, RunnerConfigurationInterface):
|
|
27
38
|
"""Service for configuring and initializing ClaudeRunner components."""
|
|
@@ -495,12 +506,14 @@ class RunnerConfigurationService(BaseService, RunnerConfigurationInterface):
|
|
|
495
506
|
)
|
|
496
507
|
return None
|
|
497
508
|
|
|
498
|
-
def register_session_management_service(
|
|
509
|
+
def register_session_management_service(
|
|
510
|
+
self, container, runner: "ClaudeRunnerProtocol"
|
|
511
|
+
):
|
|
499
512
|
"""Register session management service in the DI container.
|
|
500
513
|
|
|
501
514
|
Args:
|
|
502
515
|
container: DI container instance
|
|
503
|
-
runner: ClaudeRunner instance
|
|
516
|
+
runner: ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
|
|
504
517
|
|
|
505
518
|
Returns:
|
|
506
519
|
Initialized session management service or None if failed
|
|
@@ -9,29 +9,41 @@ This service handles:
|
|
|
9
9
|
4. Session logging and cleanup
|
|
10
10
|
|
|
11
11
|
Extracted from ClaudeRunner to follow Single Responsibility Principle.
|
|
12
|
+
|
|
13
|
+
DEPENDENCY INJECTION:
|
|
14
|
+
This service uses protocol-based dependency injection to avoid circular imports.
|
|
15
|
+
It accepts a ClaudeRunnerProtocol instead of importing ClaudeRunner directly.
|
|
12
16
|
"""
|
|
13
17
|
|
|
14
18
|
import time
|
|
15
19
|
import uuid
|
|
16
20
|
from datetime import timezone
|
|
17
|
-
from typing import Any, Dict, List, Optional
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
18
22
|
|
|
19
23
|
from claude_mpm.core.base_service import BaseService
|
|
20
24
|
from claude_mpm.core.enums import OperationResult, ServiceState
|
|
21
25
|
from claude_mpm.services.core.interfaces import SessionManagementInterface
|
|
22
26
|
|
|
27
|
+
# Protocol imports for type checking without circular dependencies
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from claude_mpm.core.protocols import ClaudeRunnerProtocol
|
|
30
|
+
else:
|
|
31
|
+
# At runtime, accept any object with matching interface
|
|
32
|
+
ClaudeRunnerProtocol = Any
|
|
33
|
+
|
|
23
34
|
|
|
24
35
|
class SessionManagementService(BaseService, SessionManagementInterface):
|
|
25
36
|
"""Service for managing Claude session orchestration."""
|
|
26
37
|
|
|
27
|
-
def __init__(self, runner=None):
|
|
38
|
+
def __init__(self, runner: Optional["ClaudeRunnerProtocol"] = None):
|
|
28
39
|
"""Initialize the session management service.
|
|
29
40
|
|
|
30
41
|
Args:
|
|
31
|
-
runner: ClaudeRunner instance
|
|
42
|
+
runner: ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
|
|
43
|
+
for delegation
|
|
32
44
|
"""
|
|
33
45
|
super().__init__(name="session_management_service")
|
|
34
|
-
self.runner = runner
|
|
46
|
+
self.runner: Optional[ClaudeRunnerProtocol] = runner
|
|
35
47
|
self.active_sessions = {} # Track active sessions
|
|
36
48
|
|
|
37
49
|
async def _initialize(self) -> None:
|
|
@@ -78,10 +78,13 @@ class NormalizedEvent:
|
|
|
78
78
|
subtype: str = "" # Specific event type
|
|
79
79
|
timestamp: str = "" # ISO format timestamp
|
|
80
80
|
data: Dict[str, Any] = field(default_factory=dict) # Event payload
|
|
81
|
+
correlation_id: Optional[str] = (
|
|
82
|
+
None # For correlating related events (e.g., pre_tool/post_tool)
|
|
83
|
+
)
|
|
81
84
|
|
|
82
85
|
def to_dict(self) -> Dict[str, Any]:
|
|
83
86
|
"""Convert to dictionary for emission."""
|
|
84
|
-
|
|
87
|
+
result = {
|
|
85
88
|
"event": self.event,
|
|
86
89
|
"source": self.source,
|
|
87
90
|
"type": self.type,
|
|
@@ -89,6 +92,10 @@ class NormalizedEvent:
|
|
|
89
92
|
"timestamp": self.timestamp,
|
|
90
93
|
"data": self.data,
|
|
91
94
|
}
|
|
95
|
+
# Include correlation_id if present
|
|
96
|
+
if self.correlation_id:
|
|
97
|
+
result["correlation_id"] = self.correlation_id
|
|
98
|
+
return result
|
|
92
99
|
|
|
93
100
|
|
|
94
101
|
class EventNormalizer:
|
|
@@ -218,6 +225,11 @@ class EventNormalizer:
|
|
|
218
225
|
# Get or generate timestamp
|
|
219
226
|
timestamp = self._extract_timestamp(event_data)
|
|
220
227
|
|
|
228
|
+
# Extract correlation_id if present
|
|
229
|
+
correlation_id = None
|
|
230
|
+
if isinstance(event_data, dict):
|
|
231
|
+
correlation_id = event_data.get("correlation_id")
|
|
232
|
+
|
|
221
233
|
# Create normalized event
|
|
222
234
|
normalized = NormalizedEvent(
|
|
223
235
|
event="claude_event",
|
|
@@ -226,6 +238,7 @@ class EventNormalizer:
|
|
|
226
238
|
subtype=subtype,
|
|
227
239
|
timestamp=timestamp,
|
|
228
240
|
data=data,
|
|
241
|
+
correlation_id=correlation_id,
|
|
229
242
|
)
|
|
230
243
|
|
|
231
244
|
self.stats["normalized"] += 1
|
|
@@ -281,6 +294,7 @@ class EventNormalizer:
|
|
|
281
294
|
"timestamp", datetime.now(timezone.utc).isoformat()
|
|
282
295
|
),
|
|
283
296
|
data=event_data.get("data", {}),
|
|
297
|
+
correlation_id=event_data.get("correlation_id"),
|
|
284
298
|
)
|
|
285
299
|
|
|
286
300
|
def _extract_event_info(self, event_data: Any) -> Tuple[str, str, Dict[str, Any]]:
|