claude-mpm 5.0.9__py3-none-any.whl → 5.6.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +115 -0
- claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +186 -0
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +479 -616
- claude_mpm/agents/WORKFLOW.md +6 -253
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +70 -2
- claude_mpm/agents/templates/circuit-breakers.md +457 -62
- claude_mpm/cli/__init__.py +5 -2
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +18 -27
- claude_mpm/cli/commands/agents.py +177 -41
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/auto_configure.py +723 -236
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1874 -170
- claude_mpm/cli/commands/configure_agent_display.py +27 -6
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +232 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +379 -204
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +141 -19
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +115 -60
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
- claude_mpm/cli/parsers/base_parser.py +88 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +1 -1
- claude_mpm/cli/startup.py +1017 -266
- claude_mpm/cli/startup_display.py +74 -6
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +146 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +146 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +450 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +36 -0
- claude_mpm/commands/mpm-doctor.md +16 -21
- claude_mpm/commands/mpm-help.md +12 -286
- claude_mpm/commands/mpm-init.md +88 -506
- claude_mpm/commands/mpm-monitor.md +22 -401
- claude_mpm/commands/mpm-organize.md +128 -0
- claude_mpm/commands/mpm-postmortem.md +13 -107
- claude_mpm/commands/mpm-session-resume.md +20 -363
- claude_mpm/commands/mpm-status.md +13 -69
- claude_mpm/commands/mpm-ticket-view.md +60 -495
- claude_mpm/commands/mpm-version.md +13 -107
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +154 -2
- claude_mpm/core/config.py +37 -26
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +39 -9
- claude_mpm/core/logging_utils.py +35 -11
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/optimized_startup.py +61 -0
- claude_mpm/core/output_style_manager.py +219 -44
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +16 -8
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +76 -8
- claude_mpm/core/unified_paths.py +95 -90
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/1WZnGYqX.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.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/BRcwIQNr.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C3rbW_a-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DR8nis88.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.RgBboRvH.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.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/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.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__/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/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +479 -128
- claude_mpm/hooks/claude_hooks/hook_handler.py +254 -83
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +149 -18
- claude_mpm/hooks/claude_hooks/memory_integration.py +67 -19
- claude_mpm/hooks/claude_hooks/response_tracking.py +44 -62
- 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 +69 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
- claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/init.py +276 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/claude-hook-handler.sh +87 -20
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +7 -7
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +6 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +42 -20
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +348 -29
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +570 -68
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/git_source_manager.py +57 -4
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +6 -6
- claude_mpm/services/agents/sources/git_source_sync_service.py +129 -11
- claude_mpm/services/agents/startup_sync.py +27 -4
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +31 -1
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/git/git_operations_service.py +101 -16
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +54 -7
- claude_mpm/services/monitor/management/lifecycle.py +15 -3
- claude_mpm/services/monitor/server.py +796 -30
- claude_mpm/services/pm_skills_deployer.py +884 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +303 -12
- claude_mpm/services/skills/selective_skill_deployer.py +869 -0
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +294 -55
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +51 -6
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/core.py +386 -108
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +98 -3
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +115 -4
- claude_mpm/utils/agent_filters.py +17 -44
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +86 -21
- claude_mpm-5.6.23.dist-info/METADATA +393 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/RECORD +508 -261
- claude_mpm-5.6.23.dist-info/entry_points.txt +5 -0
- claude_mpm-5.6.23.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.6.23.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/OUTPUT_STYLE.md +0 -290
- claude_mpm/agents/PM_INSTRUCTIONS_TEACH.md +0 -1322
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.0.9.dist-info/METADATA +0 -1028
- claude_mpm-5.0.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.9.dist-info → claude_mpm-5.6.23.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,6 @@ DESIGN DECISIONS:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
-
import contextlib
|
|
19
18
|
import os
|
|
20
19
|
import threading
|
|
21
20
|
import time
|
|
@@ -25,10 +24,11 @@ from typing import Dict, Optional
|
|
|
25
24
|
|
|
26
25
|
import socketio
|
|
27
26
|
from aiohttp import web
|
|
27
|
+
from watchdog.events import FileSystemEventHandler
|
|
28
|
+
from watchdog.observers import Observer
|
|
28
29
|
|
|
29
30
|
from ...core.enums import ServiceState
|
|
30
31
|
from ...core.logging_config import get_logger
|
|
31
|
-
from ...dashboard.api.simple_directory import list_directory
|
|
32
32
|
from .event_emitter import get_event_emitter
|
|
33
33
|
from .handlers.code_analysis import CodeAnalysisHandler
|
|
34
34
|
from .handlers.dashboard import DashboardHandler
|
|
@@ -45,6 +45,91 @@ except ImportError:
|
|
|
45
45
|
EVENTBUS_AVAILABLE = False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class SvelteBuildWatcher(FileSystemEventHandler):
|
|
49
|
+
"""File watcher for Svelte build directory changes.
|
|
50
|
+
|
|
51
|
+
Watches for file changes in svelte-build directory and triggers
|
|
52
|
+
hot reload via Socket.IO event emission.
|
|
53
|
+
|
|
54
|
+
STABILITY FIX: Added thread lock and stop() method to prevent timer leaks.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self, sio: socketio.AsyncServer, loop: asyncio.AbstractEventLoop, logger
|
|
59
|
+
):
|
|
60
|
+
"""Initialize the file watcher.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
sio: Socket.IO server instance for emitting events
|
|
64
|
+
loop: Event loop for async operations
|
|
65
|
+
logger: Logger instance
|
|
66
|
+
"""
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.sio = sio
|
|
69
|
+
self.loop = loop
|
|
70
|
+
self.logger = logger
|
|
71
|
+
self.debounce_timer = None
|
|
72
|
+
self.debounce_delay = 0.5 # Wait 500ms after last change
|
|
73
|
+
self._timer_lock = threading.Lock() # STABILITY FIX: Prevent race condition
|
|
74
|
+
|
|
75
|
+
def stop(self):
|
|
76
|
+
"""Stop the watcher and cancel any pending timers.
|
|
77
|
+
|
|
78
|
+
STABILITY FIX: Ensures timer is cancelled on shutdown.
|
|
79
|
+
"""
|
|
80
|
+
with self._timer_lock:
|
|
81
|
+
if self.debounce_timer:
|
|
82
|
+
self.debounce_timer.cancel()
|
|
83
|
+
self.debounce_timer = None
|
|
84
|
+
|
|
85
|
+
def on_any_event(self, event):
|
|
86
|
+
"""Handle any file system event.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
event: File system event from watchdog
|
|
90
|
+
"""
|
|
91
|
+
# Ignore directory events and temporary files
|
|
92
|
+
if event.is_directory or event.src_path.endswith((".tmp", ".swp", "~")):
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
self.logger.debug(
|
|
96
|
+
f"File change detected: {event.event_type} - {event.src_path}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# STABILITY FIX: Use lock to prevent timer race condition
|
|
100
|
+
with self._timer_lock:
|
|
101
|
+
# Cancel existing timer
|
|
102
|
+
if self.debounce_timer:
|
|
103
|
+
self.debounce_timer.cancel()
|
|
104
|
+
|
|
105
|
+
# Schedule reload after debounce delay
|
|
106
|
+
self.debounce_timer = threading.Timer(
|
|
107
|
+
self.debounce_delay, self._trigger_reload
|
|
108
|
+
)
|
|
109
|
+
self.debounce_timer.start()
|
|
110
|
+
|
|
111
|
+
def _trigger_reload(self):
|
|
112
|
+
"""Trigger hot reload by emitting Socket.IO event."""
|
|
113
|
+
try:
|
|
114
|
+
# Schedule the async emit in the event loop
|
|
115
|
+
asyncio.run_coroutine_threadsafe(self._emit_reload_event(), self.loop)
|
|
116
|
+
self.logger.info("Hot reload triggered - Svelte build changed")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self.logger.error(f"Error triggering reload: {e}")
|
|
119
|
+
|
|
120
|
+
async def _emit_reload_event(self):
|
|
121
|
+
"""Emit the reload event to all connected clients."""
|
|
122
|
+
if self.sio:
|
|
123
|
+
await self.sio.emit(
|
|
124
|
+
"reload",
|
|
125
|
+
{
|
|
126
|
+
"type": "reload",
|
|
127
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
128
|
+
"reason": "svelte-build-updated",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
48
133
|
class UnifiedMonitorServer:
|
|
49
134
|
"""Unified server that combines HTTP dashboard and Socket.IO functionality.
|
|
50
135
|
|
|
@@ -52,15 +137,19 @@ class UnifiedMonitorServer:
|
|
|
52
137
|
Replaces multiple competing server implementations with one stable solution.
|
|
53
138
|
"""
|
|
54
139
|
|
|
55
|
-
def __init__(
|
|
140
|
+
def __init__(
|
|
141
|
+
self, host: str = "localhost", port: int = 8765, enable_hot_reload: bool = False
|
|
142
|
+
):
|
|
56
143
|
"""Initialize the unified monitor server.
|
|
57
144
|
|
|
58
145
|
Args:
|
|
59
146
|
host: Host to bind to
|
|
60
147
|
port: Port to bind to
|
|
148
|
+
enable_hot_reload: Enable file watching and hot reload for development
|
|
61
149
|
"""
|
|
62
150
|
self.host = host
|
|
63
151
|
self.port = port
|
|
152
|
+
self.enable_hot_reload = enable_hot_reload
|
|
64
153
|
self.logger = get_logger(__name__)
|
|
65
154
|
|
|
66
155
|
# Core components
|
|
@@ -78,6 +167,10 @@ class UnifiedMonitorServer:
|
|
|
78
167
|
# High-performance event emitter
|
|
79
168
|
self.event_emitter = None
|
|
80
169
|
|
|
170
|
+
# File watching (optional for dev mode)
|
|
171
|
+
self.file_observer: Optional[Observer] = None
|
|
172
|
+
self.file_watcher: Optional[SvelteBuildWatcher] = None
|
|
173
|
+
|
|
81
174
|
# State
|
|
82
175
|
self.running = False
|
|
83
176
|
self.loop = None
|
|
@@ -184,6 +277,9 @@ class UnifiedMonitorServer:
|
|
|
184
277
|
|
|
185
278
|
time.sleep(0.1)
|
|
186
279
|
|
|
280
|
+
# STABILITY FIX: Give tasks more time to clean up before closing
|
|
281
|
+
time.sleep(0.5)
|
|
282
|
+
|
|
187
283
|
# Clear the event loop from the thread BEFORE closing
|
|
188
284
|
# This prevents other code from accidentally using it
|
|
189
285
|
asyncio.set_event_loop(None)
|
|
@@ -229,6 +325,10 @@ class UnifiedMonitorServer:
|
|
|
229
325
|
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
230
326
|
self.logger.info("Heartbeat task started (3-minute interval)")
|
|
231
327
|
|
|
328
|
+
# Setup file watching for hot reload (if enabled)
|
|
329
|
+
if self.enable_hot_reload:
|
|
330
|
+
self._setup_file_watcher()
|
|
331
|
+
|
|
232
332
|
# Setup HTTP routes
|
|
233
333
|
self._setup_http_routes()
|
|
234
334
|
|
|
@@ -268,6 +368,83 @@ class UnifiedMonitorServer:
|
|
|
268
368
|
finally:
|
|
269
369
|
await self._cleanup_async()
|
|
270
370
|
|
|
371
|
+
def _categorize_event(self, event_name: str) -> str:
|
|
372
|
+
"""Categorize event by name to determine Socket.IO event type.
|
|
373
|
+
|
|
374
|
+
Maps specific event names to their category for frontend filtering.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
event_name: The raw event name (e.g., "subagent_start", "todo_updated")
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Category name (e.g., "hook_event", "system_event")
|
|
381
|
+
"""
|
|
382
|
+
# Hook events - agent lifecycle and todo updates
|
|
383
|
+
if event_name in ("subagent_start", "subagent_stop", "todo_updated"):
|
|
384
|
+
return "hook_event"
|
|
385
|
+
|
|
386
|
+
# Tool events - both hook-style and direct tool events
|
|
387
|
+
if event_name in (
|
|
388
|
+
"pre_tool",
|
|
389
|
+
"post_tool",
|
|
390
|
+
"tool.start",
|
|
391
|
+
"tool.end",
|
|
392
|
+
"tool_use",
|
|
393
|
+
"tool_result",
|
|
394
|
+
):
|
|
395
|
+
return "tool_event"
|
|
396
|
+
|
|
397
|
+
# Session events - session lifecycle
|
|
398
|
+
if event_name in (
|
|
399
|
+
"session.started",
|
|
400
|
+
"session.ended",
|
|
401
|
+
"session_start",
|
|
402
|
+
"session_end",
|
|
403
|
+
):
|
|
404
|
+
return "session_event"
|
|
405
|
+
|
|
406
|
+
# Response events - API response lifecycle
|
|
407
|
+
if event_name in (
|
|
408
|
+
"response.start",
|
|
409
|
+
"response.end",
|
|
410
|
+
"response_started",
|
|
411
|
+
"response_ended",
|
|
412
|
+
):
|
|
413
|
+
return "response_event"
|
|
414
|
+
|
|
415
|
+
# Agent events - agent delegation and returns
|
|
416
|
+
if event_name in (
|
|
417
|
+
"agent.delegated",
|
|
418
|
+
"agent.returned",
|
|
419
|
+
"agent_start",
|
|
420
|
+
"agent_end",
|
|
421
|
+
):
|
|
422
|
+
return "agent_event"
|
|
423
|
+
|
|
424
|
+
# File events - file operations
|
|
425
|
+
if event_name in (
|
|
426
|
+
"file.read",
|
|
427
|
+
"file.write",
|
|
428
|
+
"file.edit",
|
|
429
|
+
"file_read",
|
|
430
|
+
"file_write",
|
|
431
|
+
):
|
|
432
|
+
return "file_event"
|
|
433
|
+
|
|
434
|
+
# Claude API events
|
|
435
|
+
if event_name in ("user_prompt", "assistant_message"):
|
|
436
|
+
return "claude_event"
|
|
437
|
+
|
|
438
|
+
# System events
|
|
439
|
+
if event_name in ("system_ready", "system_shutdown"):
|
|
440
|
+
return "system_event"
|
|
441
|
+
|
|
442
|
+
# Log uncategorized events for debugging
|
|
443
|
+
self.logger.debug(f"Uncategorized event: {event_name}")
|
|
444
|
+
|
|
445
|
+
# Default to claude_event for unknown events
|
|
446
|
+
return "claude_event"
|
|
447
|
+
|
|
271
448
|
def _setup_event_handlers(self):
|
|
272
449
|
"""Setup Socket.IO event handlers."""
|
|
273
450
|
try:
|
|
@@ -304,20 +481,64 @@ class UnifiedMonitorServer:
|
|
|
304
481
|
self.logger.error(f"Error setting up event emitter: {e}")
|
|
305
482
|
raise
|
|
306
483
|
|
|
484
|
+
def _setup_file_watcher(self):
|
|
485
|
+
"""Setup file watcher for Svelte build directory.
|
|
486
|
+
|
|
487
|
+
Watches for changes in svelte-build and triggers hot reload.
|
|
488
|
+
Only enabled when enable_hot_reload is True.
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
492
|
+
svelte_build_dir = dashboard_dir / "static" / "svelte-build"
|
|
493
|
+
|
|
494
|
+
if not svelte_build_dir.exists():
|
|
495
|
+
self.logger.warning(
|
|
496
|
+
f"Svelte build directory not found: {svelte_build_dir}. "
|
|
497
|
+
"Hot reload disabled."
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
# Create file watcher with Socket.IO reference
|
|
502
|
+
self.file_watcher = SvelteBuildWatcher(
|
|
503
|
+
sio=self.sio, loop=self.loop, logger=self.logger
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Create observer and schedule watching
|
|
507
|
+
self.file_observer = Observer()
|
|
508
|
+
self.file_observer.schedule(
|
|
509
|
+
self.file_watcher, str(svelte_build_dir), recursive=True
|
|
510
|
+
)
|
|
511
|
+
self.file_observer.start()
|
|
512
|
+
|
|
513
|
+
self.logger.info(f"🔥 Hot reload enabled - watching {svelte_build_dir}")
|
|
514
|
+
|
|
515
|
+
except Exception as e:
|
|
516
|
+
self.logger.error(f"Error setting up file watcher: {e}")
|
|
517
|
+
# Don't raise - hot reload is optional
|
|
518
|
+
|
|
307
519
|
def _setup_http_routes(self):
|
|
308
520
|
"""Setup HTTP routes for the dashboard."""
|
|
309
521
|
try:
|
|
310
|
-
# Dashboard static files
|
|
311
|
-
dashboard_dir = Path(__file__).parent.parent.parent / "dashboard"
|
|
522
|
+
# Dashboard static files - use .resolve() for absolute path
|
|
523
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
524
|
+
static_dir = dashboard_dir / "static"
|
|
312
525
|
|
|
313
|
-
# Main dashboard route
|
|
526
|
+
# Main dashboard route - serve Svelte dashboard
|
|
314
527
|
async def dashboard_index(request):
|
|
315
|
-
|
|
316
|
-
if
|
|
317
|
-
with
|
|
528
|
+
svelte_index = static_dir / "svelte-build" / "index.html"
|
|
529
|
+
if svelte_index.exists():
|
|
530
|
+
with svelte_index.open(encoding="utf-8") as f:
|
|
318
531
|
content = f.read()
|
|
319
532
|
return web.Response(text=content, content_type="text/html")
|
|
320
|
-
|
|
533
|
+
|
|
534
|
+
# Log error with path details for debugging
|
|
535
|
+
self.logger.error(
|
|
536
|
+
f"Dashboard index.html not found at: {svelte_index.resolve()}"
|
|
537
|
+
)
|
|
538
|
+
return web.Response(
|
|
539
|
+
text=f"Dashboard not found. Expected location: {svelte_index.resolve()}",
|
|
540
|
+
status=404,
|
|
541
|
+
)
|
|
321
542
|
|
|
322
543
|
# Health check
|
|
323
544
|
async def health_check(request):
|
|
@@ -325,11 +546,12 @@ class UnifiedMonitorServer:
|
|
|
325
546
|
version = "1.0.0"
|
|
326
547
|
try:
|
|
327
548
|
version_file = (
|
|
328
|
-
Path(__file__).parent.parent.parent.parent.parent
|
|
549
|
+
Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
550
|
+
/ "VERSION"
|
|
329
551
|
)
|
|
330
552
|
if version_file.exists():
|
|
331
553
|
version = version_file.read_text().strip()
|
|
332
|
-
except Exception:
|
|
554
|
+
except Exception: # nosec B110
|
|
333
555
|
pass
|
|
334
556
|
|
|
335
557
|
return web.json_response(
|
|
@@ -354,10 +576,23 @@ class UnifiedMonitorServer:
|
|
|
354
576
|
event = data.get("event", "claude_event")
|
|
355
577
|
event_data = data.get("data", {})
|
|
356
578
|
|
|
357
|
-
#
|
|
579
|
+
# Categorize event and wrap in expected format
|
|
580
|
+
event_type = self._categorize_event(event)
|
|
581
|
+
wrapped_event = {
|
|
582
|
+
"type": event_type,
|
|
583
|
+
"subtype": event,
|
|
584
|
+
"data": event_data,
|
|
585
|
+
"timestamp": event_data.get("timestamp")
|
|
586
|
+
or datetime.now(timezone.utc).isoformat() + "Z",
|
|
587
|
+
"session_id": event_data.get("session_id"),
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
# Emit to Socket.IO clients via the categorized event type
|
|
358
591
|
if self.sio:
|
|
359
|
-
await self.sio.emit(
|
|
360
|
-
self.logger.debug(
|
|
592
|
+
await self.sio.emit(event_type, wrapped_event)
|
|
593
|
+
self.logger.debug(
|
|
594
|
+
f"HTTP event forwarded to Socket.IO: {event} -> {event_type}"
|
|
595
|
+
)
|
|
361
596
|
|
|
362
597
|
return web.Response(status=204) # No content response
|
|
363
598
|
|
|
@@ -442,6 +677,243 @@ class UnifiedMonitorServer:
|
|
|
442
677
|
{"success": False, "error": str(e)}, status=500
|
|
443
678
|
)
|
|
444
679
|
|
|
680
|
+
# File listing endpoint for file browser
|
|
681
|
+
async def api_files_handler(request):
|
|
682
|
+
"""List files in a directory for the file browser."""
|
|
683
|
+
try:
|
|
684
|
+
# Get path from query param, default to working directory
|
|
685
|
+
path = request.query.get("path", str(Path.cwd()))
|
|
686
|
+
dir_path = Path(path)
|
|
687
|
+
|
|
688
|
+
if not dir_path.exists():
|
|
689
|
+
return web.json_response(
|
|
690
|
+
{"success": False, "error": "Directory not found"},
|
|
691
|
+
status=404,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
if not dir_path.is_dir():
|
|
695
|
+
return web.json_response(
|
|
696
|
+
{"success": False, "error": "Path is not a directory"},
|
|
697
|
+
status=400,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Patterns to exclude
|
|
701
|
+
exclude_patterns = {
|
|
702
|
+
".git",
|
|
703
|
+
"node_modules",
|
|
704
|
+
"__pycache__",
|
|
705
|
+
".svelte-kit",
|
|
706
|
+
"venv",
|
|
707
|
+
".venv",
|
|
708
|
+
"dist",
|
|
709
|
+
"build",
|
|
710
|
+
".next",
|
|
711
|
+
".cache",
|
|
712
|
+
".pytest_cache",
|
|
713
|
+
".mypy_cache",
|
|
714
|
+
".ruff_cache",
|
|
715
|
+
"eggs",
|
|
716
|
+
"*.egg-info",
|
|
717
|
+
".tox",
|
|
718
|
+
".nox",
|
|
719
|
+
"htmlcov",
|
|
720
|
+
".coverage",
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
entries = []
|
|
724
|
+
try:
|
|
725
|
+
for entry in sorted(
|
|
726
|
+
dir_path.iterdir(),
|
|
727
|
+
key=lambda x: (not x.is_dir(), x.name.lower()),
|
|
728
|
+
):
|
|
729
|
+
# Skip hidden files and excluded patterns
|
|
730
|
+
if entry.name.startswith(".") and entry.name not in {
|
|
731
|
+
".env",
|
|
732
|
+
".gitignore",
|
|
733
|
+
}:
|
|
734
|
+
if entry.name in {".git", ".svelte-kit", ".cache"}:
|
|
735
|
+
continue
|
|
736
|
+
if entry.name in exclude_patterns:
|
|
737
|
+
continue
|
|
738
|
+
if any(
|
|
739
|
+
entry.name.endswith(p.replace("*", ""))
|
|
740
|
+
for p in exclude_patterns
|
|
741
|
+
if "*" in p
|
|
742
|
+
):
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
try:
|
|
746
|
+
stat = entry.stat()
|
|
747
|
+
entries.append(
|
|
748
|
+
{
|
|
749
|
+
"name": entry.name,
|
|
750
|
+
"path": str(entry),
|
|
751
|
+
"type": "directory"
|
|
752
|
+
if entry.is_dir()
|
|
753
|
+
else "file",
|
|
754
|
+
"size": stat.st_size if entry.is_file() else 0,
|
|
755
|
+
"modified": stat.st_mtime,
|
|
756
|
+
"extension": entry.suffix.lstrip(".")
|
|
757
|
+
if entry.is_file()
|
|
758
|
+
else None,
|
|
759
|
+
}
|
|
760
|
+
)
|
|
761
|
+
except (PermissionError, OSError):
|
|
762
|
+
continue
|
|
763
|
+
|
|
764
|
+
except PermissionError:
|
|
765
|
+
return web.json_response(
|
|
766
|
+
{"success": False, "error": "Permission denied"},
|
|
767
|
+
status=403,
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Separate directories and files
|
|
771
|
+
directories = [e for e in entries if e["type"] == "directory"]
|
|
772
|
+
files = [e for e in entries if e["type"] == "file"]
|
|
773
|
+
|
|
774
|
+
return web.json_response(
|
|
775
|
+
{
|
|
776
|
+
"success": True,
|
|
777
|
+
"path": str(dir_path),
|
|
778
|
+
"directories": directories,
|
|
779
|
+
"files": files,
|
|
780
|
+
"total_directories": len(directories),
|
|
781
|
+
"total_files": len(files),
|
|
782
|
+
}
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
except Exception as e:
|
|
786
|
+
self.logger.error(f"Error listing directory: {e}")
|
|
787
|
+
return web.json_response(
|
|
788
|
+
{"success": False, "error": str(e)}, status=500
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# File read endpoint (GET) for file browser
|
|
792
|
+
async def api_file_read_handler(request):
|
|
793
|
+
"""Read file content via GET request."""
|
|
794
|
+
import base64
|
|
795
|
+
|
|
796
|
+
try:
|
|
797
|
+
file_path = request.query.get("path", "")
|
|
798
|
+
|
|
799
|
+
if not file_path:
|
|
800
|
+
return web.json_response(
|
|
801
|
+
{"success": False, "error": "Path parameter required"},
|
|
802
|
+
status=400,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
path = Path(file_path)
|
|
806
|
+
|
|
807
|
+
if not path.exists():
|
|
808
|
+
return web.json_response(
|
|
809
|
+
{"success": False, "error": "File not found"},
|
|
810
|
+
status=404,
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
if not path.is_file():
|
|
814
|
+
return web.json_response(
|
|
815
|
+
{"success": False, "error": "Path is not a file"},
|
|
816
|
+
status=400,
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# Get file info
|
|
820
|
+
file_size = path.stat().st_size
|
|
821
|
+
file_ext = path.suffix.lstrip(".").lower()
|
|
822
|
+
|
|
823
|
+
# Define image extensions
|
|
824
|
+
image_extensions = {
|
|
825
|
+
"png",
|
|
826
|
+
"jpg",
|
|
827
|
+
"jpeg",
|
|
828
|
+
"gif",
|
|
829
|
+
"svg",
|
|
830
|
+
"webp",
|
|
831
|
+
"ico",
|
|
832
|
+
"bmp",
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
# Check if file is an image
|
|
836
|
+
if file_ext in image_extensions:
|
|
837
|
+
# Read as binary and encode to base64
|
|
838
|
+
try:
|
|
839
|
+
binary_content = path.read_bytes()
|
|
840
|
+
base64_content = base64.b64encode(binary_content).decode(
|
|
841
|
+
"utf-8"
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
# Map extension to MIME type
|
|
845
|
+
mime_types = {
|
|
846
|
+
"png": "image/png",
|
|
847
|
+
"jpg": "image/jpeg",
|
|
848
|
+
"jpeg": "image/jpeg",
|
|
849
|
+
"gif": "image/gif",
|
|
850
|
+
"svg": "image/svg+xml",
|
|
851
|
+
"webp": "image/webp",
|
|
852
|
+
"ico": "image/x-icon",
|
|
853
|
+
"bmp": "image/bmp",
|
|
854
|
+
}
|
|
855
|
+
mime_type = mime_types.get(file_ext, "image/png")
|
|
856
|
+
|
|
857
|
+
return web.json_response(
|
|
858
|
+
{
|
|
859
|
+
"success": True,
|
|
860
|
+
"path": str(path),
|
|
861
|
+
"content": base64_content,
|
|
862
|
+
"size": file_size,
|
|
863
|
+
"type": "image",
|
|
864
|
+
"mime": mime_type,
|
|
865
|
+
"extension": file_ext,
|
|
866
|
+
}
|
|
867
|
+
)
|
|
868
|
+
except Exception as e:
|
|
869
|
+
self.logger.error(f"Error reading image file: {e}")
|
|
870
|
+
return web.json_response(
|
|
871
|
+
{
|
|
872
|
+
"success": False,
|
|
873
|
+
"error": f"Failed to read image: {e!s}",
|
|
874
|
+
},
|
|
875
|
+
status=500,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
# Read text file content
|
|
879
|
+
try:
|
|
880
|
+
content = path.read_text(encoding="utf-8")
|
|
881
|
+
lines = content.count("\n") + 1
|
|
882
|
+
except UnicodeDecodeError:
|
|
883
|
+
return web.json_response(
|
|
884
|
+
{"success": False, "error": "File is not a text file"},
|
|
885
|
+
status=415,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
return web.json_response(
|
|
889
|
+
{
|
|
890
|
+
"success": True,
|
|
891
|
+
"path": str(path),
|
|
892
|
+
"content": content,
|
|
893
|
+
"lines": lines,
|
|
894
|
+
"size": file_size,
|
|
895
|
+
"type": file_ext or "text",
|
|
896
|
+
}
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
except Exception as e:
|
|
900
|
+
self.logger.error(f"Error reading file: {e}")
|
|
901
|
+
return web.json_response(
|
|
902
|
+
{"success": False, "error": str(e)}, status=500
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
# Favicon handler
|
|
906
|
+
async def favicon_handler(request):
|
|
907
|
+
"""Serve favicon.svg from static directory."""
|
|
908
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
909
|
+
|
|
910
|
+
favicon_path = static_dir / "svelte-build" / "favicon.svg"
|
|
911
|
+
if favicon_path.exists():
|
|
912
|
+
return FileResponse(
|
|
913
|
+
favicon_path, headers={"Content-Type": "image/svg+xml"}
|
|
914
|
+
)
|
|
915
|
+
raise web.HTTPNotFound()
|
|
916
|
+
|
|
445
917
|
# Version endpoint for dashboard build tracker
|
|
446
918
|
async def version_handler(request):
|
|
447
919
|
"""Serve version information for dashboard build tracker."""
|
|
@@ -477,7 +949,7 @@ class UnifiedMonitorServer:
|
|
|
477
949
|
# Configuration endpoint for dashboard initialization
|
|
478
950
|
async def config_handler(request):
|
|
479
951
|
"""Return configuration for dashboard initialization."""
|
|
480
|
-
import subprocess
|
|
952
|
+
import subprocess # nosec B404
|
|
481
953
|
|
|
482
954
|
config = {
|
|
483
955
|
"workingDirectory": Path.cwd(),
|
|
@@ -488,7 +960,7 @@ class UnifiedMonitorServer:
|
|
|
488
960
|
|
|
489
961
|
# Try to get current git branch
|
|
490
962
|
try:
|
|
491
|
-
result = subprocess.run(
|
|
963
|
+
result = subprocess.run( # nosec B603 B607
|
|
492
964
|
["git", "branch", "--show-current"],
|
|
493
965
|
capture_output=True,
|
|
494
966
|
text=True,
|
|
@@ -498,7 +970,7 @@ class UnifiedMonitorServer:
|
|
|
498
970
|
)
|
|
499
971
|
if result.returncode == 0 and result.stdout.strip():
|
|
500
972
|
config["gitBranch"] = result.stdout.strip()
|
|
501
|
-
except Exception:
|
|
973
|
+
except Exception: # nosec B110
|
|
502
974
|
pass # Keep default "Unknown" value
|
|
503
975
|
|
|
504
976
|
return web.json_response(config)
|
|
@@ -507,7 +979,7 @@ class UnifiedMonitorServer:
|
|
|
507
979
|
async def working_directory_handler(request):
|
|
508
980
|
"""Return the current working directory."""
|
|
509
981
|
return web.json_response(
|
|
510
|
-
{"working_directory": Path.cwd(), "success": True}
|
|
982
|
+
{"working_directory": str(Path.cwd()), "success": True}
|
|
511
983
|
)
|
|
512
984
|
|
|
513
985
|
# Monitor page routes
|
|
@@ -525,15 +997,249 @@ class UnifiedMonitorServer:
|
|
|
525
997
|
return web.Response(text=content, content_type="text/html")
|
|
526
998
|
return web.Response(text="Page not found", status=404)
|
|
527
999
|
|
|
1000
|
+
# Git history handler
|
|
1001
|
+
async def git_history_handler(request: web.Request) -> web.Response:
|
|
1002
|
+
"""Get git history for a file."""
|
|
1003
|
+
import subprocess # nosec B404
|
|
1004
|
+
|
|
1005
|
+
try:
|
|
1006
|
+
data = await request.json()
|
|
1007
|
+
file_path = data.get("path", "")
|
|
1008
|
+
limit = data.get("limit", 10)
|
|
1009
|
+
|
|
1010
|
+
if not file_path:
|
|
1011
|
+
return web.json_response(
|
|
1012
|
+
{
|
|
1013
|
+
"success": False,
|
|
1014
|
+
"error": "No path provided",
|
|
1015
|
+
"commits": [],
|
|
1016
|
+
},
|
|
1017
|
+
status=400,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
path = Path(file_path)
|
|
1021
|
+
if not path.exists():
|
|
1022
|
+
return web.json_response(
|
|
1023
|
+
{
|
|
1024
|
+
"success": False,
|
|
1025
|
+
"error": "File not found",
|
|
1026
|
+
"commits": [],
|
|
1027
|
+
},
|
|
1028
|
+
status=404,
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
# Get git log for file
|
|
1032
|
+
result = subprocess.run( # nosec B603 B607
|
|
1033
|
+
[
|
|
1034
|
+
"git",
|
|
1035
|
+
"log",
|
|
1036
|
+
f"-{limit}",
|
|
1037
|
+
"--pretty=format:%H|%an|%ar|%s",
|
|
1038
|
+
"--",
|
|
1039
|
+
str(path),
|
|
1040
|
+
],
|
|
1041
|
+
check=False,
|
|
1042
|
+
capture_output=True,
|
|
1043
|
+
text=True,
|
|
1044
|
+
cwd=str(path.parent),
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
commits = []
|
|
1048
|
+
if result.returncode == 0 and result.stdout:
|
|
1049
|
+
for line in result.stdout.strip().split("\n"):
|
|
1050
|
+
if line:
|
|
1051
|
+
parts = line.split("|", 3)
|
|
1052
|
+
if len(parts) == 4:
|
|
1053
|
+
commits.append(
|
|
1054
|
+
{
|
|
1055
|
+
"hash": parts[0][:7],
|
|
1056
|
+
"author": parts[1],
|
|
1057
|
+
"date": parts[2],
|
|
1058
|
+
"message": parts[3],
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
return web.json_response({"success": True, "commits": commits})
|
|
1063
|
+
except Exception as e:
|
|
1064
|
+
return web.json_response(
|
|
1065
|
+
{"success": False, "error": str(e), "commits": []}, status=500
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
# Git diff handler
|
|
1069
|
+
async def git_diff_handler(request: web.Request) -> web.Response:
|
|
1070
|
+
"""Get git diff for a file with optional commit selection."""
|
|
1071
|
+
import subprocess # nosec B404
|
|
1072
|
+
|
|
1073
|
+
try:
|
|
1074
|
+
file_path = request.query.get("path", "")
|
|
1075
|
+
commit_hash = request.query.get(
|
|
1076
|
+
"commit", ""
|
|
1077
|
+
) # Optional commit hash
|
|
1078
|
+
|
|
1079
|
+
if not file_path:
|
|
1080
|
+
return web.json_response(
|
|
1081
|
+
{
|
|
1082
|
+
"success": False,
|
|
1083
|
+
"error": "No path provided",
|
|
1084
|
+
"diff": "",
|
|
1085
|
+
"has_changes": False,
|
|
1086
|
+
},
|
|
1087
|
+
status=400,
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
path = Path(file_path)
|
|
1091
|
+
if not path.exists():
|
|
1092
|
+
return web.json_response(
|
|
1093
|
+
{
|
|
1094
|
+
"success": False,
|
|
1095
|
+
"error": "File not found",
|
|
1096
|
+
"diff": "",
|
|
1097
|
+
"has_changes": False,
|
|
1098
|
+
},
|
|
1099
|
+
status=404,
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
# Find git repository root
|
|
1103
|
+
git_root_result = subprocess.run( # nosec B603 B607
|
|
1104
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
1105
|
+
check=False,
|
|
1106
|
+
capture_output=True,
|
|
1107
|
+
text=True,
|
|
1108
|
+
cwd=str(path.parent),
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
if git_root_result.returncode != 0:
|
|
1112
|
+
# Not in a git repository
|
|
1113
|
+
return web.json_response(
|
|
1114
|
+
{
|
|
1115
|
+
"success": True,
|
|
1116
|
+
"diff": "",
|
|
1117
|
+
"has_changes": False,
|
|
1118
|
+
"tracked": False,
|
|
1119
|
+
"history": [],
|
|
1120
|
+
"has_uncommitted": False,
|
|
1121
|
+
}
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
git_root = Path(git_root_result.stdout.strip())
|
|
1125
|
+
|
|
1126
|
+
# Check if file is tracked by git
|
|
1127
|
+
ls_files_result = subprocess.run( # nosec B603 B607
|
|
1128
|
+
["git", "ls-files", "--error-unmatch", str(path)],
|
|
1129
|
+
check=False,
|
|
1130
|
+
capture_output=True,
|
|
1131
|
+
text=True,
|
|
1132
|
+
cwd=str(git_root),
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
if ls_files_result.returncode != 0:
|
|
1136
|
+
# File is not tracked by git
|
|
1137
|
+
return web.json_response(
|
|
1138
|
+
{
|
|
1139
|
+
"success": True,
|
|
1140
|
+
"diff": "",
|
|
1141
|
+
"has_changes": False,
|
|
1142
|
+
"tracked": False,
|
|
1143
|
+
"history": [],
|
|
1144
|
+
"has_uncommitted": False,
|
|
1145
|
+
}
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
# Get commit history for this file (last 5 commits)
|
|
1149
|
+
history_result = subprocess.run( # nosec B603 B607
|
|
1150
|
+
[
|
|
1151
|
+
"git",
|
|
1152
|
+
"log",
|
|
1153
|
+
"-5",
|
|
1154
|
+
"--pretty=format:%H|%s|%ar",
|
|
1155
|
+
"--",
|
|
1156
|
+
str(path),
|
|
1157
|
+
],
|
|
1158
|
+
check=False,
|
|
1159
|
+
capture_output=True,
|
|
1160
|
+
text=True,
|
|
1161
|
+
cwd=str(git_root),
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
history = []
|
|
1165
|
+
if history_result.returncode == 0 and history_result.stdout:
|
|
1166
|
+
for line in history_result.stdout.strip().split("\n"):
|
|
1167
|
+
if line:
|
|
1168
|
+
parts = line.split("|", 2)
|
|
1169
|
+
if len(parts) == 3:
|
|
1170
|
+
history.append(
|
|
1171
|
+
{
|
|
1172
|
+
"hash": parts[0][:7], # Short hash
|
|
1173
|
+
"full_hash": parts[0], # Full hash for API
|
|
1174
|
+
"message": parts[1],
|
|
1175
|
+
"time_ago": parts[2],
|
|
1176
|
+
}
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
# Check for uncommitted changes
|
|
1180
|
+
uncommitted_result = subprocess.run( # nosec B603 B607
|
|
1181
|
+
["git", "diff", "HEAD", str(path)],
|
|
1182
|
+
check=False,
|
|
1183
|
+
capture_output=True,
|
|
1184
|
+
text=True,
|
|
1185
|
+
cwd=str(git_root),
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
has_uncommitted = bool(uncommitted_result.stdout.strip())
|
|
1189
|
+
|
|
1190
|
+
# Get diff based on commit parameter
|
|
1191
|
+
if commit_hash:
|
|
1192
|
+
# Get diff for specific commit
|
|
1193
|
+
result = subprocess.run( # nosec B603 B607
|
|
1194
|
+
["git", "show", commit_hash, "--", str(path)],
|
|
1195
|
+
check=False,
|
|
1196
|
+
capture_output=True,
|
|
1197
|
+
text=True,
|
|
1198
|
+
cwd=str(git_root),
|
|
1199
|
+
)
|
|
1200
|
+
diff_output = result.stdout if result.returncode == 0 else ""
|
|
1201
|
+
has_changes = bool(diff_output.strip())
|
|
1202
|
+
else:
|
|
1203
|
+
# Get uncommitted diff (default behavior)
|
|
1204
|
+
diff_output = uncommitted_result.stdout
|
|
1205
|
+
has_changes = has_uncommitted
|
|
1206
|
+
|
|
1207
|
+
return web.json_response(
|
|
1208
|
+
{
|
|
1209
|
+
"success": True,
|
|
1210
|
+
"diff": diff_output,
|
|
1211
|
+
"has_changes": has_changes,
|
|
1212
|
+
"tracked": True,
|
|
1213
|
+
"history": history,
|
|
1214
|
+
"has_uncommitted": has_uncommitted,
|
|
1215
|
+
}
|
|
1216
|
+
)
|
|
1217
|
+
except Exception as e:
|
|
1218
|
+
return web.json_response(
|
|
1219
|
+
{
|
|
1220
|
+
"success": False,
|
|
1221
|
+
"error": str(e),
|
|
1222
|
+
"diff": "",
|
|
1223
|
+
"has_changes": False,
|
|
1224
|
+
"history": [],
|
|
1225
|
+
"has_uncommitted": False,
|
|
1226
|
+
},
|
|
1227
|
+
status=500,
|
|
1228
|
+
)
|
|
1229
|
+
|
|
528
1230
|
# Register routes
|
|
529
1231
|
self.app.router.add_get("/", dashboard_index)
|
|
1232
|
+
self.app.router.add_get("/favicon.svg", favicon_handler)
|
|
530
1233
|
self.app.router.add_get("/health", health_check)
|
|
531
1234
|
self.app.router.add_get("/version.json", version_handler)
|
|
532
1235
|
self.app.router.add_get("/api/config", config_handler)
|
|
533
1236
|
self.app.router.add_get("/api/working-directory", working_directory_handler)
|
|
534
|
-
self.app.router.add_get("/api/
|
|
1237
|
+
self.app.router.add_get("/api/files", api_files_handler)
|
|
1238
|
+
self.app.router.add_get("/api/file/read", api_file_read_handler)
|
|
1239
|
+
self.app.router.add_get("/api/file/diff", git_diff_handler)
|
|
535
1240
|
self.app.router.add_post("/api/events", api_events_handler)
|
|
536
1241
|
self.app.router.add_post("/api/file", api_file_handler)
|
|
1242
|
+
self.app.router.add_post("/api/git-history", git_history_handler)
|
|
537
1243
|
|
|
538
1244
|
# Monitor page routes
|
|
539
1245
|
self.app.router.add_get("/monitor", lambda r: monitor_page_handler(r))
|
|
@@ -546,12 +1252,43 @@ class UnifiedMonitorServer:
|
|
|
546
1252
|
"/monitor/events", lambda r: monitor_page_handler(r)
|
|
547
1253
|
)
|
|
548
1254
|
|
|
549
|
-
#
|
|
550
|
-
|
|
1255
|
+
# Serve Svelte _app assets (compiled JS/CSS)
|
|
1256
|
+
svelte_build_dir = static_dir / "svelte-build"
|
|
1257
|
+
if svelte_build_dir.exists():
|
|
1258
|
+
svelte_app_dir = svelte_build_dir / "_app"
|
|
1259
|
+
if svelte_app_dir.exists():
|
|
1260
|
+
# Serve _app assets with proper caching
|
|
1261
|
+
async def app_assets_handler(request):
|
|
1262
|
+
"""Serve Svelte _app assets."""
|
|
1263
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
1264
|
+
|
|
1265
|
+
rel_path = request.match_info["filepath"]
|
|
1266
|
+
file_path = svelte_app_dir / rel_path
|
|
1267
|
+
|
|
1268
|
+
if not file_path.exists() or not file_path.is_file():
|
|
1269
|
+
raise web.HTTPNotFound()
|
|
1270
|
+
|
|
1271
|
+
response = FileResponse(file_path)
|
|
1272
|
+
|
|
1273
|
+
# Add cache headers for immutable assets
|
|
1274
|
+
if "/immutable/" in str(rel_path):
|
|
1275
|
+
response.headers["Cache-Control"] = (
|
|
1276
|
+
"public, max-age=31536000, immutable"
|
|
1277
|
+
)
|
|
1278
|
+
else:
|
|
1279
|
+
response.headers["Cache-Control"] = (
|
|
1280
|
+
"no-cache, no-store, must-revalidate"
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
return response
|
|
1284
|
+
|
|
1285
|
+
self.app.router.add_get("/_app/{filepath:.*}", app_assets_handler)
|
|
1286
|
+
|
|
1287
|
+
# Legacy static files (for backward compatibility)
|
|
551
1288
|
if static_dir.exists():
|
|
552
1289
|
|
|
553
1290
|
async def static_handler(request):
|
|
554
|
-
"""Serve static files with cache-control headers for development."""
|
|
1291
|
+
"""Serve legacy static files with cache-control headers for development."""
|
|
555
1292
|
|
|
556
1293
|
from aiohttp.web_fileresponse import FileResponse
|
|
557
1294
|
|
|
@@ -576,10 +1313,13 @@ class UnifiedMonitorServer:
|
|
|
576
1313
|
|
|
577
1314
|
self.app.router.add_get("/static/{filepath:.*}", static_handler)
|
|
578
1315
|
|
|
579
|
-
#
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1316
|
+
# Log dashboard availability
|
|
1317
|
+
if svelte_build_dir.exists():
|
|
1318
|
+
self.logger.info(
|
|
1319
|
+
f"✅ Svelte dashboard available at / (root) (build: {svelte_build_dir})"
|
|
1320
|
+
)
|
|
1321
|
+
else:
|
|
1322
|
+
self.logger.warning(f"Svelte build not found at: {svelte_build_dir}")
|
|
583
1323
|
|
|
584
1324
|
self.logger.info("HTTP routes registered successfully")
|
|
585
1325
|
|
|
@@ -691,11 +1431,37 @@ class UnifiedMonitorServer:
|
|
|
691
1431
|
async def _cleanup_async(self):
|
|
692
1432
|
"""Cleanup async resources."""
|
|
693
1433
|
try:
|
|
1434
|
+
# Stop file observer if running
|
|
1435
|
+
# STABILITY FIX: Ensure watcher is stopped and verify observer termination
|
|
1436
|
+
if self.file_observer:
|
|
1437
|
+
try:
|
|
1438
|
+
# Stop the watcher first to cancel pending timers
|
|
1439
|
+
if self.file_watcher:
|
|
1440
|
+
self.file_watcher.stop()
|
|
1441
|
+
|
|
1442
|
+
# Stop the observer
|
|
1443
|
+
self.file_observer.stop()
|
|
1444
|
+
self.file_observer.join(timeout=2)
|
|
1445
|
+
|
|
1446
|
+
# Verify observer actually stopped
|
|
1447
|
+
if self.file_observer.is_alive():
|
|
1448
|
+
self.logger.warning("File observer did not stop cleanly")
|
|
1449
|
+
|
|
1450
|
+
self.logger.debug("File observer stopped")
|
|
1451
|
+
except Exception as e:
|
|
1452
|
+
self.logger.debug(f"Error stopping file observer: {e}")
|
|
1453
|
+
finally:
|
|
1454
|
+
self.file_observer = None
|
|
1455
|
+
self.file_watcher = None
|
|
1456
|
+
|
|
694
1457
|
# Cancel heartbeat task if running
|
|
1458
|
+
# STABILITY FIX: Add timeout to prevent infinite wait on cancellation
|
|
695
1459
|
if self.heartbeat_task and not self.heartbeat_task.done():
|
|
696
1460
|
self.heartbeat_task.cancel()
|
|
697
|
-
|
|
698
|
-
await self.heartbeat_task
|
|
1461
|
+
try:
|
|
1462
|
+
await asyncio.wait_for(self.heartbeat_task, timeout=2.0)
|
|
1463
|
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
|
1464
|
+
pass
|
|
699
1465
|
self.logger.debug("Heartbeat task cancelled")
|
|
700
1466
|
|
|
701
1467
|
# Close the Socket.IO server first to stop accepting new connections
|
|
@@ -793,7 +1559,7 @@ class UnifiedMonitorServer:
|
|
|
793
1559
|
gather = asyncio.gather(*tasks_to_cancel, return_exceptions=True)
|
|
794
1560
|
try:
|
|
795
1561
|
loop.run_until_complete(gather)
|
|
796
|
-
except Exception:
|
|
1562
|
+
except Exception: # nosec B110
|
|
797
1563
|
# Some tasks might fail to cancel, that's ok
|
|
798
1564
|
pass
|
|
799
1565
|
|