claude-mpm 3.4.10__py3-none-any.whl → 5.4.55__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/BUILD_NUMBER +1 -0
- claude_mpm/VERSION +1 -0
- claude_mpm/__init__.py +50 -12
- claude_mpm/__main__.py +7 -2
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +290 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/MEMORY.md +72 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1402 -0
- claude_mpm/agents/WORKFLOW.md +111 -0
- claude_mpm/agents/__init__.py +92 -80
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +560 -745
- claude_mpm/agents/agent_loader_integration.py +53 -55
- claude_mpm/agents/agents_metadata.py +186 -27
- claude_mpm/agents/async_agent_loader.py +436 -0
- claude_mpm/agents/base_agent.json +8 -4
- claude_mpm/agents/frontmatter_validator.py +754 -0
- claude_mpm/agents/system_agent_config.py +222 -155
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/__init__.py +17 -13
- claude_mpm/agents/templates/circuit-breakers.md +1391 -0
- claude_mpm/agents/templates/context-management-examples.md +544 -0
- claude_mpm/agents/templates/git-file-tracking.md +584 -0
- claude_mpm/agents/templates/pm-examples.md +474 -0
- claude_mpm/agents/templates/pm-red-flags.md +310 -0
- claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
- claude_mpm/agents/templates/research-gate-examples.md +669 -0
- claude_mpm/agents/templates/response-format.md +583 -0
- claude_mpm/agents/templates/structured-questions-examples.md +615 -0
- claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
- claude_mpm/agents/templates/ticketing-examples.md +277 -0
- claude_mpm/agents/templates/validation-templates.md +312 -0
- claude_mpm/cli/__init__.py +90 -128
- claude_mpm/cli/__main__.py +33 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/__init__.py +36 -12
- claude_mpm/cli/commands/agent_manager.py +1403 -0
- claude_mpm/cli/commands/agent_source.py +774 -0
- claude_mpm/cli/commands/agent_state_manager.py +335 -0
- claude_mpm/cli/commands/agents.py +2503 -168
- claude_mpm/cli/commands/agents_cleanup.py +210 -0
- claude_mpm/cli/commands/agents_discover.py +338 -0
- claude_mpm/cli/commands/aggregate.py +540 -0
- claude_mpm/cli/commands/analyze.py +553 -0
- claude_mpm/cli/commands/analyze_code.py +528 -0
- claude_mpm/cli/commands/auto_configure.py +1053 -0
- claude_mpm/cli/commands/cleanup.py +588 -0
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
- claude_mpm/cli/commands/config.py +586 -0
- claude_mpm/cli/commands/configure.py +2654 -0
- claude_mpm/cli/commands/configure_agent_display.py +282 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +184 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/dashboard.py +286 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/doctor.py +243 -0
- claude_mpm/cli/commands/hook_errors.py +277 -0
- claude_mpm/cli/commands/info.py +195 -74
- claude_mpm/cli/commands/local_deploy.py +534 -0
- claude_mpm/cli/commands/mcp.py +205 -0
- claude_mpm/cli/commands/mcp_command_router.py +161 -0
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +346 -0
- claude_mpm/cli/commands/mcp_pipx_config.py +208 -0
- claude_mpm/cli/commands/mcp_server_commands.py +155 -0
- claude_mpm/cli/commands/mcp_setup_external.py +868 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +585 -846
- claude_mpm/cli/commands/monitor.py +228 -310
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +759 -0
- claude_mpm/cli/commands/mpm_init/display.py +341 -0
- claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +722 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +195 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/run.py +910 -488
- claude_mpm/cli/commands/search.py +458 -0
- claude_mpm/cli/commands/skill_source.py +694 -0
- claude_mpm/cli/commands/skills.py +1246 -0
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/commands/tickets.py +536 -53
- claude_mpm/cli/commands/uninstall.py +176 -0
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/commands/verify.py +119 -0
- claude_mpm/cli/executor.py +297 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +21 -0
- claude_mpm/cli/interactive/agent_wizard.py +1947 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parser.py +87 -563
- claude_mpm/cli/parsers/__init__.py +35 -0
- claude_mpm/cli/parsers/agent_manager_parser.py +393 -0
- claude_mpm/cli/parsers/agent_source_parser.py +171 -0
- claude_mpm/cli/parsers/agents_parser.py +575 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +120 -0
- claude_mpm/cli/parsers/base_parser.py +644 -0
- claude_mpm/cli/parsers/config_parser.py +208 -0
- claude_mpm/cli/parsers/configure_parser.py +138 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mcp_parser.py +195 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +142 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/run_parser.py +157 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/parsers/skill_source_parser.py +169 -0
- claude_mpm/cli/parsers/skills_parser.py +277 -0
- claude_mpm/cli/parsers/source_parser.py +138 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/shared/__init__.py +40 -0
- claude_mpm/cli/shared/argument_patterns.py +205 -0
- claude_mpm/cli/shared/base_command.py +242 -0
- claude_mpm/cli/shared/error_handling.py +242 -0
- claude_mpm/cli/shared/output_formatters.py +241 -0
- claude_mpm/cli/startup.py +1743 -0
- claude_mpm/cli/startup_display.py +480 -0
- claude_mpm/cli/startup_logging.py +839 -0
- claude_mpm/cli/utils.py +136 -47
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +42 -64
- claude_mpm/commands/__init__.py +14 -0
- claude_mpm/commands/mpm-config.md +28 -0
- claude_mpm/commands/mpm-doctor.md +20 -0
- claude_mpm/commands/mpm-help.md +20 -0
- claude_mpm/commands/mpm-init.md +120 -0
- claude_mpm/commands/mpm-monitor.md +31 -0
- claude_mpm/commands/mpm-organize.md +120 -0
- claude_mpm/commands/mpm-postmortem.md +21 -0
- claude_mpm/commands/mpm-session-resume.md +30 -0
- claude_mpm/commands/mpm-status.md +20 -0
- claude_mpm/commands/mpm-ticket-view.md +109 -0
- claude_mpm/commands/mpm-version.md +20 -0
- claude_mpm/commands/mpm.md +31 -0
- claude_mpm/config/__init__.py +42 -2
- claude_mpm/config/agent_config.py +402 -0
- claude_mpm/config/agent_presets.py +488 -0
- claude_mpm/config/agent_sources.py +352 -0
- claude_mpm/config/experimental_features.py +217 -0
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/config/paths.py +258 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/config/skill_sources.py +590 -0
- claude_mpm/config/socketio_config.py +125 -83
- claude_mpm/constants.py +132 -22
- claude_mpm/core/__init__.py +62 -36
- claude_mpm/core/agent_name_normalizer.py +71 -73
- claude_mpm/core/agent_registry.py +385 -492
- claude_mpm/core/agent_session_manager.py +81 -70
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/base_service.py +159 -122
- claude_mpm/core/cache.py +560 -0
- claude_mpm/core/claude_runner.py +696 -916
- claude_mpm/core/config.py +613 -122
- claude_mpm/core/config_aliases.py +74 -73
- claude_mpm/core/config_constants.py +314 -0
- claude_mpm/core/constants.py +361 -0
- claude_mpm/core/container.py +646 -104
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/error_handler.py +623 -0
- claude_mpm/core/exceptions.py +536 -0
- claude_mpm/core/factories.py +105 -109
- claude_mpm/core/file_utils.py +764 -0
- claude_mpm/core/framework/__init__.py +25 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +278 -0
- claude_mpm/core/framework/formatters/context_generator.py +185 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +213 -0
- claude_mpm/core/framework/loaders/file_loader.py +176 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +222 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +485 -414
- claude_mpm/core/hook_error_memory.py +381 -0
- claude_mpm/core/hook_manager.py +246 -86
- claude_mpm/core/hook_performance_config.py +147 -0
- claude_mpm/core/injectable_service.py +72 -63
- claude_mpm/core/instruction_reinforcement_hook.py +267 -0
- claude_mpm/core/interactive_session.py +670 -0
- claude_mpm/core/interfaces.py +570 -164
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/log_manager.py +707 -0
- claude_mpm/core/logger.py +295 -134
- claude_mpm/core/logging_config.py +474 -0
- claude_mpm/core/logging_utils.py +520 -0
- claude_mpm/core/minimal_framework_loader.py +24 -22
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +594 -0
- claude_mpm/core/optimized_agent_loader.py +479 -0
- claude_mpm/core/optimized_startup.py +554 -0
- claude_mpm/core/output_style_manager.py +483 -0
- claude_mpm/core/pm_hook_interceptor.py +197 -82
- 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/service_registry.py +153 -116
- claude_mpm/core/session_manager.py +179 -64
- claude_mpm/core/shared/__init__.py +17 -0
- claude_mpm/core/shared/config_loader.py +326 -0
- claude_mpm/core/shared/path_resolver.py +281 -0
- claude_mpm/core/shared/singleton_manager.py +221 -0
- claude_mpm/core/socketio_pool.py +400 -137
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/tool_access_control.py +64 -57
- claude_mpm/core/types.py +307 -0
- claude_mpm/core/typing_utils.py +553 -0
- claude_mpm/core/unified_agent_registry.py +969 -0
- claude_mpm/core/unified_config.py +570 -0
- claude_mpm/core/unified_paths.py +941 -0
- claude_mpm/dashboard/__init__.py +12 -0
- claude_mpm/dashboard/api/simple_directory.py +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -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/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- claude_mpm/experimental/__init__.py +10 -0
- claude_mpm/experimental/cli_enhancements.py +104 -89
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +76 -66
- claude_mpm/hooks/__init__.py +37 -1
- claude_mpm/hooks/base_hook.py +37 -32
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- 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/connection_pool.py +250 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +888 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +652 -875
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -7
- claude_mpm/hooks/claude_hooks/installer.py +806 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +249 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +412 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +15 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.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 +229 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +254 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +284 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +224 -0
- claude_mpm/hooks/failure_learning/__init__.py +54 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +230 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +212 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +281 -0
- claude_mpm/hooks/instruction_reinforcement.py +301 -0
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +386 -0
- claude_mpm/hooks/kuzu_response_hook.py +179 -0
- claude_mpm/hooks/memory_integration_hook.py +201 -107
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/hooks/templates/pre_tool_use_simple.py +78 -0
- claude_mpm/hooks/templates/pre_tool_use_template.py +323 -0
- claude_mpm/hooks/tool_call_interceptor.py +92 -76
- claude_mpm/hooks/validation_hooks.py +62 -54
- claude_mpm/init.py +518 -83
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +40 -23
- claude_mpm/models/agent_session.py +538 -0
- claude_mpm/models/git_repository.py +198 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/schemas/__init__.py +12 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/claude-hook-handler.sh +227 -0
- claude_mpm/scripts/launch_monitor.py +165 -0
- claude_mpm/scripts/mpm_doctor.py +322 -0
- claude_mpm/scripts/socketio_daemon.py +189 -200
- claude_mpm/scripts/start_activity_logging.py +91 -0
- claude_mpm/services/__init__.py +208 -39
- claude_mpm/services/agent_capabilities_service.py +266 -0
- claude_mpm/services/agents/__init__.py +89 -0
- claude_mpm/services/agents/agent_builder.py +514 -0
- claude_mpm/services/agents/agent_preset_service.py +238 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/agent_selection_service.py +484 -0
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/agents/deployment/agent_config_provider.py +410 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +358 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +80 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +1037 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +546 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +288 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +383 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +505 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +160 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +957 -0
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +273 -0
- claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
- claude_mpm/services/agents/deployment/agent_record_service.py +418 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +84 -0
- claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +1369 -0
- claude_mpm/services/agents/deployment/agent_validator.py +376 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +322 -0
- claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +10 -13
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +149 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +768 -0
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +181 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +178 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +120 -0
- claude_mpm/services/agents/deployment/deployment_wrapper.py +129 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +70 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +269 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +226 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +362 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +1478 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +162 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +240 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +110 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +80 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +92 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +101 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +102 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +269 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +311 -0
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +862 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +113 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +148 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +131 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +130 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +228 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +21 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +319 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +214 -0
- claude_mpm/services/agents/git_source_manager.py +682 -0
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +306 -228
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +106 -91
- claude_mpm/services/agents/loading/framework_agent_loader.py +433 -0
- claude_mpm/services/agents/local_template_manager.py +784 -0
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +92 -69
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +219 -168
- claude_mpm/services/agents/memory/__init__.py +22 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +784 -0
- claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +20 -18
- claude_mpm/services/agents/memory/content_manager.py +470 -0
- claude_mpm/services/agents/memory/memory_categorization_service.py +167 -0
- claude_mpm/services/agents/memory/memory_file_service.py +129 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +101 -0
- claude_mpm/services/agents/memory/template_generator.py +83 -0
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +617 -0
- claude_mpm/services/agents/registry/__init__.py +30 -0
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +273 -0
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +370 -295
- claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
- claude_mpm/services/agents/sources/__init__.py +13 -0
- claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +1202 -0
- claude_mpm/services/agents/startup_sync.py +259 -0
- claude_mpm/services/agents/toolchain_detector.py +478 -0
- 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/async_session_logger.py +665 -0
- claude_mpm/services/claude_session_logger.py +321 -0
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +408 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +590 -0
- claude_mpm/services/cli/memory_crud_service.py +622 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +604 -0
- claude_mpm/services/cli/session_pause_manager.py +504 -0
- claude_mpm/services/cli/session_resume_helper.py +372 -0
- claude_mpm/services/cli/startup_checker.py +362 -0
- claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
- claude_mpm/services/command_deployment_service.py +446 -0
- claude_mpm/services/command_handler_service.py +221 -0
- claude_mpm/services/communication/__init__.py +22 -0
- claude_mpm/services/core/__init__.py +108 -0
- claude_mpm/services/core/base.py +269 -0
- claude_mpm/services/core/cache_manager.py +309 -0
- claude_mpm/services/core/interfaces/__init__.py +273 -0
- claude_mpm/services/core/interfaces/agent.py +514 -0
- claude_mpm/services/core/interfaces/communication.py +316 -0
- claude_mpm/services/core/interfaces/health.py +169 -0
- claude_mpm/services/core/interfaces/infrastructure.py +357 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/service.py +405 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/interfaces.py +81 -0
- claude_mpm/services/core/memory_manager.py +682 -0
- claude_mpm/services/core/models/__init__.py +70 -0
- claude_mpm/services/core/models/agent_config.py +384 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +239 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +517 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +38 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +370 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
- claude_mpm/services/diagnostics/checks/base_check.py +60 -0
- claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +363 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +306 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +520 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +415 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +330 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +1058 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +281 -0
- claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +319 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +286 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +578 -0
- claude_mpm/services/diagnostics/models.py +138 -0
- claude_mpm/services/event_aggregator.py +582 -0
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/config.py +186 -0
- claude_mpm/services/event_bus/direct_relay.py +312 -0
- claude_mpm/services/event_bus/event_bus.py +396 -0
- claude_mpm/services/event_bus/relay.py +326 -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 +306 -0
- claude_mpm/services/events/consumers/logging.py +184 -0
- claude_mpm/services/events/consumers/metrics.py +241 -0
- claude_mpm/services/events/consumers/socketio.py +377 -0
- claude_mpm/services/events/core.py +480 -0
- claude_mpm/services/events/interfaces.py +214 -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 +329 -0
- claude_mpm/services/exceptions.py +433 -353
- claude_mpm/services/framework_claude_md_generator/__init__.py +81 -80
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +74 -67
- claude_mpm/services/framework_claude_md_generator/content_validator.py +66 -62
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +82 -60
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +36 -37
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +41 -40
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +15 -15
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +26 -30
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +31 -30
- claude_mpm/services/git/__init__.py +21 -0
- claude_mpm/services/git/git_operations_service.py +579 -0
- claude_mpm/services/github/__init__.py +21 -0
- claude_mpm/services/github/github_cli_service.py +397 -0
- claude_mpm/services/hook_installer_service.py +506 -0
- claude_mpm/services/hook_service.py +159 -111
- claude_mpm/services/infrastructure/__init__.py +52 -0
- claude_mpm/services/infrastructure/context_preservation.py +569 -0
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +209 -0
- claude_mpm/services/infrastructure/monitoring/__init__.py +39 -0
- claude_mpm/services/infrastructure/monitoring/aggregator.py +432 -0
- claude_mpm/services/infrastructure/monitoring/base.py +122 -0
- claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
- claude_mpm/services/infrastructure/monitoring/network.py +219 -0
- claude_mpm/services/infrastructure/monitoring/process.py +343 -0
- claude_mpm/services/infrastructure/monitoring/resources.py +244 -0
- claude_mpm/services/infrastructure/monitoring/service.py +368 -0
- claude_mpm/services/infrastructure/monitoring.py +71 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/instructions/__init__.py +9 -0
- claude_mpm/services/instructions/instruction_cache_service.py +374 -0
- claude_mpm/services/local_ops/__init__.py +155 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +26 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +427 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +1542 -0
- claude_mpm/services/mcp_service_verifier.py +732 -0
- claude_mpm/services/memory/__init__.py +19 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +465 -373
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +237 -200
- claude_mpm/services/memory/cache/simple_cache.py +331 -0
- claude_mpm/services/memory/failure_tracker.py +578 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +272 -243
- claude_mpm/services/memory/router.py +951 -0
- claude_mpm/services/memory_hook_service.py +470 -0
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +452 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +698 -0
- claude_mpm/services/monitor/daemon_manager.py +1076 -0
- claude_mpm/services/monitor/event_emitter.py +350 -0
- claude_mpm/services/monitor/handlers/__init__.py +21 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +332 -0
- claude_mpm/services/monitor/handlers/dashboard.py +299 -0
- claude_mpm/services/monitor/handlers/file.py +264 -0
- claude_mpm/services/monitor/handlers/hooks.py +512 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +730 -0
- claude_mpm/services/monitor/server.py +1493 -0
- claude_mpm/services/monitor_build_service.py +349 -0
- claude_mpm/services/native_agent_converter.py +356 -0
- claude_mpm/services/orphan_detection.py +786 -0
- claude_mpm/services/pm_skills_deployer.py +707 -0
- claude_mpm/services/port_manager.py +597 -0
- claude_mpm/services/pr/__init__.py +14 -0
- claude_mpm/services/pr/pr_template_service.py +329 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/project/__init__.py +44 -0
- claude_mpm/services/{project_analyzer.py → project/analyzer.py} +541 -291
- claude_mpm/services/project/analyzer_v2.py +566 -0
- claude_mpm/services/project/architecture_analyzer.py +461 -0
- claude_mpm/services/project/archive_manager.py +1045 -0
- claude_mpm/services/project/dependency_analyzer.py +462 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/documentation_manager.py +554 -0
- claude_mpm/services/project/enhanced_analyzer.py +572 -0
- claude_mpm/services/project/language_analyzer.py +265 -0
- claude_mpm/services/project/metrics_collector.py +407 -0
- claude_mpm/services/project/project_organizer.py +1009 -0
- claude_mpm/services/project/registry.py +636 -0
- claude_mpm/services/project/toolchain_analyzer.py +583 -0
- claude_mpm/services/project_port_allocator.py +596 -0
- claude_mpm/services/recovery_manager.py +293 -240
- claude_mpm/services/response_tracker.py +267 -0
- claude_mpm/services/runner_configuration_service.py +605 -0
- claude_mpm/services/self_upgrade_service.py +608 -0
- claude_mpm/services/session_management_service.py +314 -0
- claude_mpm/services/session_manager.py +380 -0
- claude_mpm/services/shared/__init__.py +21 -0
- claude_mpm/services/shared/async_service_base.py +216 -0
- claude_mpm/services/shared/config_service_base.py +301 -0
- claude_mpm/services/shared/lifecycle_service_base.py +308 -0
- claude_mpm/services/shared/manager_base.py +315 -0
- claude_mpm/services/shared/service_factory.py +309 -0
- claude_mpm/services/skills/__init__.py +21 -0
- claude_mpm/services/skills/git_skill_source_manager.py +1324 -0
- claude_mpm/services/skills/selective_skill_deployer.py +744 -0
- claude_mpm/services/skills/skill_discovery_service.py +568 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_config.py +547 -0
- claude_mpm/services/skills_deployer.py +1168 -0
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/client_proxy.py +229 -0
- claude_mpm/services/socketio/dashboard_server.py +362 -0
- claude_mpm/services/socketio/event_normalizer.py +798 -0
- claude_mpm/services/socketio/handlers/__init__.py +30 -0
- claude_mpm/services/socketio/handlers/base.py +136 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
- claude_mpm/services/socketio/handlers/connection.py +643 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +333 -0
- claude_mpm/services/socketio/handlers/file.py +263 -0
- claude_mpm/services/socketio/handlers/git.py +962 -0
- claude_mpm/services/socketio/handlers/hook.py +211 -0
- claude_mpm/services/socketio/handlers/memory.py +26 -0
- claude_mpm/services/socketio/handlers/project.py +24 -0
- claude_mpm/services/socketio/handlers/registry.py +214 -0
- claude_mpm/services/socketio/migration_utils.py +343 -0
- claude_mpm/services/socketio/monitor_client.py +364 -0
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +569 -0
- claude_mpm/services/socketio/server/connection_manager.py +579 -0
- claude_mpm/services/socketio/server/core.py +1079 -0
- claude_mpm/services/socketio/server/eventbus_integration.py +245 -0
- claude_mpm/services/socketio/server/main.py +501 -0
- claude_mpm/services/socketio_client_manager.py +173 -143
- claude_mpm/services/socketio_server.py +38 -1657
- claude_mpm/services/subprocess_launcher_service.py +322 -0
- claude_mpm/services/system_instructions_service.py +270 -0
- claude_mpm/services/ticket_manager.py +25 -209
- claude_mpm/services/ticket_services/__init__.py +26 -0
- claude_mpm/services/ticket_services/crud_service.py +328 -0
- claude_mpm/services/ticket_services/formatter_service.py +290 -0
- claude_mpm/services/ticket_services/search_service.py +324 -0
- claude_mpm/services/ticket_services/validation_service.py +303 -0
- claude_mpm/services/ticket_services/workflow_service.py +244 -0
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +518 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +680 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +900 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +745 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +733 -0
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +731 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +747 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1005 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +881 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +823 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1148 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +553 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +573 -0
- claude_mpm/services/unified/deployment_strategies/local.py +607 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +667 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +475 -0
- claude_mpm/services/unified/migration.py +509 -0
- claude_mpm/services/unified/strategies.py +534 -0
- claude_mpm/services/unified/unified_analyzer.py +542 -0
- claude_mpm/services/unified/unified_config.py +691 -0
- claude_mpm/services/unified/unified_deployment.py +466 -0
- claude_mpm/services/utility_service.py +280 -0
- claude_mpm/services/version_control/__init__.py +34 -37
- claude_mpm/services/version_control/branch_strategy.py +26 -17
- claude_mpm/services/version_control/conflict_resolution.py +52 -36
- claude_mpm/services/version_control/git_operations.py +183 -49
- claude_mpm/services/version_control/semantic_versioning.py +172 -61
- claude_mpm/services/version_control/version_parser.py +546 -0
- claude_mpm/services/version_service.py +379 -0
- claude_mpm/services/visualization/__init__.py +15 -0
- claude_mpm/services/visualization/mermaid_generator.py +937 -0
- claude_mpm/skills/__init__.py +42 -0
- claude_mpm/skills/agent_skills_injector.py +324 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/scripts/validate_env.py +576 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +573 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +439 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +405 -0
- claude_mpm/skills/skills_registry.py +347 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +546 -0
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/templates/questions/__init__.py +38 -0
- claude_mpm/templates/questions/base.py +193 -0
- claude_mpm/templates/questions/pr_strategy.py +311 -0
- claude_mpm/templates/questions/project_init.py +385 -0
- claude_mpm/templates/questions/ticket_mgmt.py +394 -0
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/tools/__init__.py +10 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
- claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
- claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
- claude_mpm/tools/code_tree_analyzer/core.py +380 -0
- claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
- claude_mpm/tools/code_tree_analyzer/events.py +168 -0
- claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
- claude_mpm/tools/code_tree_analyzer/models.py +39 -0
- claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
- claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +420 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- claude_mpm/utils/__init__.py +8 -8
- claude_mpm/utils/agent_dependency_loader.py +1090 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/common.py +544 -0
- claude_mpm/utils/config_manager.py +168 -126
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/dependency_cache.py +373 -0
- claude_mpm/utils/dependency_manager.py +60 -59
- claude_mpm/utils/dependency_strategies.py +381 -0
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/environment_context.py +313 -0
- claude_mpm/utils/error_handler.py +78 -66
- claude_mpm/utils/file_utils.py +305 -0
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/gitignore.py +244 -0
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/log_cleanup.py +627 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/path_operations.py +110 -104
- claude_mpm/utils/progress.py +387 -0
- claude_mpm/utils/robust_installer.py +823 -0
- claude_mpm/utils/session_logging.py +121 -0
- claude_mpm/utils/structured_questions.py +619 -0
- claude_mpm/utils/subprocess_utils.py +343 -0
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +214 -108
- claude_mpm/validation/frontmatter_validator.py +252 -0
- claude_mpm-5.4.55.dist-info/METADATA +999 -0
- claude_mpm-5.4.55.dist-info/RECORD +868 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/entry_points.txt +1 -3
- claude_mpm-5.4.55.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.55.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -88
- claude_mpm/agents/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/base_agent_loader.py +0 -529
- claude_mpm/agents/schema/agent_schema.json +0 -314
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -45
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -49
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -46
- claude_mpm/agents/templates/data_engineer.json +0 -110
- claude_mpm/agents/templates/documentation.json +0 -109
- claude_mpm/agents/templates/engineer.json +0 -113
- claude_mpm/agents/templates/ops.json +0 -109
- claude_mpm/agents/templates/pm.json +0 -25
- claude_mpm/agents/templates/qa.json +0 -111
- claude_mpm/agents/templates/research.json +0 -65
- claude_mpm/agents/templates/security.json +0 -113
- claude_mpm/agents/templates/test_integration.json +0 -112
- claude_mpm/agents/templates/version_control.json +0 -107
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/dashboard/open_dashboard.py +0 -34
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/agent_deployment.py +0 -1534
- claude_mpm/services/agent_lifecycle_manager.py +0 -1169
- claude_mpm/services/agent_memory_manager.py +0 -1415
- claude_mpm/services/agent_registry.py +0 -676
- claude_mpm/services/deployed_agent_discovery.py +0 -226
- claude_mpm/services/framework_agent_loader.py +0 -337
- claude_mpm/services/framework_claude_md_generator.py +0 -621
- claude_mpm/services/health_monitor.py +0 -892
- claude_mpm/services/memory_router.py +0 -538
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -508
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- claude_mpm/utils/paths.py +0 -289
- claude_mpm-3.4.10.dist-info/METADATA +0 -183
- claude_mpm-3.4.10.dist-info/RECORD +0 -201
- claude_mpm-3.4.10.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.55.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SocketIO Server Core for claude-mpm.
|
|
3
|
+
|
|
4
|
+
WHY: This module contains the core server management functionality extracted from
|
|
5
|
+
the monolithic socketio_server.py file. It handles server lifecycle, static file
|
|
6
|
+
serving, and basic server setup.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISION: Separated core server logic from event handling and broadcasting
|
|
9
|
+
to create focused, maintainable modules.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from collections import deque
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, Set
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import aiohttp
|
|
22
|
+
import socketio
|
|
23
|
+
from aiohttp import web
|
|
24
|
+
|
|
25
|
+
SOCKETIO_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
SOCKETIO_AVAILABLE = False
|
|
28
|
+
socketio = None
|
|
29
|
+
aiohttp = None
|
|
30
|
+
web = None
|
|
31
|
+
|
|
32
|
+
# Import VersionService for dynamic version retrieval
|
|
33
|
+
import contextlib
|
|
34
|
+
|
|
35
|
+
from claude_mpm.services.version_service import VersionService
|
|
36
|
+
|
|
37
|
+
from ....core.constants import SystemLimits, TimeoutConfig
|
|
38
|
+
from ....core.logging_config import get_logger
|
|
39
|
+
from ....core.unified_paths import get_project_root, get_scripts_dir
|
|
40
|
+
from ...exceptions import SocketIOServerError as MPMConnectionError
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SocketIOServerCore:
|
|
44
|
+
"""Core server management functionality for SocketIO server.
|
|
45
|
+
|
|
46
|
+
WHY: This class handles the basic server lifecycle, static file serving,
|
|
47
|
+
and core server setup. It's separated from event handling to reduce complexity.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, host: str = "localhost", port: int = 8765):
|
|
51
|
+
self.host = host
|
|
52
|
+
self.port = port
|
|
53
|
+
self.logger = get_logger(__name__ + ".SocketIOServer")
|
|
54
|
+
self.running = False
|
|
55
|
+
self.server_thread = None
|
|
56
|
+
self.loop = None
|
|
57
|
+
self.app = None
|
|
58
|
+
self.runner = None
|
|
59
|
+
self.site = None
|
|
60
|
+
|
|
61
|
+
# Socket.IO server instance
|
|
62
|
+
self.sio = None
|
|
63
|
+
|
|
64
|
+
# Connection tracking
|
|
65
|
+
self.connected_clients: Set[str] = set()
|
|
66
|
+
self.client_info: Dict[str, Dict[str, Any]] = {}
|
|
67
|
+
|
|
68
|
+
# Event buffering for reliability
|
|
69
|
+
self.event_buffer = deque(
|
|
70
|
+
maxlen=getattr(SystemLimits, "MAX_EVENTS_BUFFER", 1000)
|
|
71
|
+
)
|
|
72
|
+
self.buffer_lock = threading.Lock()
|
|
73
|
+
|
|
74
|
+
# Performance tracking
|
|
75
|
+
self.stats = {
|
|
76
|
+
"events_sent": 0,
|
|
77
|
+
"events_buffered": 0,
|
|
78
|
+
"connections_total": 0,
|
|
79
|
+
"start_time": None,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Static files path
|
|
83
|
+
self.static_path = None
|
|
84
|
+
|
|
85
|
+
# Heartbeat task
|
|
86
|
+
self.heartbeat_task = None
|
|
87
|
+
self.heartbeat_interval = 60 # seconds
|
|
88
|
+
self.main_server = None # Reference to main server for session data
|
|
89
|
+
|
|
90
|
+
def start_sync(self):
|
|
91
|
+
"""Start the Socket.IO server in a background thread (synchronous version)."""
|
|
92
|
+
if not SOCKETIO_AVAILABLE:
|
|
93
|
+
self.logger.warning("Socket.IO not available - server not started")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if self.running:
|
|
97
|
+
self.logger.warning("Socket.IO server already running")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
self.logger.info(f"Starting Socket.IO server on {self.host}:{self.port}")
|
|
101
|
+
|
|
102
|
+
# Start server in background thread
|
|
103
|
+
self.server_thread = threading.Thread(target=self._run_server, daemon=True)
|
|
104
|
+
self.server_thread.start()
|
|
105
|
+
|
|
106
|
+
# Wait for server to start
|
|
107
|
+
max_wait = getattr(TimeoutConfig, "SERVER_START_TIMEOUT", 30)
|
|
108
|
+
wait_time = 0
|
|
109
|
+
while not self.running and wait_time < max_wait:
|
|
110
|
+
time.sleep(0.1)
|
|
111
|
+
wait_time += 0.1
|
|
112
|
+
|
|
113
|
+
if not self.running:
|
|
114
|
+
raise MPMConnectionError(
|
|
115
|
+
f"Failed to start Socket.IO server within {max_wait}s"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.logger.info(
|
|
119
|
+
f"Socket.IO server started successfully on {self.host}:{self.port}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def stop_sync(self):
|
|
123
|
+
"""Stop the Socket.IO server (synchronous version)."""
|
|
124
|
+
if not self.running:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
self.logger.info("Stopping Socket.IO server...")
|
|
128
|
+
self.running = False
|
|
129
|
+
|
|
130
|
+
# Stop the server gracefully
|
|
131
|
+
if self.loop and not self.loop.is_closed():
|
|
132
|
+
asyncio.run_coroutine_threadsafe(self._stop_server(), self.loop)
|
|
133
|
+
|
|
134
|
+
def _run_server(self):
|
|
135
|
+
"""Run the server event loop."""
|
|
136
|
+
try:
|
|
137
|
+
# Create new event loop for this thread
|
|
138
|
+
# WHY: We create and assign the loop immediately to minimize the race
|
|
139
|
+
# condition window where other threads might try to access it.
|
|
140
|
+
self.loop = asyncio.new_event_loop()
|
|
141
|
+
asyncio.set_event_loop(self.loop)
|
|
142
|
+
|
|
143
|
+
self.logger.debug("Event loop created and set for background thread")
|
|
144
|
+
|
|
145
|
+
# Run the server
|
|
146
|
+
self.loop.run_until_complete(self._start_server())
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self.logger.error(f"Socket.IO server error: {e}")
|
|
150
|
+
self.running = False
|
|
151
|
+
finally:
|
|
152
|
+
if self.loop and not self.loop.is_closed():
|
|
153
|
+
self.loop.close()
|
|
154
|
+
|
|
155
|
+
async def _start_server(self):
|
|
156
|
+
"""Start the Socket.IO server with aiohttp."""
|
|
157
|
+
try:
|
|
158
|
+
# Import centralized configuration for consistency
|
|
159
|
+
from ....config.socketio_config import CONNECTION_CONFIG
|
|
160
|
+
|
|
161
|
+
# Create Socket.IO server with centralized configuration
|
|
162
|
+
# CRITICAL: These values MUST match client settings to prevent disconnections
|
|
163
|
+
self.sio = socketio.AsyncServer(
|
|
164
|
+
cors_allowed_origins="*",
|
|
165
|
+
logger=False, # Disable Socket.IO's own logging
|
|
166
|
+
engineio_logger=False,
|
|
167
|
+
ping_interval=CONNECTION_CONFIG[
|
|
168
|
+
"ping_interval"
|
|
169
|
+
], # 45 seconds from config
|
|
170
|
+
ping_timeout=CONNECTION_CONFIG[
|
|
171
|
+
"ping_timeout"
|
|
172
|
+
], # 20 seconds from config
|
|
173
|
+
max_http_buffer_size=CONNECTION_CONFIG[
|
|
174
|
+
"max_http_buffer_size"
|
|
175
|
+
], # 100MB from config
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Create aiohttp application
|
|
179
|
+
self.app = web.Application()
|
|
180
|
+
self.sio.attach(self.app)
|
|
181
|
+
|
|
182
|
+
# CRITICAL: Register event handlers BEFORE starting the server
|
|
183
|
+
# This ensures handlers are ready when clients connect
|
|
184
|
+
if self.main_server and hasattr(self.main_server, "_register_events_async"):
|
|
185
|
+
self.logger.info(
|
|
186
|
+
"Registering Socket.IO event handlers before server start"
|
|
187
|
+
)
|
|
188
|
+
await self.main_server._register_events_async()
|
|
189
|
+
else:
|
|
190
|
+
self.logger.warning("Main server not available for event registration")
|
|
191
|
+
|
|
192
|
+
# Setup HTTP API endpoints for receiving events from hook handlers
|
|
193
|
+
self._setup_http_api()
|
|
194
|
+
|
|
195
|
+
# Setup simple directory API
|
|
196
|
+
self._setup_directory_api()
|
|
197
|
+
|
|
198
|
+
# Find and serve static files
|
|
199
|
+
self._setup_static_files()
|
|
200
|
+
|
|
201
|
+
# Create and start the server
|
|
202
|
+
self.runner = web.AppRunner(self.app)
|
|
203
|
+
await self.runner.setup()
|
|
204
|
+
|
|
205
|
+
self.site = web.TCPSite(
|
|
206
|
+
self.runner, self.host, self.port, reuse_address=True, reuse_port=True
|
|
207
|
+
)
|
|
208
|
+
await self.site.start()
|
|
209
|
+
|
|
210
|
+
self.running = True
|
|
211
|
+
self.stats["start_time"] = datetime.now(timezone.utc)
|
|
212
|
+
|
|
213
|
+
self.logger.info(
|
|
214
|
+
f"Socket.IO server listening on http://{self.host}:{self.port}"
|
|
215
|
+
)
|
|
216
|
+
if self.static_path:
|
|
217
|
+
self.logger.info(f"Serving static files from: {self.static_path}")
|
|
218
|
+
|
|
219
|
+
# Conditionally start heartbeat task based on configuration
|
|
220
|
+
from ....config.socketio_config import CONNECTION_CONFIG
|
|
221
|
+
|
|
222
|
+
if CONNECTION_CONFIG.get("enable_extra_heartbeat", False):
|
|
223
|
+
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
224
|
+
self.logger.info("Started system heartbeat task")
|
|
225
|
+
else:
|
|
226
|
+
self.logger.info(
|
|
227
|
+
"System heartbeat disabled (using Socket.IO ping/pong instead)"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Keep the server running
|
|
231
|
+
while self.running:
|
|
232
|
+
await asyncio.sleep(1)
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.logger.error(f"Failed to start Socket.IO server: {e}")
|
|
236
|
+
self.running = False
|
|
237
|
+
raise
|
|
238
|
+
|
|
239
|
+
async def _stop_server(self):
|
|
240
|
+
"""Stop the server gracefully."""
|
|
241
|
+
try:
|
|
242
|
+
# Cancel heartbeat task
|
|
243
|
+
if self.heartbeat_task and not self.heartbeat_task.done():
|
|
244
|
+
self.heartbeat_task.cancel()
|
|
245
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
246
|
+
await self.heartbeat_task
|
|
247
|
+
self.logger.info("Stopped system heartbeat task")
|
|
248
|
+
|
|
249
|
+
if self.site:
|
|
250
|
+
await self.site.stop()
|
|
251
|
+
self.site = None
|
|
252
|
+
|
|
253
|
+
if self.runner:
|
|
254
|
+
await self.runner.cleanup()
|
|
255
|
+
self.runner = None
|
|
256
|
+
|
|
257
|
+
self.logger.info("Socket.IO server stopped")
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
self.logger.error(f"Error stopping Socket.IO server: {e}")
|
|
261
|
+
|
|
262
|
+
def _setup_http_api(self):
|
|
263
|
+
"""Setup HTTP API endpoints for receiving events from hook handlers.
|
|
264
|
+
|
|
265
|
+
WHY: Hook handlers are ephemeral processes that spawn and die quickly.
|
|
266
|
+
Using HTTP POST allows them to send events without managing persistent
|
|
267
|
+
connections, eliminating disconnection issues.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
async def api_events_handler(request):
|
|
271
|
+
"""Handle POST /api/events from hook handlers."""
|
|
272
|
+
try:
|
|
273
|
+
# Parse JSON payload
|
|
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
|
|
290
|
+
|
|
291
|
+
# Log receipt with more detail
|
|
292
|
+
event_type = (
|
|
293
|
+
event_data.get("subtype")
|
|
294
|
+
or event_data.get("hook_event_name")
|
|
295
|
+
or "unknown"
|
|
296
|
+
)
|
|
297
|
+
self.logger.info(f"📨 Received HTTP event: {event_type}")
|
|
298
|
+
self.logger.debug(f"Event data keys: {list(event_data.keys())}")
|
|
299
|
+
self.logger.debug(f"Connected clients: {len(self.connected_clients)}")
|
|
300
|
+
|
|
301
|
+
# Transform hook event format to claude_event format if needed
|
|
302
|
+
if "hook_event_name" in event_data and "event" not in event_data:
|
|
303
|
+
# This is a raw hook event, transform it
|
|
304
|
+
from claude_mpm.services.socketio.event_normalizer import (
|
|
305
|
+
EventNormalizer,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
normalizer = EventNormalizer()
|
|
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
|
+
|
|
370
|
+
# Create the format expected by normalizer
|
|
371
|
+
raw_event = {
|
|
372
|
+
"type": "hook",
|
|
373
|
+
"subtype": subtype,
|
|
374
|
+
"timestamp": event_data.get("timestamp"),
|
|
375
|
+
"data": event_data.get("hook_input_data", {}),
|
|
376
|
+
"source": "claude_hooks",
|
|
377
|
+
"session_id": event_data.get("session_id"),
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
normalized = normalizer.normalize(raw_event, source="hook")
|
|
381
|
+
event_data = normalized.to_dict()
|
|
382
|
+
self.logger.debug(
|
|
383
|
+
f"Normalized event: type={event_data.get('type')}, subtype={event_data.get('subtype')}"
|
|
384
|
+
)
|
|
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
|
+
|
|
400
|
+
# Broadcast to all connected dashboard clients via SocketIO
|
|
401
|
+
if self.sio:
|
|
402
|
+
# CRITICAL: Use the main server's broadcaster for proper event handling
|
|
403
|
+
# The broadcaster handles retries, connection management, and buffering
|
|
404
|
+
if (
|
|
405
|
+
self.main_server
|
|
406
|
+
and hasattr(self.main_server, "broadcaster")
|
|
407
|
+
and self.main_server.broadcaster
|
|
408
|
+
):
|
|
409
|
+
# The broadcaster expects raw event data and will normalize it
|
|
410
|
+
# Since we already normalized it, we need to pass it in a way that won't double-normalize
|
|
411
|
+
# We'll emit directly through the broadcaster's sio with proper handling
|
|
412
|
+
|
|
413
|
+
# Add to event buffer and history
|
|
414
|
+
with self.buffer_lock:
|
|
415
|
+
self.event_buffer.append(event_data)
|
|
416
|
+
self.stats["events_buffered"] = len(self.event_buffer)
|
|
417
|
+
|
|
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
|
+
):
|
|
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
|
+
)
|
|
435
|
+
|
|
436
|
+
# Use the broadcaster's sio to emit (it's the same as self.sio)
|
|
437
|
+
# This ensures the event goes through the proper channels
|
|
438
|
+
await self.sio.emit("claude_event", event_data)
|
|
439
|
+
|
|
440
|
+
# Update broadcaster stats
|
|
441
|
+
if hasattr(self.main_server.broadcaster, "stats"):
|
|
442
|
+
self.main_server.broadcaster.stats["events_sent"] = (
|
|
443
|
+
self.main_server.broadcaster.stats.get("events_sent", 0)
|
|
444
|
+
+ 1
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
self.logger.info(
|
|
448
|
+
f"✅ Event broadcasted: {event_data.get('subtype', 'unknown')} to {len(self.connected_clients)} clients"
|
|
449
|
+
)
|
|
450
|
+
self.logger.debug(
|
|
451
|
+
f"Connected client IDs: {list(self.connected_clients) if self.connected_clients else 'None'}"
|
|
452
|
+
)
|
|
453
|
+
else:
|
|
454
|
+
# Fallback: Direct emit if broadcaster not available (shouldn't happen)
|
|
455
|
+
self.logger.warning(
|
|
456
|
+
"Broadcaster not available, using direct emit"
|
|
457
|
+
)
|
|
458
|
+
await self.sio.emit("claude_event", event_data)
|
|
459
|
+
|
|
460
|
+
# Update stats manually if using fallback
|
|
461
|
+
self.stats["events_sent"] = self.stats.get("events_sent", 0) + 1
|
|
462
|
+
|
|
463
|
+
# Add to event buffer for late-joining clients
|
|
464
|
+
with self.buffer_lock:
|
|
465
|
+
self.event_buffer.append(event_data)
|
|
466
|
+
self.stats["events_buffered"] = len(self.event_buffer)
|
|
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
|
+
|
|
483
|
+
# Return 204 No Content for success
|
|
484
|
+
self.logger.debug(f"✅ HTTP event processed successfully: {event_type}")
|
|
485
|
+
return web.Response(status=204)
|
|
486
|
+
|
|
487
|
+
except Exception as e:
|
|
488
|
+
self.logger.error(f"Error handling HTTP event: {e}")
|
|
489
|
+
return web.Response(status=500, text=str(e))
|
|
490
|
+
|
|
491
|
+
# Register the HTTP POST endpoint
|
|
492
|
+
self.app.router.add_post("/api/events", api_events_handler)
|
|
493
|
+
self.logger.info("✅ HTTP API endpoint registered at /api/events")
|
|
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
|
+
|
|
536
|
+
# Add working directory endpoint
|
|
537
|
+
async def working_directory_handler(request):
|
|
538
|
+
"""Handle GET /api/working-directory to provide current working directory."""
|
|
539
|
+
from pathlib import Path
|
|
540
|
+
|
|
541
|
+
try:
|
|
542
|
+
working_dir = str(Path.cwd())
|
|
543
|
+
home_dir = str(Path.home())
|
|
544
|
+
|
|
545
|
+
return web.json_response(
|
|
546
|
+
{
|
|
547
|
+
"working_directory": working_dir,
|
|
548
|
+
"home_directory": home_dir,
|
|
549
|
+
"process_cwd": working_dir,
|
|
550
|
+
"session_id": getattr(self, "session_id", None),
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
except Exception as e:
|
|
554
|
+
self.logger.error(f"Error getting working directory: {e}")
|
|
555
|
+
return web.json_response(
|
|
556
|
+
{
|
|
557
|
+
"working_directory": "/Users/masa/Projects/claude-mpm",
|
|
558
|
+
"home_directory": "/Users/masa",
|
|
559
|
+
"error": str(e),
|
|
560
|
+
},
|
|
561
|
+
status=500,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
self.app.router.add_get("/api/working-directory", working_directory_handler)
|
|
565
|
+
self.logger.info(
|
|
566
|
+
"✅ Working directory endpoint registered at /api/working-directory"
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
# Add file reading endpoint for source viewer
|
|
570
|
+
async def file_read_handler(request):
|
|
571
|
+
"""Handle GET /api/file/read for reading source files."""
|
|
572
|
+
|
|
573
|
+
file_path = request.query.get("path", "")
|
|
574
|
+
|
|
575
|
+
if not file_path:
|
|
576
|
+
return web.json_response({"error": "No path provided"}, status=400)
|
|
577
|
+
|
|
578
|
+
abs_path = Path(Path(file_path).resolve().expanduser())
|
|
579
|
+
|
|
580
|
+
# Security check - ensure file is within the project
|
|
581
|
+
try:
|
|
582
|
+
project_root = Path.cwd()
|
|
583
|
+
if not abs_path.startswith(project_root):
|
|
584
|
+
return web.json_response({"error": "Access denied"}, status=403)
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
587
|
+
|
|
588
|
+
if not Path(abs_path).exists():
|
|
589
|
+
return web.json_response({"error": "File not found"}, status=404)
|
|
590
|
+
|
|
591
|
+
if not Path(abs_path).is_file():
|
|
592
|
+
return web.json_response({"error": "Not a file"}, status=400)
|
|
593
|
+
|
|
594
|
+
try:
|
|
595
|
+
# Read file with appropriate encoding
|
|
596
|
+
encodings = ["utf-8", "latin-1", "cp1252"]
|
|
597
|
+
content = None
|
|
598
|
+
|
|
599
|
+
for encoding in encodings:
|
|
600
|
+
try:
|
|
601
|
+
with Path(abs_path).open(
|
|
602
|
+
encoding=encoding,
|
|
603
|
+
) as f:
|
|
604
|
+
content = f.read()
|
|
605
|
+
break
|
|
606
|
+
except UnicodeDecodeError:
|
|
607
|
+
continue
|
|
608
|
+
|
|
609
|
+
if content is None:
|
|
610
|
+
return web.json_response(
|
|
611
|
+
{"error": "Could not decode file"}, status=400
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
return web.json_response(
|
|
615
|
+
{
|
|
616
|
+
"path": abs_path,
|
|
617
|
+
"name": Path(abs_path).name,
|
|
618
|
+
"content": content,
|
|
619
|
+
"lines": len(content.splitlines()),
|
|
620
|
+
"size": Path(abs_path).stat().st_size,
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
except PermissionError:
|
|
625
|
+
return web.json_response({"error": "Permission denied"}, status=403)
|
|
626
|
+
except Exception as e:
|
|
627
|
+
return web.json_response({"error": str(e)}, status=500)
|
|
628
|
+
|
|
629
|
+
self.app.router.add_get("/api/file/read", file_read_handler)
|
|
630
|
+
self.logger.info("✅ File reading API registered at /api/file/read")
|
|
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
|
+
|
|
795
|
+
def _setup_directory_api(self):
|
|
796
|
+
"""Setup simple directory listing API.
|
|
797
|
+
|
|
798
|
+
WHY: Provides a dead-simple way to list directory contents via HTTP GET
|
|
799
|
+
without complex WebSocket interactions.
|
|
800
|
+
"""
|
|
801
|
+
try:
|
|
802
|
+
from claude_mpm.dashboard.api.simple_directory import register_routes
|
|
803
|
+
|
|
804
|
+
register_routes(self.app)
|
|
805
|
+
self.logger.info(
|
|
806
|
+
"✅ Simple directory API registered at /api/directory/list"
|
|
807
|
+
)
|
|
808
|
+
except Exception as e:
|
|
809
|
+
self.logger.error(f"Failed to setup directory API: {e}")
|
|
810
|
+
|
|
811
|
+
def _setup_static_files(self):
|
|
812
|
+
"""Setup static file serving for the Svelte dashboard."""
|
|
813
|
+
try:
|
|
814
|
+
# Add debug logging for deployment context
|
|
815
|
+
try:
|
|
816
|
+
from ....core.unified_paths import PathContext
|
|
817
|
+
|
|
818
|
+
deployment_context = PathContext.detect_deployment_context()
|
|
819
|
+
self.logger.debug(
|
|
820
|
+
f"Setting up static files in {deployment_context.value} mode"
|
|
821
|
+
)
|
|
822
|
+
except Exception as e:
|
|
823
|
+
self.logger.debug(f"Could not detect deployment context: {e}")
|
|
824
|
+
|
|
825
|
+
# Find Svelte build directory
|
|
826
|
+
svelte_build_path = self._find_static_path()
|
|
827
|
+
|
|
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
|
|
831
|
+
|
|
832
|
+
# Serve Svelte index.html at root
|
|
833
|
+
async def index_handler(request):
|
|
834
|
+
index_file = svelte_build_path / "index.html"
|
|
835
|
+
if index_file.exists():
|
|
836
|
+
self.logger.debug(
|
|
837
|
+
f"Serving Svelte dashboard from: {index_file}"
|
|
838
|
+
)
|
|
839
|
+
return web.FileResponse(index_file)
|
|
840
|
+
self.logger.warning(f"Svelte index.html not found at: {index_file}")
|
|
841
|
+
return web.Response(text="Dashboard not available", status=404)
|
|
842
|
+
|
|
843
|
+
self.app.router.add_get("/", index_handler)
|
|
844
|
+
|
|
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"
|
|
850
|
+
)
|
|
851
|
+
self.logger.info(
|
|
852
|
+
f"✅ Svelte dashboard available at http://{self.host}:{self.port}/ (build: {svelte_build_path})"
|
|
853
|
+
)
|
|
854
|
+
else:
|
|
855
|
+
self.logger.warning(
|
|
856
|
+
f"⚠️ Svelte _app directory not found at: {svelte_app_path}"
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
# Serve version.json from Svelte build directory
|
|
860
|
+
async def version_handler(request):
|
|
861
|
+
version_file = svelte_build_path / "version.json"
|
|
862
|
+
if version_file.exists():
|
|
863
|
+
self.logger.debug(f"Serving version.json from: {version_file}")
|
|
864
|
+
return web.FileResponse(version_file)
|
|
865
|
+
# Return default version info if file doesn't exist
|
|
866
|
+
return web.json_response(
|
|
867
|
+
{
|
|
868
|
+
"version": "1.0.0",
|
|
869
|
+
"build": 1,
|
|
870
|
+
"formatted_build": "0001",
|
|
871
|
+
"full_version": "v1.0.0-0001",
|
|
872
|
+
}
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
self.app.router.add_get("/version.json", version_handler)
|
|
876
|
+
|
|
877
|
+
else:
|
|
878
|
+
self.logger.warning(
|
|
879
|
+
"⚠️ Svelte dashboard not found, serving fallback response"
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Fallback handler
|
|
883
|
+
async def fallback_handler(request):
|
|
884
|
+
return web.Response(
|
|
885
|
+
text="Socket.IO server running - Dashboard not available",
|
|
886
|
+
status=200,
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
self.app.router.add_get("/", fallback_handler)
|
|
890
|
+
|
|
891
|
+
except Exception as e:
|
|
892
|
+
self.logger.error(f"❌ Error setting up static files: {e}")
|
|
893
|
+
import traceback
|
|
894
|
+
|
|
895
|
+
self.logger.debug(f"Static file setup traceback: {traceback.format_exc()}")
|
|
896
|
+
|
|
897
|
+
# Ensure we always have a basic handler
|
|
898
|
+
async def error_handler(request):
|
|
899
|
+
return web.Response(
|
|
900
|
+
text="Socket.IO server running - Static files unavailable",
|
|
901
|
+
status=200,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
self.app.router.add_get("/", error_handler)
|
|
905
|
+
|
|
906
|
+
def _find_static_path(self):
|
|
907
|
+
"""Find the Svelte build directory using multiple approaches.
|
|
908
|
+
|
|
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.
|
|
911
|
+
"""
|
|
912
|
+
# Get deployment-context-aware paths
|
|
913
|
+
try:
|
|
914
|
+
from ....core.unified_paths import get_path_manager
|
|
915
|
+
|
|
916
|
+
path_manager = get_path_manager()
|
|
917
|
+
|
|
918
|
+
# Use package root for installed packages (including pipx)
|
|
919
|
+
package_root = path_manager.package_root
|
|
920
|
+
self.logger.debug(f"Package root: {package_root}")
|
|
921
|
+
|
|
922
|
+
# Use project root for development
|
|
923
|
+
project_root = get_project_root()
|
|
924
|
+
self.logger.debug(f"Project root: {project_root}")
|
|
925
|
+
|
|
926
|
+
except Exception as e:
|
|
927
|
+
self.logger.debug(f"Could not get path manager: {e}")
|
|
928
|
+
package_root = None
|
|
929
|
+
project_root = get_project_root()
|
|
930
|
+
|
|
931
|
+
# Try multiple possible locations for Svelte build directory
|
|
932
|
+
possible_paths = [
|
|
933
|
+
# Package-based paths (for pipx and pip installations)
|
|
934
|
+
package_root / "dashboard" / "static" / "svelte-build"
|
|
935
|
+
if package_root
|
|
936
|
+
else None,
|
|
937
|
+
# Project-based paths (for development)
|
|
938
|
+
project_root
|
|
939
|
+
/ "src"
|
|
940
|
+
/ "claude_mpm"
|
|
941
|
+
/ "dashboard"
|
|
942
|
+
/ "static"
|
|
943
|
+
/ "svelte-build",
|
|
944
|
+
project_root / "dashboard" / "static" / "svelte-build",
|
|
945
|
+
# Package installation locations (fallback)
|
|
946
|
+
Path(__file__).parent.parent.parent
|
|
947
|
+
/ "dashboard"
|
|
948
|
+
/ "static"
|
|
949
|
+
/ "svelte-build",
|
|
950
|
+
# Scripts directory (for standalone installations)
|
|
951
|
+
get_scripts_dir() / "dashboard" / "static" / "svelte-build",
|
|
952
|
+
# Current working directory
|
|
953
|
+
Path.cwd() / "src" / "claude_mpm" / "dashboard" / "static" / "svelte-build",
|
|
954
|
+
Path.cwd() / "dashboard" / "static" / "svelte-build",
|
|
955
|
+
]
|
|
956
|
+
|
|
957
|
+
# Filter out None values
|
|
958
|
+
possible_paths = [p for p in possible_paths if p is not None]
|
|
959
|
+
self.logger.debug(
|
|
960
|
+
f"Searching {len(possible_paths)} possible Svelte build locations"
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
for path in possible_paths:
|
|
964
|
+
self.logger.debug(f"Checking for Svelte build at: {path}")
|
|
965
|
+
try:
|
|
966
|
+
if path.exists() and path.is_dir():
|
|
967
|
+
# Check if it contains expected Svelte build files
|
|
968
|
+
if (path / "index.html").exists():
|
|
969
|
+
self.logger.info(f"✅ Found Svelte build at: {path}")
|
|
970
|
+
return path
|
|
971
|
+
self.logger.debug(f"Directory exists but no index.html: {path}")
|
|
972
|
+
else:
|
|
973
|
+
self.logger.debug(f"Path does not exist: {path}")
|
|
974
|
+
except Exception as e:
|
|
975
|
+
self.logger.debug(f"Error checking path {path}: {e}")
|
|
976
|
+
|
|
977
|
+
self.logger.warning(
|
|
978
|
+
"⚠️ Svelte build not found - dashboard will not be available"
|
|
979
|
+
)
|
|
980
|
+
self.logger.debug(f"Searched paths: {[str(p) for p in possible_paths]}")
|
|
981
|
+
return None
|
|
982
|
+
|
|
983
|
+
def get_connection_count(self) -> int:
|
|
984
|
+
"""Get number of connected clients.
|
|
985
|
+
|
|
986
|
+
WHY: Provides interface compliance for monitoring.
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
Number of connected clients
|
|
990
|
+
"""
|
|
991
|
+
return len(self.connected_clients)
|
|
992
|
+
|
|
993
|
+
def is_running(self) -> bool:
|
|
994
|
+
"""Check if server is running.
|
|
995
|
+
|
|
996
|
+
WHY: Provides interface compliance for status checking.
|
|
997
|
+
|
|
998
|
+
Returns:
|
|
999
|
+
True if server is active
|
|
1000
|
+
"""
|
|
1001
|
+
return self.running
|
|
1002
|
+
|
|
1003
|
+
async def _heartbeat_loop(self):
|
|
1004
|
+
"""Send periodic heartbeat events to connected clients.
|
|
1005
|
+
|
|
1006
|
+
WHY: This provides a way to verify the event flow is working and
|
|
1007
|
+
track server health and active sessions without relying on hook events.
|
|
1008
|
+
"""
|
|
1009
|
+
while self.running:
|
|
1010
|
+
try:
|
|
1011
|
+
# Wait for the interval
|
|
1012
|
+
await asyncio.sleep(self.heartbeat_interval)
|
|
1013
|
+
|
|
1014
|
+
if not self.sio:
|
|
1015
|
+
continue
|
|
1016
|
+
|
|
1017
|
+
# Calculate uptime
|
|
1018
|
+
uptime_seconds = 0
|
|
1019
|
+
if self.stats.get("start_time"):
|
|
1020
|
+
uptime_seconds = int(
|
|
1021
|
+
(
|
|
1022
|
+
datetime.now(timezone.utc) - self.stats["start_time"]
|
|
1023
|
+
).total_seconds()
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
# Get active sessions from main server if available
|
|
1027
|
+
active_sessions = []
|
|
1028
|
+
if self.main_server and hasattr(
|
|
1029
|
+
self.main_server, "get_active_sessions"
|
|
1030
|
+
):
|
|
1031
|
+
try:
|
|
1032
|
+
active_sessions = self.main_server.get_active_sessions()
|
|
1033
|
+
except Exception as e:
|
|
1034
|
+
self.logger.debug(f"Could not get active sessions: {e}")
|
|
1035
|
+
|
|
1036
|
+
# Prepare heartbeat data (using new schema)
|
|
1037
|
+
heartbeat_data = {
|
|
1038
|
+
"type": "system",
|
|
1039
|
+
"subtype": "heartbeat",
|
|
1040
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1041
|
+
"source": "server",
|
|
1042
|
+
"data": {
|
|
1043
|
+
"uptime_seconds": uptime_seconds,
|
|
1044
|
+
"connected_clients": len(self.connected_clients),
|
|
1045
|
+
"total_events": self.stats.get("events_sent", 0),
|
|
1046
|
+
"active_sessions": active_sessions,
|
|
1047
|
+
"server_info": {
|
|
1048
|
+
"version": VersionService().get_version(),
|
|
1049
|
+
"port": self.port,
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
# Add to event history UNCONDITIONALLY
|
|
1055
|
+
# WHY: Heartbeat events should persist for new clients too
|
|
1056
|
+
if self.main_server and hasattr(self.main_server, "event_history"):
|
|
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!")
|
|
1063
|
+
|
|
1064
|
+
# Emit heartbeat to all connected clients (already using new schema)
|
|
1065
|
+
await self.sio.emit("system_event", heartbeat_data)
|
|
1066
|
+
|
|
1067
|
+
self.logger.info(
|
|
1068
|
+
f"System heartbeat sent - clients: {len(self.connected_clients)}, "
|
|
1069
|
+
f"uptime: {uptime_seconds}s, events: {self.stats.get('events_sent', 0)}, "
|
|
1070
|
+
f"sessions: {len(active_sessions)}"
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
except asyncio.CancelledError:
|
|
1074
|
+
# Task was cancelled, exit gracefully
|
|
1075
|
+
break
|
|
1076
|
+
except Exception as e:
|
|
1077
|
+
self.logger.error(f"Error in heartbeat loop: {e}")
|
|
1078
|
+
# Continue running even if one heartbeat fails
|
|
1079
|
+
await asyncio.sleep(5) # Short delay before retry
|