claude-mpm 5.0.9__py3-none-any.whl → 5.4.41__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/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +468 -468
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +70 -2
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +175 -37
- claude_mpm/cli/commands/auto_configure.py +723 -236
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1262 -157
- claude_mpm/cli/commands/configure_agent_display.py +25 -6
- claude_mpm/cli/commands/mpm_init/core.py +225 -46
- 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 +1 -1
- claude_mpm/cli/commands/profile.py +277 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +21 -3
- claude_mpm/cli/interactive/agent_wizard.py +85 -10
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
- claude_mpm/cli/parsers/base_parser.py +12 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +148 -0
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +879 -149
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -287
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/optimized_startup.py +59 -0
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +28 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +63 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
- claude_mpm/services/agents/deployment/agent_template_builder.py +5 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +320 -29
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +546 -68
- claude_mpm/services/agents/git_source_manager.py +36 -2
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +13 -6
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/management/lifecycle.py +8 -1
- claude_mpm/services/monitor/server.py +698 -22
- claude_mpm/services/pm_skills_deployer.py +676 -0
- claude_mpm/services/profile_manager.py +331 -0
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +130 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/skill_manager.py +92 -3
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +57 -87
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +160 -211
- claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.41.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- 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/hooks/claude_hooks/__pycache__/__init__.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/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/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 -977
- 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-5.0.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/top_level.txt +0 -0
|
@@ -271,7 +271,22 @@ class SocketIOServerCore:
|
|
|
271
271
|
"""Handle POST /api/events from hook handlers."""
|
|
272
272
|
try:
|
|
273
273
|
# Parse JSON payload
|
|
274
|
-
|
|
274
|
+
payload = await request.json()
|
|
275
|
+
|
|
276
|
+
# Extract event data from payload (handles both direct and wrapped formats)
|
|
277
|
+
# ConnectionManagerService sends: {"namespace": "...", "event": "...", "data": {...}}
|
|
278
|
+
# Direct hook events may send data directly
|
|
279
|
+
# CRITICAL: Check if payload has the expected event structure (type, subtype, timestamp)
|
|
280
|
+
# If it does, use it directly. Only extract 'data' field if it's a wrapper object.
|
|
281
|
+
if "type" in payload and "subtype" in payload:
|
|
282
|
+
# Payload is already in normalized format, use it directly
|
|
283
|
+
event_data = payload
|
|
284
|
+
elif "data" in payload and isinstance(payload.get("data"), dict):
|
|
285
|
+
# Payload is a wrapper with 'data' field (from ConnectionManagerService)
|
|
286
|
+
event_data = payload["data"]
|
|
287
|
+
else:
|
|
288
|
+
# Fallback: use entire payload
|
|
289
|
+
event_data = payload
|
|
275
290
|
|
|
276
291
|
# Log receipt with more detail
|
|
277
292
|
event_type = (
|
|
@@ -292,38 +307,96 @@ class SocketIOServerCore:
|
|
|
292
307
|
|
|
293
308
|
normalizer = EventNormalizer()
|
|
294
309
|
|
|
310
|
+
# Map hook event names to dashboard subtypes
|
|
311
|
+
# Comprehensive mapping of all known Claude Code hook event types
|
|
312
|
+
subtype_map = {
|
|
313
|
+
# User interaction events
|
|
314
|
+
"UserPromptSubmit": "user_prompt_submit",
|
|
315
|
+
"UserPromptCancel": "user_prompt_cancel",
|
|
316
|
+
# Tool execution events
|
|
317
|
+
"PreToolUse": "pre_tool_use",
|
|
318
|
+
"PostToolUse": "post_tool_use",
|
|
319
|
+
"ToolStart": "tool_start",
|
|
320
|
+
"ToolUse": "tool_use",
|
|
321
|
+
# Assistant events
|
|
322
|
+
"AssistantResponse": "assistant_response",
|
|
323
|
+
# Session lifecycle events
|
|
324
|
+
"Start": "start",
|
|
325
|
+
"Stop": "stop",
|
|
326
|
+
"SessionStart": "session_start",
|
|
327
|
+
# Subagent events
|
|
328
|
+
"SubagentStart": "subagent_start",
|
|
329
|
+
"SubagentStop": "subagent_stop",
|
|
330
|
+
"SubagentEvent": "subagent_event",
|
|
331
|
+
# Task events
|
|
332
|
+
"Task": "task",
|
|
333
|
+
"TaskStart": "task_start",
|
|
334
|
+
"TaskComplete": "task_complete",
|
|
335
|
+
# File operation events
|
|
336
|
+
"FileWrite": "file_write",
|
|
337
|
+
"Write": "write",
|
|
338
|
+
# System events
|
|
339
|
+
"Notification": "notification",
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Helper function to convert PascalCase to snake_case
|
|
343
|
+
def to_snake_case(name: str) -> str:
|
|
344
|
+
"""Convert PascalCase event names to snake_case.
|
|
345
|
+
|
|
346
|
+
Examples:
|
|
347
|
+
UserPromptSubmit → user_prompt_submit
|
|
348
|
+
PreToolUse → pre_tool_use
|
|
349
|
+
TaskComplete → task_complete
|
|
350
|
+
"""
|
|
351
|
+
import re
|
|
352
|
+
|
|
353
|
+
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
|
|
354
|
+
|
|
355
|
+
# Get hook event name and map to subtype
|
|
356
|
+
hook_event_name = event_data.get("hook_event_name", "unknown")
|
|
357
|
+
subtype = subtype_map.get(
|
|
358
|
+
hook_event_name, to_snake_case(hook_event_name)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Debug log for unmapped events to discover new event types
|
|
362
|
+
if (
|
|
363
|
+
hook_event_name not in subtype_map
|
|
364
|
+
and hook_event_name != "unknown"
|
|
365
|
+
):
|
|
366
|
+
self.logger.debug(
|
|
367
|
+
f"Unmapped hook event: {hook_event_name} → {subtype}"
|
|
368
|
+
)
|
|
369
|
+
|
|
295
370
|
# Create the format expected by normalizer
|
|
296
371
|
raw_event = {
|
|
297
372
|
"type": "hook",
|
|
298
|
-
"subtype":
|
|
299
|
-
.lower()
|
|
300
|
-
.replace("submit", "")
|
|
301
|
-
.replace("use", "_use"),
|
|
373
|
+
"subtype": subtype,
|
|
302
374
|
"timestamp": event_data.get("timestamp"),
|
|
303
375
|
"data": event_data.get("hook_input_data", {}),
|
|
304
376
|
"source": "claude_hooks",
|
|
305
377
|
"session_id": event_data.get("session_id"),
|
|
306
378
|
}
|
|
307
379
|
|
|
308
|
-
# Map hook event names to dashboard subtypes
|
|
309
|
-
subtype_map = {
|
|
310
|
-
"UserPromptSubmit": "user_prompt",
|
|
311
|
-
"PreToolUse": "pre_tool",
|
|
312
|
-
"PostToolUse": "post_tool",
|
|
313
|
-
"Stop": "stop",
|
|
314
|
-
"SubagentStop": "subagent_stop",
|
|
315
|
-
"AssistantResponse": "assistant_response",
|
|
316
|
-
}
|
|
317
|
-
raw_event["subtype"] = subtype_map.get(
|
|
318
|
-
event_data.get("hook_event_name"), "unknown"
|
|
319
|
-
)
|
|
320
|
-
|
|
321
380
|
normalized = normalizer.normalize(raw_event, source="hook")
|
|
322
381
|
event_data = normalized.to_dict()
|
|
323
382
|
self.logger.debug(
|
|
324
383
|
f"Normalized event: type={event_data.get('type')}, subtype={event_data.get('subtype')}"
|
|
325
384
|
)
|
|
326
385
|
|
|
386
|
+
# Publish to EventBus for cross-component communication
|
|
387
|
+
# WHY: This allows other parts of the system to react to hook events
|
|
388
|
+
# without coupling to Socket.IO directly
|
|
389
|
+
try:
|
|
390
|
+
from claude_mpm.services.event_bus import EventBus
|
|
391
|
+
|
|
392
|
+
event_bus = EventBus.get_instance()
|
|
393
|
+
event_type = f"hook.{event_data.get('subtype', 'unknown')}"
|
|
394
|
+
event_bus.publish(event_type, event_data)
|
|
395
|
+
self.logger.debug(f"Published to EventBus: {event_type}")
|
|
396
|
+
except Exception as e:
|
|
397
|
+
# Non-fatal: EventBus publication failure shouldn't break event flow
|
|
398
|
+
self.logger.warning(f"Failed to publish to EventBus: {e}")
|
|
399
|
+
|
|
327
400
|
# Broadcast to all connected dashboard clients via SocketIO
|
|
328
401
|
if self.sio:
|
|
329
402
|
# CRITICAL: Use the main server's broadcaster for proper event handling
|
|
@@ -342,9 +415,23 @@ class SocketIOServerCore:
|
|
|
342
415
|
self.event_buffer.append(event_data)
|
|
343
416
|
self.stats["events_buffered"] = len(self.event_buffer)
|
|
344
417
|
|
|
345
|
-
# Add to main server's event history
|
|
346
|
-
|
|
418
|
+
# Add to main server's event history UNCONDITIONALLY
|
|
419
|
+
# WHY: event_history is always initialized in SocketIOServer.__init__
|
|
420
|
+
# This ensures events persist for new clients who connect later
|
|
421
|
+
if self.main_server and hasattr(
|
|
422
|
+
self.main_server, "event_history"
|
|
423
|
+
):
|
|
347
424
|
self.main_server.event_history.append(event_data)
|
|
425
|
+
self.logger.debug(
|
|
426
|
+
f"Added to history (total: {len(self.main_server.event_history)})"
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
# CRITICAL: Log warning if event_history is not available
|
|
430
|
+
# This indicates a configuration or initialization problem
|
|
431
|
+
self.logger.warning(
|
|
432
|
+
"event_history not initialized on main_server! "
|
|
433
|
+
"Events will not persist for new clients."
|
|
434
|
+
)
|
|
348
435
|
|
|
349
436
|
# Use the broadcaster's sio to emit (it's the same as self.sio)
|
|
350
437
|
# This ensures the event goes through the proper channels
|
|
@@ -378,6 +465,21 @@ class SocketIOServerCore:
|
|
|
378
465
|
self.event_buffer.append(event_data)
|
|
379
466
|
self.stats["events_buffered"] = len(self.event_buffer)
|
|
380
467
|
|
|
468
|
+
# Add to main server's event history (fallback path)
|
|
469
|
+
# WHY: Ensure events persist even when broadcaster is unavailable
|
|
470
|
+
if self.main_server and hasattr(
|
|
471
|
+
self.main_server, "event_history"
|
|
472
|
+
):
|
|
473
|
+
self.main_server.event_history.append(event_data)
|
|
474
|
+
self.logger.debug(
|
|
475
|
+
f"Added to history via fallback (total: {len(self.main_server.event_history)})"
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
self.logger.warning(
|
|
479
|
+
"event_history not initialized on main_server (fallback path)! "
|
|
480
|
+
"Events will not persist for new clients."
|
|
481
|
+
)
|
|
482
|
+
|
|
381
483
|
# Return 204 No Content for success
|
|
382
484
|
self.logger.debug(f"✅ HTTP event processed successfully: {event_type}")
|
|
383
485
|
return web.Response(status=204)
|
|
@@ -390,13 +492,54 @@ class SocketIOServerCore:
|
|
|
390
492
|
self.app.router.add_post("/api/events", api_events_handler)
|
|
391
493
|
self.logger.info("✅ HTTP API endpoint registered at /api/events")
|
|
392
494
|
|
|
495
|
+
# Add health check endpoint
|
|
496
|
+
async def health_handler(request):
|
|
497
|
+
"""Handle GET /api/health for health checks."""
|
|
498
|
+
try:
|
|
499
|
+
# Get server status
|
|
500
|
+
uptime_seconds = 0
|
|
501
|
+
if self.stats.get("start_time"):
|
|
502
|
+
uptime_seconds = int(
|
|
503
|
+
(
|
|
504
|
+
datetime.now(timezone.utc) - self.stats["start_time"]
|
|
505
|
+
).total_seconds()
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
health_data = {
|
|
509
|
+
"status": "healthy",
|
|
510
|
+
"service": "claude-mpm-socketio",
|
|
511
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
512
|
+
"uptime_seconds": uptime_seconds,
|
|
513
|
+
"connected_clients": len(self.connected_clients),
|
|
514
|
+
"total_events": self.stats.get("events_sent", 0),
|
|
515
|
+
"buffered_events": self.stats.get("events_buffered", 0),
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return web.json_response(health_data)
|
|
519
|
+
except Exception as e:
|
|
520
|
+
self.logger.error(f"Error in health check: {e}")
|
|
521
|
+
return web.json_response(
|
|
522
|
+
{
|
|
523
|
+
"status": "unhealthy",
|
|
524
|
+
"service": "claude-mpm-socketio",
|
|
525
|
+
"error": str(e),
|
|
526
|
+
},
|
|
527
|
+
status=503,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
self.app.router.add_get("/api/health", health_handler)
|
|
531
|
+
self.app.router.add_get("/health", health_handler) # Alias for convenience
|
|
532
|
+
self.logger.info(
|
|
533
|
+
"✅ Health check endpoints registered at /api/health and /health"
|
|
534
|
+
)
|
|
535
|
+
|
|
393
536
|
# Add working directory endpoint
|
|
394
537
|
async def working_directory_handler(request):
|
|
395
538
|
"""Handle GET /api/working-directory to provide current working directory."""
|
|
396
539
|
from pathlib import Path
|
|
397
540
|
|
|
398
541
|
try:
|
|
399
|
-
working_dir = Path.cwd()
|
|
542
|
+
working_dir = str(Path.cwd())
|
|
400
543
|
home_dir = str(Path.home())
|
|
401
544
|
|
|
402
545
|
return web.json_response(
|
|
@@ -486,6 +629,169 @@ class SocketIOServerCore:
|
|
|
486
629
|
self.app.router.add_get("/api/file/read", file_read_handler)
|
|
487
630
|
self.logger.info("✅ File reading API registered at /api/file/read")
|
|
488
631
|
|
|
632
|
+
# Add files listing endpoint for file browser
|
|
633
|
+
async def files_list_handler(request):
|
|
634
|
+
"""Handle GET /api/files for listing files in working directory."""
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
# Get working directory from query params or use current directory
|
|
638
|
+
working_dir = request.query.get("path", str(Path.cwd()))
|
|
639
|
+
abs_working_dir = Path(working_dir).resolve().expanduser()
|
|
640
|
+
|
|
641
|
+
# Security check - ensure directory is accessible
|
|
642
|
+
if not abs_working_dir.exists():
|
|
643
|
+
return web.json_response(
|
|
644
|
+
{"error": "Directory not found"}, status=404
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
if not abs_working_dir.is_dir():
|
|
648
|
+
return web.json_response(
|
|
649
|
+
{"error": "Path is not a directory"}, status=400
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
# Collect files and directories
|
|
653
|
+
files = []
|
|
654
|
+
directories = []
|
|
655
|
+
|
|
656
|
+
# Common patterns to exclude
|
|
657
|
+
exclude_patterns = {
|
|
658
|
+
".git",
|
|
659
|
+
".venv",
|
|
660
|
+
"venv",
|
|
661
|
+
"node_modules",
|
|
662
|
+
"__pycache__",
|
|
663
|
+
".pytest_cache",
|
|
664
|
+
".mypy_cache",
|
|
665
|
+
"dist",
|
|
666
|
+
"build",
|
|
667
|
+
".next",
|
|
668
|
+
"coverage",
|
|
669
|
+
".coverage",
|
|
670
|
+
".tox",
|
|
671
|
+
".eggs",
|
|
672
|
+
"*.egg-info",
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
for entry in abs_working_dir.iterdir():
|
|
676
|
+
# Skip hidden files and excluded patterns
|
|
677
|
+
if entry.name.startswith("."):
|
|
678
|
+
# Allow .py, .ts, .md, etc. files but skip directories like .git
|
|
679
|
+
if entry.is_dir():
|
|
680
|
+
continue
|
|
681
|
+
|
|
682
|
+
# Skip excluded directories
|
|
683
|
+
if entry.name in exclude_patterns:
|
|
684
|
+
continue
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
stat_info = entry.stat()
|
|
688
|
+
entry_data = {
|
|
689
|
+
"name": entry.name,
|
|
690
|
+
"path": str(entry),
|
|
691
|
+
"type": "directory" if entry.is_dir() else "file",
|
|
692
|
+
"size": stat_info.st_size if entry.is_file() else 0,
|
|
693
|
+
"modified": stat_info.st_mtime,
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if entry.is_dir():
|
|
697
|
+
directories.append(entry_data)
|
|
698
|
+
else:
|
|
699
|
+
# Add file extension for syntax highlighting
|
|
700
|
+
entry_data["extension"] = entry.suffix.lower()
|
|
701
|
+
files.append(entry_data)
|
|
702
|
+
except (PermissionError, OSError):
|
|
703
|
+
# Skip files we can't access
|
|
704
|
+
continue
|
|
705
|
+
|
|
706
|
+
# Sort directories and files alphabetically
|
|
707
|
+
directories.sort(key=lambda x: x["name"].lower())
|
|
708
|
+
files.sort(key=lambda x: x["name"].lower())
|
|
709
|
+
|
|
710
|
+
return web.json_response(
|
|
711
|
+
{
|
|
712
|
+
"success": True,
|
|
713
|
+
"path": str(abs_working_dir),
|
|
714
|
+
"directories": directories,
|
|
715
|
+
"files": files,
|
|
716
|
+
"total_files": len(files),
|
|
717
|
+
"total_directories": len(directories),
|
|
718
|
+
}
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
except Exception as e:
|
|
722
|
+
self.logger.error(f"Error listing files: {e}")
|
|
723
|
+
return web.json_response({"error": str(e)}, status=500)
|
|
724
|
+
|
|
725
|
+
self.app.router.add_get("/api/files", files_list_handler)
|
|
726
|
+
self.logger.info("✅ Files listing API registered at /api/files")
|
|
727
|
+
|
|
728
|
+
# Add git history endpoint
|
|
729
|
+
async def git_history_handler(request):
|
|
730
|
+
"""Handle POST /api/git-history for getting file git history."""
|
|
731
|
+
import subprocess
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
# Parse JSON body
|
|
735
|
+
data = await request.json()
|
|
736
|
+
file_path = data.get("path", "")
|
|
737
|
+
limit = data.get("limit", 10)
|
|
738
|
+
|
|
739
|
+
if not file_path:
|
|
740
|
+
return web.json_response(
|
|
741
|
+
{"success": False, "error": "No path provided", "commits": []},
|
|
742
|
+
status=400,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
abs_path = Path(Path(file_path).resolve().expanduser())
|
|
746
|
+
|
|
747
|
+
if not Path(abs_path).exists():
|
|
748
|
+
return web.json_response(
|
|
749
|
+
{"success": False, "error": "File not found", "commits": []},
|
|
750
|
+
status=404,
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
# Get git log for file
|
|
754
|
+
result = subprocess.run(
|
|
755
|
+
[
|
|
756
|
+
"git",
|
|
757
|
+
"log",
|
|
758
|
+
f"-{limit}",
|
|
759
|
+
"--pretty=format:%H|%an|%ar|%s",
|
|
760
|
+
"--",
|
|
761
|
+
abs_path,
|
|
762
|
+
],
|
|
763
|
+
check=False,
|
|
764
|
+
capture_output=True,
|
|
765
|
+
text=True,
|
|
766
|
+
cwd=Path(abs_path).parent,
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
commits = []
|
|
770
|
+
if result.returncode == 0 and result.stdout:
|
|
771
|
+
for line in result.stdout.strip().split("\n"):
|
|
772
|
+
if line:
|
|
773
|
+
parts = line.split("|", 3)
|
|
774
|
+
if len(parts) == 4:
|
|
775
|
+
commits.append(
|
|
776
|
+
{
|
|
777
|
+
"hash": parts[0][:7], # Short hash
|
|
778
|
+
"author": parts[1],
|
|
779
|
+
"date": parts[2],
|
|
780
|
+
"message": parts[3],
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
return web.json_response({"success": True, "commits": commits})
|
|
785
|
+
|
|
786
|
+
except Exception as e:
|
|
787
|
+
self.logger.error(f"Error getting git history: {e}")
|
|
788
|
+
return web.json_response(
|
|
789
|
+
{"success": False, "error": str(e), "commits": []}, status=500
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
self.app.router.add_post("/api/git-history", git_history_handler)
|
|
793
|
+
self.logger.info("✅ Git history API registered at /api/git-history")
|
|
794
|
+
|
|
489
795
|
def _setup_directory_api(self):
|
|
490
796
|
"""Setup simple directory listing API.
|
|
491
797
|
|
|
@@ -503,7 +809,7 @@ class SocketIOServerCore:
|
|
|
503
809
|
self.logger.error(f"Failed to setup directory API: {e}")
|
|
504
810
|
|
|
505
811
|
def _setup_static_files(self):
|
|
506
|
-
"""Setup static file serving for the dashboard."""
|
|
812
|
+
"""Setup static file serving for the Svelte dashboard."""
|
|
507
813
|
try:
|
|
508
814
|
# Add debug logging for deployment context
|
|
509
815
|
try:
|
|
@@ -516,65 +822,43 @@ class SocketIOServerCore:
|
|
|
516
822
|
except Exception as e:
|
|
517
823
|
self.logger.debug(f"Could not detect deployment context: {e}")
|
|
518
824
|
|
|
519
|
-
|
|
825
|
+
# Find Svelte build directory
|
|
826
|
+
svelte_build_path = self._find_static_path()
|
|
520
827
|
|
|
521
|
-
if
|
|
522
|
-
self.logger.info(f"✅
|
|
828
|
+
if svelte_build_path and svelte_build_path.exists():
|
|
829
|
+
self.logger.info(f"✅ Svelte dashboard found at: {svelte_build_path}")
|
|
830
|
+
self.dashboard_path = svelte_build_path
|
|
523
831
|
|
|
524
|
-
# Serve index.html at root
|
|
832
|
+
# Serve Svelte index.html at root
|
|
525
833
|
async def index_handler(request):
|
|
526
|
-
index_file =
|
|
834
|
+
index_file = svelte_build_path / "index.html"
|
|
527
835
|
if index_file.exists():
|
|
528
|
-
self.logger.debug(
|
|
836
|
+
self.logger.debug(
|
|
837
|
+
f"Serving Svelte dashboard from: {index_file}"
|
|
838
|
+
)
|
|
529
839
|
return web.FileResponse(index_file)
|
|
530
|
-
self.logger.warning(
|
|
531
|
-
f"Dashboard index.html not found at: {index_file}"
|
|
532
|
-
)
|
|
840
|
+
self.logger.warning(f"Svelte index.html not found at: {index_file}")
|
|
533
841
|
return web.Response(text="Dashboard not available", status=404)
|
|
534
842
|
|
|
535
843
|
self.app.router.add_get("/", index_handler)
|
|
536
844
|
|
|
537
|
-
# Serve
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if dashboard_template.exists():
|
|
543
|
-
self.logger.debug(
|
|
544
|
-
f"Serving dashboard template from: {dashboard_template}"
|
|
545
|
-
)
|
|
546
|
-
return web.FileResponse(dashboard_template)
|
|
547
|
-
# Fallback to the main index if template doesn't exist
|
|
548
|
-
self.logger.warning(
|
|
549
|
-
f"Dashboard template not found at: {dashboard_template}, falling back to index"
|
|
845
|
+
# Serve Svelte app assets at /_app/ (needed for SvelteKit builds)
|
|
846
|
+
svelte_app_path = svelte_build_path / "_app"
|
|
847
|
+
if svelte_app_path.exists():
|
|
848
|
+
self.app.router.add_static(
|
|
849
|
+
"/_app/", svelte_app_path, name="svelte_app"
|
|
550
850
|
)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
self.app.router.add_get("/dashboard", dashboard_handler)
|
|
554
|
-
|
|
555
|
-
# Serve simple code view template at /code-simple
|
|
556
|
-
async def code_simple_handler(request):
|
|
557
|
-
code_simple_template = (
|
|
558
|
-
self.dashboard_path.parent / "templates" / "code_simple.html"
|
|
851
|
+
self.logger.info(
|
|
852
|
+
f"✅ Svelte dashboard available at http://{self.host}:{self.port}/ (build: {svelte_build_path})"
|
|
559
853
|
)
|
|
560
|
-
|
|
561
|
-
self.logger.debug(
|
|
562
|
-
f"Serving code simple template from: {code_simple_template}"
|
|
563
|
-
)
|
|
564
|
-
return web.FileResponse(code_simple_template)
|
|
565
|
-
# Return error if template doesn't exist
|
|
854
|
+
else:
|
|
566
855
|
self.logger.warning(
|
|
567
|
-
f"
|
|
568
|
-
)
|
|
569
|
-
return web.Response(
|
|
570
|
-
text="Simple code view not available", status=404
|
|
856
|
+
f"⚠️ Svelte _app directory not found at: {svelte_app_path}"
|
|
571
857
|
)
|
|
572
858
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
# Serve version.json from dashboard directory
|
|
859
|
+
# Serve version.json from Svelte build directory
|
|
576
860
|
async def version_handler(request):
|
|
577
|
-
version_file =
|
|
861
|
+
version_file = svelte_build_path / "version.json"
|
|
578
862
|
if version_file.exists():
|
|
579
863
|
self.logger.debug(f"Serving version.json from: {version_file}")
|
|
580
864
|
return web.FileResponse(version_file)
|
|
@@ -590,24 +874,10 @@ class SocketIOServerCore:
|
|
|
590
874
|
|
|
591
875
|
self.app.router.add_get("/version.json", version_handler)
|
|
592
876
|
|
|
593
|
-
# Serve static assets (CSS, JS) from the dashboard static directory
|
|
594
|
-
dashboard_static_path = (
|
|
595
|
-
get_project_root() / "src" / "claude_mpm" / "dashboard" / "static"
|
|
596
|
-
)
|
|
597
|
-
if dashboard_static_path.exists():
|
|
598
|
-
self.app.router.add_static(
|
|
599
|
-
"/static/", dashboard_static_path, name="dashboard_static"
|
|
600
|
-
)
|
|
601
|
-
self.logger.info(
|
|
602
|
-
f"✅ Static assets available at: {dashboard_static_path}"
|
|
603
|
-
)
|
|
604
|
-
else:
|
|
605
|
-
self.logger.warning(
|
|
606
|
-
f"⚠️ Static assets directory not found at: {dashboard_static_path}"
|
|
607
|
-
)
|
|
608
|
-
|
|
609
877
|
else:
|
|
610
|
-
self.logger.warning(
|
|
878
|
+
self.logger.warning(
|
|
879
|
+
"⚠️ Svelte dashboard not found, serving fallback response"
|
|
880
|
+
)
|
|
611
881
|
|
|
612
882
|
# Fallback handler
|
|
613
883
|
async def fallback_handler(request):
|
|
@@ -634,10 +904,10 @@ class SocketIOServerCore:
|
|
|
634
904
|
self.app.router.add_get("/", error_handler)
|
|
635
905
|
|
|
636
906
|
def _find_static_path(self):
|
|
637
|
-
"""Find the
|
|
907
|
+
"""Find the Svelte build directory using multiple approaches.
|
|
638
908
|
|
|
639
|
-
WHY: The
|
|
640
|
-
|
|
909
|
+
WHY: The dashboard is now pure Svelte, located at dashboard/static/svelte-build/.
|
|
910
|
+
We search for this specific structure across different deployment contexts.
|
|
641
911
|
"""
|
|
642
912
|
# Get deployment-context-aware paths
|
|
643
913
|
try:
|
|
@@ -658,43 +928,45 @@ class SocketIOServerCore:
|
|
|
658
928
|
package_root = None
|
|
659
929
|
project_root = get_project_root()
|
|
660
930
|
|
|
661
|
-
# Try multiple possible locations for
|
|
931
|
+
# Try multiple possible locations for Svelte build directory
|
|
662
932
|
possible_paths = [
|
|
663
933
|
# Package-based paths (for pipx and pip installations)
|
|
664
|
-
package_root / "dashboard" / "
|
|
665
|
-
|
|
666
|
-
|
|
934
|
+
package_root / "dashboard" / "static" / "svelte-build"
|
|
935
|
+
if package_root
|
|
936
|
+
else None,
|
|
667
937
|
# Project-based paths (for development)
|
|
668
|
-
project_root
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
938
|
+
project_root
|
|
939
|
+
/ "src"
|
|
940
|
+
/ "claude_mpm"
|
|
941
|
+
/ "dashboard"
|
|
942
|
+
/ "static"
|
|
943
|
+
/ "svelte-build",
|
|
944
|
+
project_root / "dashboard" / "static" / "svelte-build",
|
|
674
945
|
# Package installation locations (fallback)
|
|
675
|
-
Path(__file__).parent.parent
|
|
676
|
-
|
|
946
|
+
Path(__file__).parent.parent.parent
|
|
947
|
+
/ "dashboard"
|
|
948
|
+
/ "static"
|
|
949
|
+
/ "svelte-build",
|
|
677
950
|
# Scripts directory (for standalone installations)
|
|
678
|
-
get_scripts_dir() / "static",
|
|
679
|
-
get_scripts_dir() / "socketio" / "static",
|
|
951
|
+
get_scripts_dir() / "dashboard" / "static" / "svelte-build",
|
|
680
952
|
# Current working directory
|
|
681
|
-
Path.cwd() / "static",
|
|
682
|
-
Path.cwd() / "
|
|
953
|
+
Path.cwd() / "src" / "claude_mpm" / "dashboard" / "static" / "svelte-build",
|
|
954
|
+
Path.cwd() / "dashboard" / "static" / "svelte-build",
|
|
683
955
|
]
|
|
684
956
|
|
|
685
957
|
# Filter out None values
|
|
686
958
|
possible_paths = [p for p in possible_paths if p is not None]
|
|
687
959
|
self.logger.debug(
|
|
688
|
-
f"Searching {len(possible_paths)} possible
|
|
960
|
+
f"Searching {len(possible_paths)} possible Svelte build locations"
|
|
689
961
|
)
|
|
690
962
|
|
|
691
963
|
for path in possible_paths:
|
|
692
|
-
self.logger.debug(f"Checking for
|
|
964
|
+
self.logger.debug(f"Checking for Svelte build at: {path}")
|
|
693
965
|
try:
|
|
694
966
|
if path.exists() and path.is_dir():
|
|
695
|
-
# Check if it contains expected files
|
|
967
|
+
# Check if it contains expected Svelte build files
|
|
696
968
|
if (path / "index.html").exists():
|
|
697
|
-
self.logger.info(f"✅ Found
|
|
969
|
+
self.logger.info(f"✅ Found Svelte build at: {path}")
|
|
698
970
|
return path
|
|
699
971
|
self.logger.debug(f"Directory exists but no index.html: {path}")
|
|
700
972
|
else:
|
|
@@ -703,7 +975,7 @@ class SocketIOServerCore:
|
|
|
703
975
|
self.logger.debug(f"Error checking path {path}: {e}")
|
|
704
976
|
|
|
705
977
|
self.logger.warning(
|
|
706
|
-
"⚠️
|
|
978
|
+
"⚠️ Svelte build not found - dashboard will not be available"
|
|
707
979
|
)
|
|
708
980
|
self.logger.debug(f"Searched paths: {[str(p) for p in possible_paths]}")
|
|
709
981
|
return None
|
|
@@ -779,9 +1051,15 @@ class SocketIOServerCore:
|
|
|
779
1051
|
},
|
|
780
1052
|
}
|
|
781
1053
|
|
|
782
|
-
# Add to event history
|
|
1054
|
+
# Add to event history UNCONDITIONALLY
|
|
1055
|
+
# WHY: Heartbeat events should persist for new clients too
|
|
783
1056
|
if self.main_server and hasattr(self.main_server, "event_history"):
|
|
784
1057
|
self.main_server.event_history.append(heartbeat_data)
|
|
1058
|
+
self.logger.debug(
|
|
1059
|
+
f"Heartbeat added to history (total: {len(self.main_server.event_history)})"
|
|
1060
|
+
)
|
|
1061
|
+
else:
|
|
1062
|
+
self.logger.warning("event_history not initialized for heartbeat!")
|
|
785
1063
|
|
|
786
1064
|
# Emit heartbeat to all connected clients (already using new schema)
|
|
787
1065
|
await self.sio.emit("system_event", heartbeat_data)
|