claude-mpm 4.1.6__py3-none-any.whl → 4.24.0__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 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +20 -5
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_DOCUMENTATION.md +53 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/BASE_OPS.md +219 -0
- claude_mpm/agents/BASE_PM.md +431 -214
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +787 -0
- claude_mpm/agents/BASE_QA.md +167 -0
- claude_mpm/agents/BASE_RESEARCH.md +53 -0
- claude_mpm/agents/MEMORY.md +3 -0
- claude_mpm/agents/OUTPUT_STYLE.md +335 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1159 -0
- claude_mpm/agents/WORKFLOW.md +355 -187
- claude_mpm/agents/agent_loader.py +40 -10
- claude_mpm/agents/agent_loader_integration.py +3 -2
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/async_agent_loader.py +3 -3
- claude_mpm/agents/base_agent_loader.py +11 -9
- claude_mpm/agents/frontmatter_validator.py +291 -251
- claude_mpm/agents/system_agent_config.py +3 -2
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +267 -18
- claude_mpm/agents/templates/agentic-coder-optimizer.json +248 -0
- claude_mpm/agents/templates/api_qa.json +16 -4
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +235 -0
- claude_mpm/agents/templates/code_analyzer.json +25 -9
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +307 -0
- claude_mpm/agents/templates/data_engineer.json +87 -14
- claude_mpm/agents/templates/documentation.json +76 -13
- claude_mpm/agents/templates/engineer.json +44 -10
- claude_mpm/agents/templates/gcp_ops_agent.json +253 -0
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +5 -2
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/javascript_engineer_agent.json +380 -0
- claude_mpm/agents/templates/local_ops_agent.json +1840 -0
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250901_010124_142.md +400 -0
- claude_mpm/agents/templates/memory_manager.json +6 -3
- claude_mpm/agents/templates/nextjs_engineer.json +285 -0
- claude_mpm/agents/templates/ops.json +27 -8
- claude_mpm/agents/templates/php-engineer.json +287 -0
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +262 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +19 -5
- claude_mpm/agents/templates/prompt-engineer.json +737 -0
- claude_mpm/agents/templates/python_engineer.json +387 -0
- claude_mpm/agents/templates/qa.json +26 -6
- claude_mpm/agents/templates/react_engineer.json +239 -0
- claude_mpm/agents/templates/refactoring_engineer.json +15 -5
- claude_mpm/agents/templates/research.json +47 -22
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +280 -0
- claude_mpm/agents/templates/rust_engineer.json +275 -0
- claude_mpm/agents/templates/security.json +59 -10
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/tauri_engineer.json +274 -0
- claude_mpm/agents/templates/ticketing.json +16 -7
- claude_mpm/agents/templates/typescript_engineer.json +285 -0
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +164 -33
- claude_mpm/agents/templates/version_control.json +16 -4
- claude_mpm/agents/templates/web_qa.json +243 -21
- claude_mpm/agents/templates/web_ui.json +18 -5
- claude_mpm/cli/__init__.py +38 -363
- claude_mpm/cli/commands/__init__.py +8 -0
- claude_mpm/cli/commands/agent_manager.py +675 -20
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +722 -150
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +10 -6
- claude_mpm/cli/commands/analyze.py +553 -0
- claude_mpm/cli/commands/analyze_code.py +528 -0
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/cleanup.py +12 -12
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +488 -884
- claude_mpm/cli/commands/configure_agent_display.py +261 -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 +167 -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 +43 -7
- claude_mpm/cli/commands/info.py +3 -4
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/mcp.py +17 -10
- claude_mpm/cli/commands/mcp_command_router.py +11 -0
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +101 -32
- claude_mpm/cli/commands/mcp_pipx_config.py +2 -2
- claude_mpm/cli/commands/mcp_setup_external.py +868 -0
- claude_mpm/cli/commands/memory.py +55 -21
- claude_mpm/cli/commands/monitor.py +168 -617
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +525 -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/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +442 -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/run.py +169 -42
- claude_mpm/cli/commands/search.py +458 -0
- claude_mpm/cli/commands/skills.py +488 -0
- 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 +204 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +21 -0
- claude_mpm/cli/interactive/agent_wizard.py +962 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parser.py +79 -2
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +161 -1
- claude_mpm/cli/parsers/agents_parser.py +116 -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 +245 -0
- claude_mpm/cli/parsers/base_parser.py +187 -3
- claude_mpm/cli/parsers/configure_parser.py +34 -15
- 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 +15 -0
- claude_mpm/cli/parsers/monitor_parser.py +12 -2
- claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/shared/argument_patterns.py +20 -13
- claude_mpm/cli/shared/base_command.py +2 -2
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +562 -0
- claude_mpm/cli/startup_logging.py +179 -13
- claude_mpm/cli/utils.py +53 -2
- claude_mpm/commands/__init__.py +14 -0
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +122 -0
- claude_mpm/commands/mpm-auto-configure.md +269 -0
- claude_mpm/commands/mpm-config.md +141 -0
- claude_mpm/commands/mpm-doctor.md +24 -0
- claude_mpm/commands/mpm-help.md +290 -0
- claude_mpm/commands/mpm-init.md +521 -0
- claude_mpm/commands/mpm-monitor.md +409 -0
- claude_mpm/commands/mpm-organize.md +295 -0
- claude_mpm/commands/mpm-resume.md +372 -0
- claude_mpm/commands/mpm-status.md +75 -0
- claude_mpm/commands/mpm-tickets.md +151 -0
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +21 -0
- claude_mpm/config/agent_config.py +4 -4
- claude_mpm/config/experimental_features.py +7 -7
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/config/paths.py +3 -2
- claude_mpm/config/socketio_config.py +36 -7
- claude_mpm/constants.py +27 -1
- claude_mpm/core/__init__.py +53 -17
- claude_mpm/core/agent_name_normalizer.py +3 -2
- claude_mpm/core/agent_registry.py +2 -2
- claude_mpm/core/agent_session_manager.py +10 -10
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/base_service.py +33 -23
- claude_mpm/core/cache.py +9 -9
- claude_mpm/core/claude_runner.py +19 -8
- claude_mpm/core/config.py +103 -8
- claude_mpm/core/config_aliases.py +7 -6
- claude_mpm/core/constants.py +65 -0
- claude_mpm/core/container.py +11 -5
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/error_handler.py +623 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/file_utils.py +764 -0
- claude_mpm/core/framework/__init__.py +38 -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 +288 -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 +210 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -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 +323 -1491
- claude_mpm/core/hook_manager.py +8 -6
- claude_mpm/core/injectable_service.py +11 -8
- claude_mpm/core/instruction_reinforcement_hook.py +267 -0
- claude_mpm/core/interactive_session.py +55 -8
- claude_mpm/core/interfaces.py +56 -1
- claude_mpm/core/lazy.py +3 -3
- claude_mpm/core/log_manager.py +100 -28
- claude_mpm/core/logger.py +19 -14
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/logging_utils.py +520 -0
- claude_mpm/core/oneshot_session.py +51 -7
- claude_mpm/core/optimized_agent_loader.py +9 -9
- claude_mpm/core/optimized_startup.py +1 -1
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/pm_hook_interceptor.py +118 -15
- claude_mpm/core/service_registry.py +7 -3
- claude_mpm/core/session_manager.py +14 -12
- claude_mpm/core/shared/config_loader.py +1 -1
- claude_mpm/core/socketio_pool.py +15 -15
- claude_mpm/core/tool_access_control.py +3 -2
- claude_mpm/core/types.py +4 -11
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/core/unified_agent_registry.py +116 -12
- claude_mpm/core/unified_config.py +6 -6
- claude_mpm/core/unified_paths.py +23 -20
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/__init__.py +12 -0
- claude_mpm/dashboard/analysis_runner.py +455 -0
- claude_mpm/dashboard/api/simple_directory.py +261 -0
- claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
- claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
- claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
- claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
- claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
- claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
- claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
- claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
- claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
- claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
- claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
- claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
- claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
- claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
- claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
- claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
- claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
- claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
- claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
- claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/unified-data-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/connection-manager.js +536 -0
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/built/react/events.js +30 -0
- claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
- claude_mpm/dashboard/static/built/shared/logger.js +385 -0
- claude_mpm/dashboard/static/built/shared/page-structure.js +249 -0
- claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/css/activity.css +1958 -0
- claude_mpm/dashboard/static/css/dashboard.css +1413 -72
- claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/react/events.js +30 -0
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/events.html +607 -0
- claude_mpm/dashboard/static/index.html +635 -0
- claude_mpm/dashboard/static/js/components/activity-tree.js +1871 -0
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
- claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
- claude_mpm/dashboard/static/js/components/build-tracker.js +23 -13
- claude_mpm/dashboard/static/js/components/code-simple.js +857 -0
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -107
- claude_mpm/dashboard/static/js/components/event-viewer.js +98 -11
- claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
- claude_mpm/dashboard/static/js/components/file-viewer.js +580 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +68 -205
- claude_mpm/dashboard/static/js/components/session-manager.js +46 -10
- claude_mpm/dashboard/static/js/components/socket-manager.js +16 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +359 -40
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +1824 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +61 -10
- claude_mpm/dashboard/static/js/connection-manager.js +1 -1
- claude_mpm/dashboard/static/js/dashboard.js +523 -622
- claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/js/shared/logger.js +385 -0
- claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/js/socket-client.js +549 -62
- claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/legacy/activity.html +736 -0
- claude_mpm/dashboard/static/legacy/agents.html +786 -0
- claude_mpm/dashboard/static/legacy/files.html +747 -0
- claude_mpm/dashboard/static/legacy/tools.html +831 -0
- claude_mpm/dashboard/static/monitors.html +431 -0
- claude_mpm/dashboard/static/production/events.html +659 -0
- claude_mpm/dashboard/static/production/main.html +698 -0
- claude_mpm/dashboard/static/production/monitors.html +483 -0
- claude_mpm/dashboard/static/socket.io.min.js +7 -0
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
- claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
- claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
- claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
- claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
- claude_mpm/dashboard/static/test-archive/test_debug.html +25 -0
- claude_mpm/dashboard/templates/code_simple.html +153 -0
- claude_mpm/dashboard/templates/index.html +267 -9
- claude_mpm/experimental/__init__.py +10 -0
- claude_mpm/experimental/cli_enhancements.py +4 -2
- claude_mpm/generators/agent_profile_generator.py +5 -3
- claude_mpm/hooks/__init__.py +37 -1
- claude_mpm/hooks/base_hook.py +5 -4
- claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +21 -18
- claude_mpm/hooks/claude_hooks/hook_handler.py +209 -25
- claude_mpm/hooks/claude_hooks/installer.py +783 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
- claude_mpm/hooks/claude_hooks/response_tracking.py +57 -17
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +64 -49
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +140 -76
- claude_mpm/hooks/claude_hooks/services/state_manager.py +11 -9
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -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 +183 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- 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 +8 -5
- claude_mpm/hooks/validation_hooks.py +3 -3
- claude_mpm/init.py +23 -4
- claude_mpm/models/agent_session.py +8 -6
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/schemas/__init__.py +12 -0
- claude_mpm/scripts/claude-hook-handler.sh +187 -0
- claude_mpm/scripts/launch_monitor.py +85 -0
- claude_mpm/scripts/mcp_server.py +3 -5
- claude_mpm/scripts/mpm_doctor.py +3 -2
- claude_mpm/scripts/socketio_daemon.py +156 -396
- claude_mpm/services/__init__.py +144 -160
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/agent_builder.py +13 -11
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_deployment.py +38 -15
- claude_mpm/services/agents/deployment/agent_discovery_service.py +125 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_format_converter.py +56 -12
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +4 -2
- claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +5 -6
- claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_template_builder.py +722 -37
- claude_mpm/services/agents/deployment/agent_validator.py +31 -7
- claude_mpm/services/agents/deployment/agent_version_manager.py +9 -1
- claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/deployment_config_loader.py +131 -7
- claude_mpm/services/agents/deployment/deployment_type_detector.py +10 -14
- claude_mpm/services/agents/deployment/deployment_wrapper.py +58 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +360 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +134 -38
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +8 -7
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +7 -5
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +64 -44
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/loading/agent_profile_loader.py +10 -9
- claude_mpm/services/agents/loading/base_agent_manager.py +16 -6
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -2
- claude_mpm/services/agents/local_template_manager.py +744 -0
- claude_mpm/services/agents/management/agent_capabilities_generator.py +3 -2
- claude_mpm/services/agents/management/agent_management_service.py +5 -5
- claude_mpm/services/agents/memory/agent_memory_manager.py +32 -29
- claude_mpm/services/agents/memory/content_manager.py +17 -9
- claude_mpm/services/agents/memory/memory_categorization_service.py +4 -2
- claude_mpm/services/agents/memory/memory_file_service.py +32 -6
- claude_mpm/services/agents/memory/memory_format_service.py +7 -7
- claude_mpm/services/agents/memory/memory_limits_service.py +4 -2
- claude_mpm/services/agents/memory/template_generator.py +3 -3
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +615 -0
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +3 -3
- claude_mpm/services/agents/registry/modification_tracker.py +30 -19
- claude_mpm/services/async_session_logger.py +141 -98
- claude_mpm/services/claude_session_logger.py +82 -74
- claude_mpm/services/cli/agent_cleanup_service.py +6 -5
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_listing_service.py +5 -5
- claude_mpm/services/cli/agent_validation_service.py +6 -5
- claude_mpm/services/cli/memory_crud_service.py +12 -7
- claude_mpm/services/cli/memory_output_formatter.py +2 -2
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +104 -13
- 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 +13 -21
- claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
- claude_mpm/services/command_deployment_service.py +17 -9
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/base.py +26 -11
- claude_mpm/services/core/cache_manager.py +1 -3
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -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/stability.py +260 -0
- claude_mpm/services/core/interfaces.py +56 -1
- claude_mpm/services/core/memory_manager.py +92 -47
- claude_mpm/services/core/models/__init__.py +79 -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 +37 -18
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/__init__.py +4 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +30 -32
- claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +28 -27
- claude_mpm/services/diagnostics/checks/configuration_check.py +26 -25
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +165 -60
- claude_mpm/services/diagnostics/checks/instructions_check.py +22 -24
- claude_mpm/services/diagnostics/checks/mcp_check.py +57 -43
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +1066 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +24 -23
- claude_mpm/services/diagnostics/checks/startup_log_check.py +14 -11
- claude_mpm/services/diagnostics/diagnostic_runner.py +22 -13
- claude_mpm/services/diagnostics/doctor_reporter.py +275 -47
- claude_mpm/services/diagnostics/models.py +37 -21
- claude_mpm/services/event_aggregator.py +5 -3
- claude_mpm/services/event_bus/direct_relay.py +152 -13
- claude_mpm/services/event_bus/event_bus.py +51 -9
- claude_mpm/services/event_bus/relay.py +33 -14
- claude_mpm/services/events/consumers/dead_letter.py +7 -5
- claude_mpm/services/events/core.py +5 -6
- claude_mpm/services/events/producers/hook.py +6 -6
- claude_mpm/services/events/producers/system.py +8 -8
- claude_mpm/services/exceptions.py +5 -5
- claude_mpm/services/framework_claude_md_generator/__init__.py +1 -1
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +5 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +2 -2
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +1 -1
- claude_mpm/services/hook_installer_service.py +506 -0
- claude_mpm/services/hook_service.py +5 -6
- claude_mpm/services/infrastructure/context_preservation.py +13 -11
- claude_mpm/services/infrastructure/daemon_manager.py +9 -9
- claude_mpm/services/infrastructure/logging.py +2 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +12 -12
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +8 -7
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/monitoring.py +12 -12
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +165 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -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 +430 -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 +1612 -0
- claude_mpm/services/mcp_gateway/__init__.py +97 -93
- claude_mpm/services/mcp_gateway/auto_configure.py +43 -38
- claude_mpm/services/mcp_gateway/config/config_loader.py +3 -3
- claude_mpm/services/mcp_gateway/config/configuration.py +23 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +20 -33
- claude_mpm/services/mcp_gateway/core/process_pool.py +585 -31
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +2 -2
- claude_mpm/services/mcp_gateway/core/startup_verification.py +3 -3
- claude_mpm/services/mcp_gateway/main.py +90 -15
- claude_mpm/services/mcp_gateway/registry/service_registry.py +4 -2
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +12 -9
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
- claude_mpm/services/mcp_gateway/server/stdio_server.py +9 -15
- claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +10 -9
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +654 -0
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +36 -34
- claude_mpm/services/mcp_gateway/tools/hello_world.py +8 -8
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +551 -0
- claude_mpm/services/mcp_gateway/utils/__init__.py +14 -0
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +160 -0
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +170 -0
- claude_mpm/services/mcp_service_verifier.py +729 -0
- claude_mpm/services/memory/builder.py +9 -8
- claude_mpm/services/memory/cache/shared_prompt_cache.py +2 -1
- claude_mpm/services/memory/cache/simple_cache.py +2 -2
- claude_mpm/services/memory/failure_tracker.py +578 -0
- claude_mpm/services/memory/indexed_memory.py +8 -8
- claude_mpm/services/memory/optimizer.py +8 -9
- claude_mpm/services/memory/router.py +3 -3
- claude_mpm/services/memory_hook_service.py +165 -4
- 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 +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +671 -0
- claude_mpm/services/monitor/daemon_manager.py +963 -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 +724 -0
- claude_mpm/services/monitor/server.py +817 -0
- claude_mpm/services/monitor_build_service.py +2 -2
- claude_mpm/services/native_agent_converter.py +356 -0
- claude_mpm/services/orphan_detection.py +786 -0
- claude_mpm/services/port_manager.py +2 -2
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/analyzer.py +3 -3
- claude_mpm/services/project/architecture_analyzer.py +6 -6
- claude_mpm/services/project/archive_manager.py +1045 -0
- claude_mpm/services/project/dependency_analyzer.py +8 -8
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/documentation_manager.py +553 -0
- claude_mpm/services/project/enhanced_analyzer.py +572 -0
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +7 -10
- claude_mpm/services/project/project_organizer.py +1005 -0
- claude_mpm/services/project/registry.py +13 -7
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/project_port_allocator.py +596 -0
- claude_mpm/services/response_tracker.py +21 -10
- claude_mpm/services/runner_configuration_service.py +1 -0
- claude_mpm/services/self_upgrade_service.py +500 -0
- claude_mpm/services/session_management_service.py +7 -5
- claude_mpm/services/session_manager.py +380 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/config_service_base.py +17 -14
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/client_proxy.py +60 -5
- claude_mpm/services/socketio/dashboard_server.py +361 -0
- claude_mpm/services/socketio/event_normalizer.py +74 -6
- claude_mpm/services/socketio/handlers/__init__.py +5 -0
- claude_mpm/services/socketio/handlers/base.py +2 -2
- claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
- claude_mpm/services/socketio/handlers/connection.py +21 -40
- claude_mpm/services/socketio/handlers/connection_handler.py +16 -28
- claude_mpm/services/socketio/handlers/file.py +46 -10
- claude_mpm/services/socketio/handlers/git.py +8 -8
- claude_mpm/services/socketio/handlers/hook.py +29 -17
- claude_mpm/services/socketio/handlers/registry.py +4 -0
- claude_mpm/services/socketio/monitor_client.py +364 -0
- claude_mpm/services/socketio/server/broadcaster.py +9 -7
- claude_mpm/services/socketio/server/connection_manager.py +131 -68
- claude_mpm/services/socketio/server/core.py +275 -22
- claude_mpm/services/socketio/server/eventbus_integration.py +20 -14
- claude_mpm/services/socketio/server/main.py +99 -29
- claude_mpm/services/socketio_client_manager.py +4 -4
- claude_mpm/services/subprocess_launcher_service.py +19 -15
- claude_mpm/services/system_instructions_service.py +2 -2
- claude_mpm/services/ticket_services/formatter_service.py +1 -1
- claude_mpm/services/ticket_services/validation_service.py +5 -5
- 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 +903 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +746 -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 +475 -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 +470 -0
- claude_mpm/services/utility_service.py +6 -3
- claude_mpm/services/version_control/branch_strategy.py +2 -2
- claude_mpm/services/version_control/conflict_resolution.py +8 -4
- claude_mpm/services/version_control/git_operations.py +26 -24
- claude_mpm/services/version_control/semantic_versioning.py +14 -14
- claude_mpm/services/version_control/version_parser.py +14 -11
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -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/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/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/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/database-migration.md +199 -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/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/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/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/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/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/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/refactoring-patterns.md +180 -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 +327 -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/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/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/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/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -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/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 +310 -0
- claude_mpm/skills/skills_registry.py +348 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/storage/state_storage.py +31 -31
- 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/agent_dependency_loader.py +108 -27
- claude_mpm/utils/common.py +544 -0
- claude_mpm/utils/config_manager.py +12 -6
- claude_mpm/utils/database_connector.py +298 -0
- claude_mpm/utils/dependency_cache.py +2 -2
- claude_mpm/utils/dependency_strategies.py +15 -10
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/environment_context.py +4 -3
- claude_mpm/utils/error_handler.py +5 -3
- claude_mpm/utils/file_utils.py +13 -14
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/log_cleanup.py +627 -0
- claude_mpm/utils/path_operations.py +7 -4
- claude_mpm/utils/robust_installer.py +133 -24
- claude_mpm/utils/session_logging.py +2 -2
- claude_mpm/utils/subprocess_utils.py +9 -8
- claude_mpm/validation/agent_validator.py +6 -6
- claude_mpm/validation/frontmatter_validator.py +6 -6
- claude_mpm-4.24.0.dist-info/METADATA +675 -0
- claude_mpm-4.24.0.dist-info/RECORD +1018 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.24.0.dist-info}/entry_points.txt +1 -0
- claude_mpm/agents/INSTRUCTIONS.md +0 -237
- claude_mpm/agents/schema/agent_schema.json +0 -314
- claude_mpm/agents/templates/agent-manager.md +0 -304
- claude_mpm/cli/commands/configure_tui.py +0 -1921
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1040
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/cli/dashboard_launcher.py +0 -424
- claude_mpm/services/cli/socketio_manager.py +0 -498
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +0 -286
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- claude_mpm-4.1.6.dist-info/METADATA +0 -325
- claude_mpm-4.1.6.dist-info/RECORD +0 -550
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.24.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.24.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.24.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Daemon Manager Service
|
|
3
|
+
==============================
|
|
4
|
+
|
|
5
|
+
WHY: This service consolidates ALL daemon lifecycle operations into a single place,
|
|
6
|
+
eliminating duplicate code and race conditions from having daemon management logic
|
|
7
|
+
scattered across multiple files.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Single source of truth for all daemon operations
|
|
11
|
+
- Robust port cleanup with retry logic
|
|
12
|
+
- Thread-safe operations with proper locking
|
|
13
|
+
- Comprehensive error handling and recovery
|
|
14
|
+
- Supports both foreground and background/daemon modes
|
|
15
|
+
- Manages PID files, port conflicts, and process lifecycle
|
|
16
|
+
|
|
17
|
+
This replaces duplicate logic that was in:
|
|
18
|
+
- UnifiedMonitorDaemon._cleanup_port_conflicts()
|
|
19
|
+
- UnifiedDashboardManager._cleanup_port_conflicts()
|
|
20
|
+
- Various daemon startup/stop logic spread across files
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import signal
|
|
25
|
+
import socket
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import threading
|
|
30
|
+
import time
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Optional, Tuple
|
|
33
|
+
|
|
34
|
+
from ...core.enums import OperationResult
|
|
35
|
+
from ...core.logging_config import get_logger
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DaemonManager:
|
|
39
|
+
"""Centralized manager for all daemon lifecycle operations.
|
|
40
|
+
|
|
41
|
+
This is the SINGLE source of truth for:
|
|
42
|
+
- Port conflict resolution
|
|
43
|
+
- Process cleanup
|
|
44
|
+
- Daemon startup/stop
|
|
45
|
+
- PID file management
|
|
46
|
+
- Service detection
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# Class-level lock for thread safety
|
|
50
|
+
_lock = threading.Lock()
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
port: int = 8765,
|
|
55
|
+
host: str = "localhost",
|
|
56
|
+
pid_file: Optional[str] = None,
|
|
57
|
+
log_file: Optional[str] = None,
|
|
58
|
+
):
|
|
59
|
+
"""Initialize the daemon manager.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
port: Port number for the daemon
|
|
63
|
+
host: Host to bind to
|
|
64
|
+
pid_file: Path to PID file (uses default if None)
|
|
65
|
+
log_file: Path to log file for daemon mode
|
|
66
|
+
"""
|
|
67
|
+
self.port = port
|
|
68
|
+
self.host = host
|
|
69
|
+
self.logger = get_logger(__name__)
|
|
70
|
+
|
|
71
|
+
# Set up paths
|
|
72
|
+
if pid_file:
|
|
73
|
+
self.pid_file = Path(pid_file)
|
|
74
|
+
else:
|
|
75
|
+
self.pid_file = self._get_default_pid_file()
|
|
76
|
+
|
|
77
|
+
self.log_file = Path(log_file) if log_file else self._get_default_log_file()
|
|
78
|
+
|
|
79
|
+
# Startup status communication
|
|
80
|
+
self.startup_status_file = None
|
|
81
|
+
|
|
82
|
+
def _get_default_pid_file(self) -> Path:
|
|
83
|
+
"""Get default PID file path."""
|
|
84
|
+
project_root = Path.cwd()
|
|
85
|
+
claude_mpm_dir = project_root / ".claude-mpm"
|
|
86
|
+
claude_mpm_dir.mkdir(exist_ok=True)
|
|
87
|
+
return claude_mpm_dir / "monitor-daemon.pid"
|
|
88
|
+
|
|
89
|
+
def _get_default_log_file(self) -> Path:
|
|
90
|
+
"""Get default log file path."""
|
|
91
|
+
project_root = Path.cwd()
|
|
92
|
+
claude_mpm_dir = project_root / ".claude-mpm"
|
|
93
|
+
claude_mpm_dir.mkdir(exist_ok=True)
|
|
94
|
+
return claude_mpm_dir / "monitor-daemon.log"
|
|
95
|
+
|
|
96
|
+
def cleanup_port_conflicts(self, max_retries: int = 3) -> bool:
|
|
97
|
+
"""Clean up any processes using the daemon port.
|
|
98
|
+
|
|
99
|
+
This is the SINGLE implementation for port cleanup, replacing
|
|
100
|
+
duplicate logic in multiple files.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
max_retries: Maximum number of cleanup attempts
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if port is available after cleanup, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
with self._lock:
|
|
109
|
+
for attempt in range(max_retries):
|
|
110
|
+
if attempt > 0:
|
|
111
|
+
self.logger.info(
|
|
112
|
+
f"Port cleanup attempt {attempt + 1}/{max_retries}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# First check if port is actually in use
|
|
116
|
+
if self._is_port_available():
|
|
117
|
+
self.logger.debug(f"Port {self.port} is available")
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
self.logger.info(f"Port {self.port} is in use, attempting cleanup")
|
|
121
|
+
|
|
122
|
+
# Try to find and kill processes using the port
|
|
123
|
+
if self._kill_processes_on_port():
|
|
124
|
+
# Wait for port to be released
|
|
125
|
+
time.sleep(2 if attempt == 0 else 3)
|
|
126
|
+
|
|
127
|
+
# Verify port is now free
|
|
128
|
+
if self._is_port_available():
|
|
129
|
+
self.logger.info(f"Port {self.port} successfully cleaned up")
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
if attempt < max_retries - 1:
|
|
133
|
+
# Wait longer between attempts
|
|
134
|
+
time.sleep(3)
|
|
135
|
+
|
|
136
|
+
self.logger.error(
|
|
137
|
+
f"Failed to clean up port {self.port} after {max_retries} attempts"
|
|
138
|
+
)
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def _is_port_available(self) -> bool:
|
|
142
|
+
"""Check if the port is available for binding.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if port is available, False otherwise
|
|
146
|
+
"""
|
|
147
|
+
# Try to bind to the port using the same method as the actual server
|
|
148
|
+
# We only need to check if we can bind to at least one address family
|
|
149
|
+
try:
|
|
150
|
+
# Try IPv4 first (most common)
|
|
151
|
+
test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
152
|
+
test_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
153
|
+
|
|
154
|
+
# Use 127.0.0.1 for localhost to match what the server does
|
|
155
|
+
bind_host = "127.0.0.1" if self.host == "localhost" else self.host
|
|
156
|
+
test_sock.bind((bind_host, self.port))
|
|
157
|
+
test_sock.close()
|
|
158
|
+
return True
|
|
159
|
+
except OSError:
|
|
160
|
+
# IPv4 failed, try IPv6
|
|
161
|
+
try:
|
|
162
|
+
test_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
|
163
|
+
test_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
164
|
+
test_sock.bind(("::1", self.port))
|
|
165
|
+
test_sock.close()
|
|
166
|
+
return True
|
|
167
|
+
except Exception:
|
|
168
|
+
# Both IPv4 and IPv6 failed - port is in use
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
def _kill_processes_on_port(self) -> bool:
|
|
172
|
+
"""Kill processes using the daemon port.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
True if processes were killed or none found, False on error
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
# Try using lsof first (most reliable)
|
|
179
|
+
if self._kill_using_lsof():
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
# Fallback to checking our known PID file
|
|
183
|
+
if self._kill_using_pid_file():
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
# Try to identify claude-mpm processes
|
|
187
|
+
return bool(self._kill_claude_mpm_processes())
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.error(f"Error killing processes on port: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
def _kill_using_lsof(self) -> bool:
|
|
194
|
+
"""Kill processes using lsof to find them.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
True if successful or lsof not available, False on error
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
# Find processes using the port
|
|
201
|
+
result = subprocess.run(
|
|
202
|
+
["lsof", "-ti", f":{self.port}"],
|
|
203
|
+
capture_output=True,
|
|
204
|
+
text=True,
|
|
205
|
+
check=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
209
|
+
self.logger.debug(f"No processes found using port {self.port}")
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
pids = result.stdout.strip().split("\n")
|
|
213
|
+
self.logger.info(f"Found processes using port {self.port}: {pids}")
|
|
214
|
+
|
|
215
|
+
# Kill each process
|
|
216
|
+
for pid_str in pids:
|
|
217
|
+
try:
|
|
218
|
+
pid = int(pid_str.strip())
|
|
219
|
+
|
|
220
|
+
# Check if it's a Python/Claude process
|
|
221
|
+
process_info = subprocess.run(
|
|
222
|
+
["ps", "-p", str(pid), "-o", "comm="],
|
|
223
|
+
capture_output=True,
|
|
224
|
+
text=True,
|
|
225
|
+
check=False,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Get full command to check if it's our monitor process
|
|
229
|
+
cmd_info = subprocess.run(
|
|
230
|
+
["ps", "-p", str(pid), "-o", "command="],
|
|
231
|
+
capture_output=True,
|
|
232
|
+
text=True,
|
|
233
|
+
check=False,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if cmd_info.returncode != 0:
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
full_command = cmd_info.stdout.strip().lower()
|
|
240
|
+
process_name = process_info.stdout.strip().lower()
|
|
241
|
+
|
|
242
|
+
# Check if this is our monitor/socketio process specifically
|
|
243
|
+
# Look for monitor, socketio, dashboard, or our specific port
|
|
244
|
+
is_monitor = any(
|
|
245
|
+
[
|
|
246
|
+
"monitor" in full_command,
|
|
247
|
+
"socketio" in full_command,
|
|
248
|
+
"dashboard" in full_command,
|
|
249
|
+
f"port={self.port}" in full_command,
|
|
250
|
+
f":{self.port}" in full_command,
|
|
251
|
+
"unified_monitor" in full_command,
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if is_monitor and "python" in process_name:
|
|
256
|
+
self.logger.info(
|
|
257
|
+
f"Killing monitor process {pid}: {full_command[:100]}"
|
|
258
|
+
)
|
|
259
|
+
os.kill(pid, signal.SIGTERM)
|
|
260
|
+
|
|
261
|
+
# Wait briefly for graceful shutdown
|
|
262
|
+
time.sleep(1)
|
|
263
|
+
|
|
264
|
+
# Check if still alive and force kill if needed
|
|
265
|
+
try:
|
|
266
|
+
os.kill(pid, 0) # Check if process exists
|
|
267
|
+
self.logger.warning(
|
|
268
|
+
f"Process {pid} didn't terminate, force killing"
|
|
269
|
+
)
|
|
270
|
+
os.kill(pid, signal.SIGKILL)
|
|
271
|
+
time.sleep(0.5)
|
|
272
|
+
except ProcessLookupError:
|
|
273
|
+
pass # Process already dead
|
|
274
|
+
else:
|
|
275
|
+
# Not a monitor process - log but don't fail
|
|
276
|
+
self.logger.info(
|
|
277
|
+
f"Skipping non-monitor process {pid} ({process_name})"
|
|
278
|
+
)
|
|
279
|
+
# Continue to next PID - don't return False
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
except (ValueError, ProcessLookupError) as e:
|
|
283
|
+
self.logger.debug(f"Error handling PID {pid_str}: {e}")
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
except FileNotFoundError:
|
|
289
|
+
# lsof not available
|
|
290
|
+
self.logger.debug("lsof not available, using alternative methods")
|
|
291
|
+
return True
|
|
292
|
+
except Exception as e:
|
|
293
|
+
self.logger.error(f"Error using lsof: {e}")
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
def _kill_using_pid_file(self) -> bool:
|
|
297
|
+
"""Kill process using PID file.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
True if successful or no PID file, False on error
|
|
301
|
+
"""
|
|
302
|
+
try:
|
|
303
|
+
if not self.pid_file.exists():
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
with self.pid_file.open() as f:
|
|
307
|
+
pid = int(f.read().strip())
|
|
308
|
+
|
|
309
|
+
self.logger.info(f"Found PID {pid} in PID file")
|
|
310
|
+
|
|
311
|
+
# Kill the process
|
|
312
|
+
try:
|
|
313
|
+
os.kill(pid, signal.SIGTERM)
|
|
314
|
+
time.sleep(1)
|
|
315
|
+
|
|
316
|
+
# Check if still alive
|
|
317
|
+
try:
|
|
318
|
+
os.kill(pid, 0)
|
|
319
|
+
os.kill(pid, signal.SIGKILL)
|
|
320
|
+
time.sleep(0.5)
|
|
321
|
+
except ProcessLookupError:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
# Remove PID file
|
|
325
|
+
self.pid_file.unlink(missing_ok=True)
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
except ProcessLookupError:
|
|
329
|
+
# Process doesn't exist, just remove PID file
|
|
330
|
+
self.pid_file.unlink(missing_ok=True)
|
|
331
|
+
return True
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
self.logger.error(f"Error killing process from PID file: {e}")
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
def _kill_claude_mpm_processes(self) -> bool:
|
|
338
|
+
"""Kill any claude-mpm monitor processes specifically.
|
|
339
|
+
|
|
340
|
+
This targets monitor/dashboard/socketio processes only,
|
|
341
|
+
NOT general Claude instances.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
True if successful, False on error
|
|
345
|
+
"""
|
|
346
|
+
try:
|
|
347
|
+
# Look for monitor-specific processes
|
|
348
|
+
result = subprocess.run(
|
|
349
|
+
["ps", "aux"], capture_output=True, text=True, check=False
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if result.returncode != 0:
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
lines = result.stdout.strip().split("\n")
|
|
356
|
+
killed_any = False
|
|
357
|
+
|
|
358
|
+
for line in lines:
|
|
359
|
+
line_lower = line.lower()
|
|
360
|
+
# Only target monitor/dashboard/socketio processes
|
|
361
|
+
if any(
|
|
362
|
+
[
|
|
363
|
+
"monitor" in line_lower and "claude" in line_lower,
|
|
364
|
+
"dashboard" in line_lower and "claude" in line_lower,
|
|
365
|
+
"socketio" in line_lower,
|
|
366
|
+
f":{self.port}" in line_lower and "python" in line_lower,
|
|
367
|
+
]
|
|
368
|
+
):
|
|
369
|
+
parts = line.split()
|
|
370
|
+
if len(parts) > 1:
|
|
371
|
+
try:
|
|
372
|
+
pid = int(parts[1])
|
|
373
|
+
self.logger.info(
|
|
374
|
+
f"Killing claude-mpm monitor process {pid}"
|
|
375
|
+
)
|
|
376
|
+
os.kill(pid, signal.SIGTERM)
|
|
377
|
+
killed_any = True
|
|
378
|
+
time.sleep(0.5)
|
|
379
|
+
except (ValueError, ProcessLookupError):
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
if killed_any:
|
|
383
|
+
time.sleep(1) # Give processes time to exit
|
|
384
|
+
|
|
385
|
+
return True
|
|
386
|
+
|
|
387
|
+
except Exception as e:
|
|
388
|
+
self.logger.error(f"Error killing claude-mpm processes: {e}")
|
|
389
|
+
return False
|
|
390
|
+
|
|
391
|
+
def is_our_service(self) -> Tuple[bool, Optional[int]]:
|
|
392
|
+
"""Check if the service on the port is our claude-mpm monitor.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Tuple of (is_ours, pid) where is_ours is True if it's our service
|
|
396
|
+
"""
|
|
397
|
+
try:
|
|
398
|
+
# First check PID file
|
|
399
|
+
if self.pid_file.exists():
|
|
400
|
+
try:
|
|
401
|
+
with self.pid_file.open() as f:
|
|
402
|
+
pid = int(f.read().strip())
|
|
403
|
+
|
|
404
|
+
# Verify process exists
|
|
405
|
+
os.kill(pid, 0)
|
|
406
|
+
|
|
407
|
+
# Check if it's a Python process
|
|
408
|
+
process_info = subprocess.run(
|
|
409
|
+
["ps", "-p", str(pid), "-o", "comm="],
|
|
410
|
+
capture_output=True,
|
|
411
|
+
text=True,
|
|
412
|
+
check=False,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if "python" in process_info.stdout.lower():
|
|
416
|
+
return True, pid
|
|
417
|
+
|
|
418
|
+
except (ValueError, ProcessLookupError, subprocess.CalledProcessError):
|
|
419
|
+
# PID file exists but process doesn't or isn't Python
|
|
420
|
+
self.pid_file.unlink(missing_ok=True)
|
|
421
|
+
|
|
422
|
+
# Check if service responds to our health endpoint
|
|
423
|
+
try:
|
|
424
|
+
import requests
|
|
425
|
+
|
|
426
|
+
response = requests.get(
|
|
427
|
+
f"http://{self.host}:{self.port}/health", timeout=2
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if response.status_code == 200:
|
|
431
|
+
# Try to get service info
|
|
432
|
+
try:
|
|
433
|
+
data = response.json()
|
|
434
|
+
if "claude" in str(data).lower() or "mpm" in str(data).lower():
|
|
435
|
+
# It's likely our service, try to find PID
|
|
436
|
+
pid = self._find_service_pid()
|
|
437
|
+
return True, pid
|
|
438
|
+
except Exception:
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
except Exception:
|
|
442
|
+
pass
|
|
443
|
+
|
|
444
|
+
return False, None
|
|
445
|
+
|
|
446
|
+
except Exception as e:
|
|
447
|
+
self.logger.error(f"Error checking service ownership: {e}")
|
|
448
|
+
return False, None
|
|
449
|
+
|
|
450
|
+
def _find_service_pid(self) -> Optional[int]:
|
|
451
|
+
"""Find PID of service on our port using lsof.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
PID if found, None otherwise
|
|
455
|
+
"""
|
|
456
|
+
try:
|
|
457
|
+
result = subprocess.run(
|
|
458
|
+
["lsof", "-ti", f":{self.port}"],
|
|
459
|
+
capture_output=True,
|
|
460
|
+
text=True,
|
|
461
|
+
check=False,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
465
|
+
pids = result.stdout.strip().split("\n")
|
|
466
|
+
if pids:
|
|
467
|
+
return int(pids[0].strip())
|
|
468
|
+
|
|
469
|
+
except Exception:
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
return None
|
|
473
|
+
|
|
474
|
+
def start_daemon(self, force_restart: bool = False) -> bool:
|
|
475
|
+
"""Start the daemon with automatic cleanup and retry.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
force_restart: Force restart even if already running
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
True if daemon started successfully
|
|
482
|
+
"""
|
|
483
|
+
with self._lock:
|
|
484
|
+
# Check if already running
|
|
485
|
+
if self.is_running():
|
|
486
|
+
if not force_restart:
|
|
487
|
+
pid = self.get_pid()
|
|
488
|
+
self.logger.info(f"Daemon already running with PID {pid}")
|
|
489
|
+
return True
|
|
490
|
+
|
|
491
|
+
# Stop existing daemon
|
|
492
|
+
self.logger.info("Force restarting daemon")
|
|
493
|
+
if not self.stop_daemon():
|
|
494
|
+
self.logger.error("Failed to stop existing daemon")
|
|
495
|
+
return False
|
|
496
|
+
|
|
497
|
+
# Wait for cleanup
|
|
498
|
+
time.sleep(2)
|
|
499
|
+
|
|
500
|
+
# Clean up port conflicts
|
|
501
|
+
if not self.cleanup_port_conflicts():
|
|
502
|
+
self.logger.error(f"Cannot start daemon - port {self.port} is in use")
|
|
503
|
+
return False
|
|
504
|
+
|
|
505
|
+
# Use subprocess for clean daemon startup (v4.2.40)
|
|
506
|
+
# This avoids fork() issues with Python threading
|
|
507
|
+
if self.use_subprocess_daemon():
|
|
508
|
+
return self.start_daemon_subprocess()
|
|
509
|
+
# Fallback to traditional fork (kept for compatibility)
|
|
510
|
+
return self.daemonize()
|
|
511
|
+
|
|
512
|
+
def use_subprocess_daemon(self) -> bool:
|
|
513
|
+
"""Check if we should use subprocess instead of fork for daemonization.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
True to use subprocess (safer), False to use traditional fork
|
|
517
|
+
"""
|
|
518
|
+
# Check if we're already in a subprocess to prevent infinite recursion
|
|
519
|
+
if os.environ.get("CLAUDE_MPM_SUBPROCESS_DAEMON") == "1":
|
|
520
|
+
# We're already in a subprocess, use traditional fork
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
# Otherwise, use subprocess for monitor daemon to avoid threading issues
|
|
524
|
+
return True
|
|
525
|
+
|
|
526
|
+
def start_daemon_subprocess(self) -> bool:
|
|
527
|
+
"""Start daemon using subprocess.Popen for clean process isolation.
|
|
528
|
+
|
|
529
|
+
This avoids all the fork() + threading issues by starting the monitor
|
|
530
|
+
in a completely fresh process with no inherited threads or locks.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
True if daemon started successfully
|
|
534
|
+
"""
|
|
535
|
+
try:
|
|
536
|
+
# Build command to run monitor in foreground mode in subprocess
|
|
537
|
+
import sys
|
|
538
|
+
|
|
539
|
+
python_exe = sys.executable
|
|
540
|
+
|
|
541
|
+
# Run 'claude-mpm monitor start' in subprocess with environment variable
|
|
542
|
+
# to indicate we're already in a subprocess (prevents infinite recursion)
|
|
543
|
+
cmd = [
|
|
544
|
+
python_exe,
|
|
545
|
+
"-m",
|
|
546
|
+
"claude_mpm.cli",
|
|
547
|
+
"monitor",
|
|
548
|
+
"start",
|
|
549
|
+
"--background", # Run as daemon
|
|
550
|
+
"--port",
|
|
551
|
+
str(self.port),
|
|
552
|
+
"--host",
|
|
553
|
+
self.host,
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
# Set environment variable to prevent recursive subprocess creation
|
|
557
|
+
env = os.environ.copy()
|
|
558
|
+
env["CLAUDE_MPM_SUBPROCESS_DAEMON"] = "1"
|
|
559
|
+
|
|
560
|
+
self.logger.info(f"Starting monitor daemon via subprocess: {' '.join(cmd)}")
|
|
561
|
+
|
|
562
|
+
# Open log file for output redirection
|
|
563
|
+
log_file_handle = None
|
|
564
|
+
if self.log_file:
|
|
565
|
+
log_file_handle = Path(self.log_file).open("a")
|
|
566
|
+
log_file = log_file_handle
|
|
567
|
+
else:
|
|
568
|
+
log_file = subprocess.DEVNULL
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
# Start the subprocess detached from parent
|
|
572
|
+
# Redirect stdout/stderr to log file to capture output
|
|
573
|
+
process = subprocess.Popen(
|
|
574
|
+
cmd,
|
|
575
|
+
stdin=subprocess.DEVNULL,
|
|
576
|
+
stdout=log_file,
|
|
577
|
+
stderr=subprocess.STDOUT if self.log_file else subprocess.DEVNULL,
|
|
578
|
+
start_new_session=True, # Create new process group
|
|
579
|
+
close_fds=(not self.log_file), # Keep log file open if redirecting
|
|
580
|
+
env=env, # Pass modified environment
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Close the log file handle now that subprocess has it
|
|
584
|
+
if log_file_handle:
|
|
585
|
+
log_file_handle.close()
|
|
586
|
+
|
|
587
|
+
# Get the process PID
|
|
588
|
+
pid = process.pid
|
|
589
|
+
self.logger.info(f"Monitor subprocess started with PID {pid}")
|
|
590
|
+
|
|
591
|
+
# Wait for the subprocess to write its PID file
|
|
592
|
+
# The subprocess will write the PID file after it starts successfully
|
|
593
|
+
max_wait = 10 # seconds
|
|
594
|
+
start_time = time.time()
|
|
595
|
+
|
|
596
|
+
while time.time() - start_time < max_wait:
|
|
597
|
+
# Check if process is still running
|
|
598
|
+
if process.poll() is not None:
|
|
599
|
+
# Process exited
|
|
600
|
+
self.logger.error(
|
|
601
|
+
f"Monitor daemon exited with code {process.returncode}"
|
|
602
|
+
)
|
|
603
|
+
return False
|
|
604
|
+
|
|
605
|
+
# Check if PID file was written
|
|
606
|
+
if self.pid_file.exists():
|
|
607
|
+
try:
|
|
608
|
+
with self.pid_file.open() as f:
|
|
609
|
+
written_pid = int(f.read().strip())
|
|
610
|
+
if written_pid == pid:
|
|
611
|
+
# PID file written correctly, check port
|
|
612
|
+
if (
|
|
613
|
+
not self._is_port_available()
|
|
614
|
+
): # Port NOT available means it's in use (good!)
|
|
615
|
+
self.logger.info(
|
|
616
|
+
f"Monitor daemon successfully started on port {self.port}"
|
|
617
|
+
)
|
|
618
|
+
return True
|
|
619
|
+
except Exception:
|
|
620
|
+
pass # PID file not ready yet
|
|
621
|
+
|
|
622
|
+
time.sleep(0.5)
|
|
623
|
+
|
|
624
|
+
# Timeout waiting for daemon to start
|
|
625
|
+
self.logger.error("Timeout waiting for monitor daemon to start")
|
|
626
|
+
# Try to kill the process if it's still running
|
|
627
|
+
if process.poll() is None:
|
|
628
|
+
process.terminate()
|
|
629
|
+
time.sleep(1)
|
|
630
|
+
if process.poll() is None:
|
|
631
|
+
process.kill()
|
|
632
|
+
return False
|
|
633
|
+
finally:
|
|
634
|
+
# Clean up log file handle if still open
|
|
635
|
+
if log_file_handle and not log_file_handle.closed:
|
|
636
|
+
log_file_handle.close()
|
|
637
|
+
|
|
638
|
+
except Exception as e:
|
|
639
|
+
self.logger.error(f"Failed to start daemon via subprocess: {e}")
|
|
640
|
+
return False
|
|
641
|
+
|
|
642
|
+
def daemonize(self) -> bool:
|
|
643
|
+
"""Daemonize the current process.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
True if successful (in parent), doesn't return in child
|
|
647
|
+
"""
|
|
648
|
+
# Guard against re-entrant execution after fork
|
|
649
|
+
if hasattr(self, "_forking_in_progress"):
|
|
650
|
+
self.logger.error(
|
|
651
|
+
"CRITICAL: Detected re-entrant daemonize call after fork!"
|
|
652
|
+
)
|
|
653
|
+
return False
|
|
654
|
+
|
|
655
|
+
self._forking_in_progress = True
|
|
656
|
+
|
|
657
|
+
try:
|
|
658
|
+
# Clean up asyncio event loops before forking
|
|
659
|
+
self._cleanup_event_loops()
|
|
660
|
+
|
|
661
|
+
# Create status file for communication
|
|
662
|
+
with tempfile.NamedTemporaryFile(
|
|
663
|
+
mode="w", delete=False, suffix=".status"
|
|
664
|
+
) as f:
|
|
665
|
+
self.startup_status_file = f.name
|
|
666
|
+
f.write("starting")
|
|
667
|
+
|
|
668
|
+
# First fork
|
|
669
|
+
pid = os.fork()
|
|
670
|
+
if pid > 0:
|
|
671
|
+
# Parent process - wait for child to confirm startup
|
|
672
|
+
del self._forking_in_progress # Clean up in parent
|
|
673
|
+
return self._parent_wait_for_startup(pid)
|
|
674
|
+
|
|
675
|
+
except OSError as e:
|
|
676
|
+
self.logger.error(f"First fork failed: {e}")
|
|
677
|
+
return False
|
|
678
|
+
|
|
679
|
+
# Child process continues...
|
|
680
|
+
|
|
681
|
+
# Decouple from parent
|
|
682
|
+
os.chdir("/")
|
|
683
|
+
os.setsid()
|
|
684
|
+
os.umask(0)
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
# Second fork
|
|
688
|
+
pid = os.fork()
|
|
689
|
+
if pid > 0:
|
|
690
|
+
# First child exits
|
|
691
|
+
sys.exit(0)
|
|
692
|
+
except OSError as e:
|
|
693
|
+
self.logger.error(f"Second fork failed: {e}")
|
|
694
|
+
self._report_startup_error(f"Second fork failed: {e}")
|
|
695
|
+
sys.exit(1)
|
|
696
|
+
|
|
697
|
+
# Grandchild process - the actual daemon
|
|
698
|
+
|
|
699
|
+
# Write PID file
|
|
700
|
+
self.write_pid_file()
|
|
701
|
+
|
|
702
|
+
# Redirect streams
|
|
703
|
+
self._redirect_streams()
|
|
704
|
+
|
|
705
|
+
# Setup signal handlers
|
|
706
|
+
self._setup_signal_handlers()
|
|
707
|
+
|
|
708
|
+
self.logger.info(f"Daemon process started with PID {os.getpid()}")
|
|
709
|
+
|
|
710
|
+
# DO NOT report success here - let the caller report after starting the service
|
|
711
|
+
# This prevents race conditions where we report success before the server starts
|
|
712
|
+
# self._report_startup_success() # REMOVED - caller must report
|
|
713
|
+
|
|
714
|
+
# Note: Daemon process continues running
|
|
715
|
+
# Caller is responsible for running the actual service AND reporting status
|
|
716
|
+
return True
|
|
717
|
+
|
|
718
|
+
def stop_daemon(self, timeout: int = 10) -> bool:
|
|
719
|
+
"""Stop the daemon process.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
timeout: Maximum time to wait for daemon to stop
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
True if stopped successfully
|
|
726
|
+
"""
|
|
727
|
+
with self._lock:
|
|
728
|
+
try:
|
|
729
|
+
pid = self.get_pid()
|
|
730
|
+
if not pid:
|
|
731
|
+
self.logger.info("No daemon PID found")
|
|
732
|
+
# Still try to clean up port
|
|
733
|
+
self.cleanup_port_conflicts()
|
|
734
|
+
return True
|
|
735
|
+
|
|
736
|
+
self.logger.info(f"Stopping daemon with PID {pid}")
|
|
737
|
+
|
|
738
|
+
# Send SIGTERM for graceful shutdown
|
|
739
|
+
try:
|
|
740
|
+
os.kill(pid, signal.SIGTERM)
|
|
741
|
+
except ProcessLookupError:
|
|
742
|
+
# Process already dead
|
|
743
|
+
self.cleanup_pid_file()
|
|
744
|
+
return True
|
|
745
|
+
|
|
746
|
+
# Wait for process to exit
|
|
747
|
+
start_time = time.time()
|
|
748
|
+
while time.time() - start_time < timeout:
|
|
749
|
+
try:
|
|
750
|
+
os.kill(pid, 0) # Check if still alive
|
|
751
|
+
time.sleep(0.5)
|
|
752
|
+
except ProcessLookupError:
|
|
753
|
+
# Process exited
|
|
754
|
+
self.cleanup_pid_file()
|
|
755
|
+
return True
|
|
756
|
+
|
|
757
|
+
# Force kill if still running
|
|
758
|
+
self.logger.warning("Daemon didn't stop gracefully, force killing")
|
|
759
|
+
try:
|
|
760
|
+
os.kill(pid, signal.SIGKILL)
|
|
761
|
+
time.sleep(1)
|
|
762
|
+
except ProcessLookupError:
|
|
763
|
+
pass
|
|
764
|
+
|
|
765
|
+
self.cleanup_pid_file()
|
|
766
|
+
return True
|
|
767
|
+
|
|
768
|
+
except Exception as e:
|
|
769
|
+
self.logger.error(f"Error stopping daemon: {e}")
|
|
770
|
+
return False
|
|
771
|
+
|
|
772
|
+
def is_running(self) -> bool:
|
|
773
|
+
"""Check if daemon is running.
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
True if daemon is running
|
|
777
|
+
"""
|
|
778
|
+
try:
|
|
779
|
+
pid = self.get_pid()
|
|
780
|
+
if not pid:
|
|
781
|
+
return False
|
|
782
|
+
|
|
783
|
+
# Check if process exists
|
|
784
|
+
os.kill(pid, 0)
|
|
785
|
+
return True
|
|
786
|
+
|
|
787
|
+
except ProcessLookupError:
|
|
788
|
+
# Process doesn't exist
|
|
789
|
+
self.cleanup_pid_file()
|
|
790
|
+
return False
|
|
791
|
+
|
|
792
|
+
def get_pid(self) -> Optional[int]:
|
|
793
|
+
"""Get daemon PID from PID file.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
PID if found, None otherwise
|
|
797
|
+
"""
|
|
798
|
+
try:
|
|
799
|
+
if not self.pid_file.exists():
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
with self.pid_file.open() as f:
|
|
803
|
+
return int(f.read().strip())
|
|
804
|
+
|
|
805
|
+
except Exception as e:
|
|
806
|
+
self.logger.error(f"Error reading PID file: {e}")
|
|
807
|
+
return None
|
|
808
|
+
|
|
809
|
+
def write_pid_file(self):
|
|
810
|
+
"""Write current PID to PID file."""
|
|
811
|
+
try:
|
|
812
|
+
self.pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
813
|
+
with self.pid_file.open("w") as f:
|
|
814
|
+
f.write(str(os.getpid()))
|
|
815
|
+
self.logger.debug(f"PID file written: {self.pid_file}")
|
|
816
|
+
except Exception as e:
|
|
817
|
+
self.logger.error(f"Error writing PID file: {e}")
|
|
818
|
+
raise
|
|
819
|
+
|
|
820
|
+
def cleanup_pid_file(self):
|
|
821
|
+
"""Remove PID file."""
|
|
822
|
+
try:
|
|
823
|
+
self.pid_file.unlink(missing_ok=True)
|
|
824
|
+
self.logger.debug("PID file removed")
|
|
825
|
+
except Exception as e:
|
|
826
|
+
self.logger.error(f"Error removing PID file: {e}")
|
|
827
|
+
|
|
828
|
+
def _cleanup_event_loops(self):
|
|
829
|
+
"""Clean up asyncio event loops before forking."""
|
|
830
|
+
try:
|
|
831
|
+
import asyncio
|
|
832
|
+
|
|
833
|
+
try:
|
|
834
|
+
loop = asyncio.get_event_loop()
|
|
835
|
+
if loop and not loop.is_closed():
|
|
836
|
+
# Cancel pending tasks
|
|
837
|
+
pending = asyncio.all_tasks(loop)
|
|
838
|
+
for task in pending:
|
|
839
|
+
task.cancel()
|
|
840
|
+
|
|
841
|
+
# Stop and close loop
|
|
842
|
+
if loop.is_running():
|
|
843
|
+
loop.stop()
|
|
844
|
+
|
|
845
|
+
asyncio.set_event_loop(None)
|
|
846
|
+
loop.close()
|
|
847
|
+
|
|
848
|
+
except RuntimeError:
|
|
849
|
+
# No event loop
|
|
850
|
+
pass
|
|
851
|
+
|
|
852
|
+
except Exception as e:
|
|
853
|
+
self.logger.debug(f"Error cleaning up event loops: {e}")
|
|
854
|
+
|
|
855
|
+
def _redirect_streams(self):
|
|
856
|
+
"""Redirect standard streams for daemon mode."""
|
|
857
|
+
try:
|
|
858
|
+
sys.stdout.flush()
|
|
859
|
+
sys.stderr.flush()
|
|
860
|
+
|
|
861
|
+
# Redirect stdin to /dev/null
|
|
862
|
+
with Path("/dev/null").open() as null_in:
|
|
863
|
+
os.dup2(null_in.fileno(), sys.stdin.fileno())
|
|
864
|
+
|
|
865
|
+
# Redirect stdout and stderr to log file
|
|
866
|
+
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
867
|
+
with self.log_file.open("a") as log_out:
|
|
868
|
+
os.dup2(log_out.fileno(), sys.stdout.fileno())
|
|
869
|
+
os.dup2(log_out.fileno(), sys.stderr.fileno())
|
|
870
|
+
|
|
871
|
+
except Exception as e:
|
|
872
|
+
self.logger.error(f"Error redirecting streams: {e}")
|
|
873
|
+
|
|
874
|
+
def _setup_signal_handlers(self):
|
|
875
|
+
"""Setup signal handlers for graceful shutdown."""
|
|
876
|
+
|
|
877
|
+
def signal_handler(signum, frame):
|
|
878
|
+
self.logger.info(f"Received signal {signum}, shutting down")
|
|
879
|
+
self.cleanup_pid_file()
|
|
880
|
+
sys.exit(0)
|
|
881
|
+
|
|
882
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
883
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
884
|
+
|
|
885
|
+
def _parent_wait_for_startup(self, child_pid: int, timeout: float = 10.0) -> bool:
|
|
886
|
+
"""Parent process waits for child to confirm startup.
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
child_pid: PID of child process
|
|
890
|
+
timeout: Maximum time to wait
|
|
891
|
+
|
|
892
|
+
Returns:
|
|
893
|
+
True if child started successfully
|
|
894
|
+
"""
|
|
895
|
+
try:
|
|
896
|
+
start_time = time.time()
|
|
897
|
+
|
|
898
|
+
while time.time() - start_time < timeout:
|
|
899
|
+
if (
|
|
900
|
+
not self.startup_status_file
|
|
901
|
+
or not Path(self.startup_status_file).exists()
|
|
902
|
+
):
|
|
903
|
+
time.sleep(0.1)
|
|
904
|
+
continue
|
|
905
|
+
|
|
906
|
+
try:
|
|
907
|
+
with self.startup_status_file.open() as f:
|
|
908
|
+
status = f.read().strip()
|
|
909
|
+
|
|
910
|
+
if status == OperationResult.SUCCESS:
|
|
911
|
+
# Cleanup status file
|
|
912
|
+
Path(self.startup_status_file).unlink(missing_ok=True)
|
|
913
|
+
return True
|
|
914
|
+
|
|
915
|
+
if status.startswith("error:"):
|
|
916
|
+
error_msg = status[6:]
|
|
917
|
+
self.logger.error(f"Daemon startup failed: {error_msg}")
|
|
918
|
+
Path(self.startup_status_file).unlink(missing_ok=True)
|
|
919
|
+
return False
|
|
920
|
+
|
|
921
|
+
except Exception:
|
|
922
|
+
pass
|
|
923
|
+
|
|
924
|
+
time.sleep(0.1)
|
|
925
|
+
|
|
926
|
+
self.logger.error("Daemon startup timed out")
|
|
927
|
+
return False
|
|
928
|
+
|
|
929
|
+
except Exception as e:
|
|
930
|
+
self.logger.error(f"Error waiting for daemon startup: {e}")
|
|
931
|
+
return False
|
|
932
|
+
|
|
933
|
+
def _report_startup_success(self):
|
|
934
|
+
"""Report successful startup to parent process."""
|
|
935
|
+
if self.startup_status_file:
|
|
936
|
+
try:
|
|
937
|
+
# Don't check if file exists - we need to write to it regardless
|
|
938
|
+
# The parent created it and is waiting for us to update it
|
|
939
|
+
with self.startup_status_file.open("w") as f:
|
|
940
|
+
f.write(OperationResult.SUCCESS)
|
|
941
|
+
f.flush() # Ensure it's written immediately
|
|
942
|
+
os.fsync(f.fileno()) # Force write to disk
|
|
943
|
+
except Exception:
|
|
944
|
+
# Logging might not work in daemon process after fork
|
|
945
|
+
pass
|
|
946
|
+
|
|
947
|
+
def _report_startup_error(self, error: str):
|
|
948
|
+
"""Report startup error to parent process."""
|
|
949
|
+
if self.startup_status_file:
|
|
950
|
+
try:
|
|
951
|
+
# Don't check if file exists - we need to write to it regardless
|
|
952
|
+
with self.startup_status_file.open("w") as f:
|
|
953
|
+
f.write(f"error:{error}")
|
|
954
|
+
f.flush() # Ensure it's written immediately
|
|
955
|
+
os.fsync(f.fileno()) # Force write to disk
|
|
956
|
+
except Exception as e:
|
|
957
|
+
# Try to write error to a debug file since logging might not work
|
|
958
|
+
try:
|
|
959
|
+
with Path("/tmp/daemon_debug_error.txt").open("a") as debug:
|
|
960
|
+
debug.write(f"Error reporting error: {e}\n")
|
|
961
|
+
debug.write(f"Status file: {self.startup_status_file}\n")
|
|
962
|
+
except Exception:
|
|
963
|
+
pass
|