claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +2 -2
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +330 -86
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +363 -220
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +124 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/built/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/built/dashboard.js +2 -0
- claude_mpm/dashboard/static/built/socket-client.js +2 -0
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +93 -72
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +133 -53
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +575 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +166 -64
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +185 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/METADATA +90 -22
- claude_mpm-4.0.4.dist-info/RECORD +417 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
    
        claude_mpm/core/claude_runner.py
    CHANGED
    
    | @@ -5,45 +5,60 @@ import os | |
| 5 5 | 
             
            import subprocess
         | 
| 6 6 | 
             
            import sys
         | 
| 7 7 | 
             
            import time
         | 
| 8 | 
            +
            import uuid
         | 
| 8 9 | 
             
            from datetime import datetime
         | 
| 9 10 | 
             
            from pathlib import Path
         | 
| 10 | 
            -
            from typing import  | 
| 11 | 
            -
             | 
| 11 | 
            +
            from typing import TYPE_CHECKING, Optional
         | 
| 12 | 
            +
             | 
| 12 13 | 
             
            from claude_mpm.config.paths import paths
         | 
| 13 14 |  | 
| 14 15 | 
             
            # Core imports that don't cause circular dependencies
         | 
| 15 16 | 
             
            from claude_mpm.core.config import Config
         | 
| 16 | 
            -
            from claude_mpm.core. | 
| 17 | 
            -
            from claude_mpm.core.logger import get_project_logger, ProjectLogger
         | 
| 18 | 
            -
            from claude_mpm.core.container import get_container, ServiceLifetime
         | 
| 17 | 
            +
            from claude_mpm.core.container import ServiceLifetime, get_container
         | 
| 19 18 | 
             
            from claude_mpm.core.interfaces import (
         | 
| 20 19 | 
             
                AgentDeploymentInterface,
         | 
| 21 | 
            -
                 | 
| 22 | 
            -
                 | 
| 20 | 
            +
                HookServiceInterface,
         | 
| 21 | 
            +
                TicketManagerInterface,
         | 
| 22 | 
            +
            )
         | 
| 23 | 
            +
            from claude_mpm.core.logger import ProjectLogger, get_project_logger
         | 
| 24 | 
            +
            from claude_mpm.core.logging_config import (
         | 
| 25 | 
            +
                get_logger,
         | 
| 26 | 
            +
                log_operation,
         | 
| 27 | 
            +
                log_performance_context,
         | 
| 28 | 
            +
            )
         | 
| 29 | 
            +
            from claude_mpm.services.core.interfaces import (
         | 
| 30 | 
            +
                AgentCapabilitiesInterface,
         | 
| 31 | 
            +
                CommandHandlerInterface,
         | 
| 32 | 
            +
                MemoryHookInterface,
         | 
| 33 | 
            +
                RunnerConfigurationInterface,
         | 
| 34 | 
            +
                SessionManagementInterface,
         | 
| 35 | 
            +
                SubprocessLauncherInterface,
         | 
| 36 | 
            +
                SystemInstructionsInterface,
         | 
| 37 | 
            +
                UtilityServiceInterface,
         | 
| 38 | 
            +
                VersionServiceInterface,
         | 
| 23 39 | 
             
            )
         | 
| 24 40 |  | 
| 25 41 | 
             
            # Type checking imports to avoid circular dependencies
         | 
| 26 42 | 
             
            if TYPE_CHECKING:
         | 
| 27 43 | 
             
                from claude_mpm.services.agents.deployment import AgentDeploymentService
         | 
| 28 | 
            -
                from claude_mpm.services.ticket_manager import TicketManager
         | 
| 29 44 | 
             
                from claude_mpm.services.hook_service import HookService
         | 
| 30 45 |  | 
| 31 46 |  | 
| 32 47 | 
             
            class ClaudeRunner:
         | 
| 33 48 | 
             
                """
         | 
| 34 49 | 
             
                Claude runner that replaces the entire orchestrator system.
         | 
| 35 | 
            -
             | 
| 50 | 
            +
             | 
| 36 51 | 
             
                This does exactly what we need:
         | 
| 37 52 | 
             
                1. Deploy native agents to .claude/agents/
         | 
| 38 53 | 
             
                2. Run Claude CLI with either exec or subprocess
         | 
| 39 54 | 
             
                3. Extract tickets if needed
         | 
| 40 55 | 
             
                4. Handle both interactive and non-interactive modes
         | 
| 41 | 
            -
             | 
| 56 | 
            +
             | 
| 42 57 | 
             
                Supports two launch methods:
         | 
| 43 58 | 
             
                - exec: Replace current process (default for backward compatibility)
         | 
| 44 59 | 
             
                - subprocess: Launch as child process for more control
         | 
| 45 60 | 
             
                """
         | 
| 46 | 
            -
             | 
| 61 | 
            +
             | 
| 47 62 | 
             
                def __init__(
         | 
| 48 63 | 
             
                    self,
         | 
| 49 64 | 
             
                    enable_tickets: bool = True,
         | 
| @@ -51,171 +66,193 @@ class ClaudeRunner: | |
| 51 66 | 
             
                    claude_args: Optional[list] = None,
         | 
| 52 67 | 
             
                    launch_method: str = "exec",  # "exec" or "subprocess"
         | 
| 53 68 | 
             
                    enable_websocket: bool = False,
         | 
| 54 | 
            -
                    websocket_port: int = 8765
         | 
| 69 | 
            +
                    websocket_port: int = 8765,
         | 
| 55 70 | 
             
                ):
         | 
| 56 71 | 
             
                    """Initialize the Claude runner."""
         | 
| 57 | 
            -
                    self.enable_tickets = enable_tickets
         | 
| 58 | 
            -
                    self.log_level = log_level
         | 
| 59 72 | 
             
                    self.logger = get_logger(__name__)
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                     | 
| 62 | 
            -
                    self.enable_websocket = enable_websocket
         | 
| 63 | 
            -
                    self.websocket_port = websocket_port
         | 
| 64 | 
            -
                    
         | 
| 65 | 
            -
                    # Initialize project logger for session logging
         | 
| 66 | 
            -
                    self.project_logger = None
         | 
| 67 | 
            -
                    if log_level != "OFF":
         | 
| 68 | 
            -
                        try:
         | 
| 69 | 
            -
                            self.project_logger = get_project_logger(log_level)
         | 
| 70 | 
            -
                            self.project_logger.log_system(
         | 
| 71 | 
            -
                                f"Initializing ClaudeRunner with {launch_method} launcher",
         | 
| 72 | 
            -
                                level="INFO",
         | 
| 73 | 
            -
                                component="runner"
         | 
| 74 | 
            -
                            )
         | 
| 75 | 
            -
                        except ImportError as e:
         | 
| 76 | 
            -
                            self.logger.warning(f"Project logger module not available: {e}")
         | 
| 77 | 
            -
                        except Exception as e:
         | 
| 78 | 
            -
                            self.logger.warning(f"Failed to initialize project logger: {e}")
         | 
| 79 | 
            -
                    
         | 
| 80 | 
            -
                    # Initialize services using dependency injection
         | 
| 81 | 
            -
                    # Determine the user's working directory from environment
         | 
| 82 | 
            -
                    user_working_dir = None
         | 
| 83 | 
            -
                    if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 84 | 
            -
                        user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 85 | 
            -
                        self.logger.info(f"Using user working directory from CLAUDE_MPM_USER_PWD", extra={"directory": str(user_working_dir)})
         | 
| 86 | 
            -
                    
         | 
| 87 | 
            -
                    # Get DI container and resolve services
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    # Initialize configuration service
         | 
| 88 75 | 
             
                    container = get_container()
         | 
| 89 | 
            -
                    
         | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
                        # Lazy import to avoid circular dependencies
         | 
| 93 | 
            -
                        from claude_mpm.services.agents.deployment import AgentDeploymentService
         | 
| 94 | 
            -
                        container.register_factory(
         | 
| 95 | 
            -
                            AgentDeploymentInterface,
         | 
| 96 | 
            -
                            lambda c: AgentDeploymentService(working_directory=user_working_dir),
         | 
| 97 | 
            -
                            lifetime=ServiceLifetime.SINGLETON
         | 
| 76 | 
            +
                    if not container.is_registered(RunnerConfigurationInterface):
         | 
| 77 | 
            +
                        from claude_mpm.services.runner_configuration_service import (
         | 
| 78 | 
            +
                            RunnerConfigurationService,
         | 
| 98 79 | 
             
                        )
         | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
                        raise RuntimeError(f"Agent deployment service initialization failed: {e}") from e
         | 
| 105 | 
            -
                    
         | 
| 106 | 
            -
                    # Initialize ticket manager if enabled using DI
         | 
| 107 | 
            -
                    if enable_tickets:
         | 
| 108 | 
            -
                        if not container.is_registered(TicketManagerInterface):
         | 
| 109 | 
            -
                            # Lazy import to avoid circular dependencies
         | 
| 110 | 
            -
                            from claude_mpm.services.ticket_manager import TicketManager
         | 
| 111 | 
            -
                            container.register_singleton(TicketManagerInterface, TicketManager)
         | 
| 112 | 
            -
                        
         | 
| 113 | 
            -
                        try:
         | 
| 114 | 
            -
                            self.ticket_manager = container.get(TicketManagerInterface)
         | 
| 115 | 
            -
                        except Exception as e:
         | 
| 116 | 
            -
                            self.logger.warning("Failed to initialize TicketManager", exc_info=True)
         | 
| 117 | 
            -
                            self.ticket_manager = None
         | 
| 118 | 
            -
                            self.enable_tickets = False
         | 
| 119 | 
            -
                    else:
         | 
| 120 | 
            -
                        self.ticket_manager = None
         | 
| 121 | 
            -
                    
         | 
| 122 | 
            -
                    # Initialize configuration
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                        container.register_singleton(
         | 
| 82 | 
            +
                            RunnerConfigurationInterface, RunnerConfigurationService
         | 
| 83 | 
            +
                        )
         | 
| 84 | 
            +
             | 
| 123 85 | 
             
                    try:
         | 
| 124 | 
            -
                        self. | 
| 125 | 
            -
                    except FileNotFoundError as e:
         | 
| 126 | 
            -
                        self.logger.warning("Configuration file not found, using defaults", extra={"error": str(e)})
         | 
| 127 | 
            -
                        self.config = Config()  # Will use defaults
         | 
| 86 | 
            +
                        self.configuration_service = container.get(RunnerConfigurationInterface)
         | 
| 128 87 | 
             
                    except Exception as e:
         | 
| 129 | 
            -
                        self.logger.error( | 
| 130 | 
            -
             | 
| 131 | 
            -
                    
         | 
| 132 | 
            -
                    # Initialize response logging if enabled
         | 
| 133 | 
            -
                    self.response_logger = None
         | 
| 134 | 
            -
                    response_config = self.config.get('response_logging', {})
         | 
| 135 | 
            -
                    if response_config.get('enabled', False):
         | 
| 136 | 
            -
                        try:
         | 
| 137 | 
            -
                            from claude_mpm.services.claude_session_logger import get_session_logger
         | 
| 138 | 
            -
                            self.response_logger = get_session_logger(self.config)
         | 
| 139 | 
            -
                            if self.project_logger:
         | 
| 140 | 
            -
                                self.project_logger.log_system(
         | 
| 141 | 
            -
                                    "Response logging initialized",
         | 
| 142 | 
            -
                                    level="INFO",
         | 
| 143 | 
            -
                                    component="logging"
         | 
| 144 | 
            -
                                )
         | 
| 145 | 
            -
                        except Exception as e:
         | 
| 146 | 
            -
                            self.logger.warning("Failed to initialize response logger", exc_info=True)
         | 
| 147 | 
            -
                    
         | 
| 148 | 
            -
                    # Initialize hook service using DI
         | 
| 149 | 
            -
                    if not container.is_registered(HookServiceInterface):
         | 
| 150 | 
            -
                        # Lazy import to avoid circular dependencies
         | 
| 151 | 
            -
                        from claude_mpm.services.hook_service import HookService
         | 
| 152 | 
            -
                        container.register_factory(
         | 
| 153 | 
            -
                            HookServiceInterface,
         | 
| 154 | 
            -
                            lambda c: HookService(self.config),
         | 
| 155 | 
            -
                            lifetime=ServiceLifetime.SINGLETON
         | 
| 88 | 
            +
                        self.logger.error(
         | 
| 89 | 
            +
                            "Failed to initialize configuration service", exc_info=True
         | 
| 156 90 | 
             
                        )
         | 
| 157 | 
            -
             | 
| 91 | 
            +
                        raise RuntimeError(
         | 
| 92 | 
            +
                            f"Configuration service initialization failed: {e}"
         | 
| 93 | 
            +
                        ) from e
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    # Initialize configuration using the service
         | 
| 96 | 
            +
                    config_data = self.configuration_service.initialize_configuration(
         | 
| 97 | 
            +
                        enable_tickets=enable_tickets,
         | 
| 98 | 
            +
                        log_level=log_level,
         | 
| 99 | 
            +
                        claude_args=claude_args,
         | 
| 100 | 
            +
                        launch_method=launch_method,
         | 
| 101 | 
            +
                        enable_websocket=enable_websocket,
         | 
| 102 | 
            +
                        websocket_port=websocket_port,
         | 
| 103 | 
            +
                    )
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    # Set configuration attributes
         | 
| 106 | 
            +
                    self.enable_tickets = config_data["enable_tickets"]
         | 
| 107 | 
            +
                    self.log_level = config_data["log_level"]
         | 
| 108 | 
            +
                    self.claude_args = config_data["claude_args"]
         | 
| 109 | 
            +
                    self.launch_method = config_data["launch_method"]
         | 
| 110 | 
            +
                    self.enable_websocket = config_data["enable_websocket"]
         | 
| 111 | 
            +
                    self.websocket_port = config_data["websocket_port"]
         | 
| 112 | 
            +
                    self.config = config_data["config"]
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    # Initialize project logger using the service
         | 
| 115 | 
            +
                    self.project_logger = self.configuration_service.initialize_project_logger(
         | 
| 116 | 
            +
                        self.log_level
         | 
| 117 | 
            +
                    )
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    # Initialize services using dependency injection and configuration service
         | 
| 120 | 
            +
                    user_working_dir = self.configuration_service.get_user_working_directory()
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    # Register core services
         | 
| 123 | 
            +
                    self.configuration_service.register_core_services(container, user_working_dir)
         | 
| 124 | 
            +
             | 
| 158 125 | 
             
                    try:
         | 
| 159 | 
            -
                        self. | 
| 160 | 
            -
                        self._register_memory_hooks()
         | 
| 126 | 
            +
                        self.deployment_service = container.get(AgentDeploymentInterface)
         | 
| 161 127 | 
             
                    except Exception as e:
         | 
| 162 | 
            -
                        self.logger. | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
                     | 
| 170 | 
            -
                     | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 128 | 
            +
                        self.logger.error(
         | 
| 129 | 
            +
                            f"Failed to resolve AgentDeploymentService", exc_info=True
         | 
| 130 | 
            +
                        )
         | 
| 131 | 
            +
                        raise RuntimeError(
         | 
| 132 | 
            +
                            f"Agent deployment service initialization failed: {e}"
         | 
| 133 | 
            +
                        ) from e
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    # Ticket manager disabled - use claude-mpm tickets CLI commands instead
         | 
| 136 | 
            +
                    self.ticket_manager = None
         | 
| 137 | 
            +
                    self.enable_tickets = False
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                    # Initialize response logger using configuration service
         | 
| 140 | 
            +
                    self.response_logger = self.configuration_service.initialize_response_logger(
         | 
| 141 | 
            +
                        self.config, self.project_logger
         | 
| 142 | 
            +
                    )
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    # Initialize hook service using configuration service
         | 
| 145 | 
            +
                    self.hook_service = self.configuration_service.register_hook_service(
         | 
| 146 | 
            +
                        container, self.config
         | 
| 147 | 
            +
                    )
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    # Initialize memory hook service using configuration service
         | 
| 150 | 
            +
                    self.memory_hook_service = (
         | 
| 151 | 
            +
                        self.configuration_service.register_memory_hook_service(
         | 
| 152 | 
            +
                            container, self.hook_service
         | 
| 153 | 
            +
                        )
         | 
| 154 | 
            +
                    )
         | 
| 155 | 
            +
                    if self.memory_hook_service:
         | 
| 156 | 
            +
                        self.memory_hook_service.register_memory_hooks()
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                    # Initialize agent capabilities service using configuration service
         | 
| 159 | 
            +
                    self.agent_capabilities_service = (
         | 
| 160 | 
            +
                        self.configuration_service.register_agent_capabilities_service(container)
         | 
| 161 | 
            +
                    )
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    # Initialize system instructions service using configuration service
         | 
| 164 | 
            +
                    self.system_instructions_service = (
         | 
| 165 | 
            +
                        self.configuration_service.register_system_instructions_service(
         | 
| 166 | 
            +
                            container, self.agent_capabilities_service
         | 
| 167 | 
            +
                        )
         | 
| 168 | 
            +
                    )
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                    # Initialize Socket.IO server reference first
         | 
| 171 | 
            +
                    self.websocket_server = None
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    # Initialize subprocess launcher service using configuration service
         | 
| 174 | 
            +
                    self.subprocess_launcher_service = (
         | 
| 175 | 
            +
                        self.configuration_service.register_subprocess_launcher_service(
         | 
| 176 | 
            +
                            container, self.project_logger, self.websocket_server
         | 
| 177 | 
            +
                        )
         | 
| 178 | 
            +
                    )
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                    # Initialize version service using configuration service
         | 
| 181 | 
            +
                    self.version_service = self.configuration_service.register_version_service(
         | 
| 182 | 
            +
                        container
         | 
| 183 | 
            +
                    )
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    # Initialize command handler service using configuration service
         | 
| 186 | 
            +
                    self.command_handler_service = (
         | 
| 187 | 
            +
                        self.configuration_service.register_command_handler_service(
         | 
| 188 | 
            +
                            container, self.project_logger
         | 
| 189 | 
            +
                        )
         | 
| 190 | 
            +
                    )
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                    # Initialize session management service using configuration service
         | 
| 193 | 
            +
                    # Note: This must be done after other services are initialized since it depends on the runner
         | 
| 194 | 
            +
                    self.session_management_service = (
         | 
| 195 | 
            +
                        self.configuration_service.register_session_management_service(
         | 
| 196 | 
            +
                            container, self
         | 
| 197 | 
            +
                        )
         | 
| 198 | 
            +
                    )
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    # Initialize utility service using configuration service
         | 
| 201 | 
            +
                    self.utility_service = self.configuration_service.register_utility_service(
         | 
| 202 | 
            +
                        container
         | 
| 203 | 
            +
                    )
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                    # Load system instructions using the service
         | 
| 206 | 
            +
                    if self.system_instructions_service:
         | 
| 207 | 
            +
                        self.system_instructions = (
         | 
| 208 | 
            +
                            self.system_instructions_service.load_system_instructions()
         | 
| 209 | 
            +
                        )
         | 
| 210 | 
            +
                    else:
         | 
| 211 | 
            +
                        self.system_instructions = self._load_system_instructions()
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                    # Create session log file using configuration service
         | 
| 214 | 
            +
                    self.session_log_file = self.configuration_service.create_session_log_file(
         | 
| 215 | 
            +
                        self.project_logger, self.log_level, config_data
         | 
| 216 | 
            +
                    )
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                    # Log session start event if we have a session log file
         | 
| 219 | 
            +
                    if self.session_log_file:
         | 
| 220 | 
            +
                        self._log_session_event(
         | 
| 221 | 
            +
                            {
         | 
| 175 222 | 
             
                                "event": "session_start",
         | 
| 176 223 | 
             
                                "runner": "ClaudeRunner",
         | 
| 177 | 
            -
                                "enable_tickets": enable_tickets,
         | 
| 178 | 
            -
                                "log_level": log_level,
         | 
| 179 | 
            -
                                "launch_method": launch_method
         | 
| 180 | 
            -
                            } | 
| 181 | 
            -
                         | 
| 182 | 
            -
             | 
| 183 | 
            -
                        except OSError as e:
         | 
| 184 | 
            -
                            self.logger.debug(f"OS error creating session log file: {e}")
         | 
| 185 | 
            -
                        except Exception as e:
         | 
| 186 | 
            -
                            self.logger.debug(f"Failed to create session log file: {e}")
         | 
| 187 | 
            -
                    
         | 
| 188 | 
            -
                    # Initialize Socket.IO server reference
         | 
| 189 | 
            -
                    self.websocket_server = None
         | 
| 190 | 
            -
                
         | 
| 224 | 
            +
                                "enable_tickets": self.enable_tickets,
         | 
| 225 | 
            +
                                "log_level": self.log_level,
         | 
| 226 | 
            +
                                "launch_method": self.launch_method,
         | 
| 227 | 
            +
                            }
         | 
| 228 | 
            +
                        )
         | 
| 229 | 
            +
             | 
| 191 230 | 
             
                def setup_agents(self) -> bool:
         | 
| 192 231 | 
             
                    """Deploy native agents to .claude/agents/."""
         | 
| 193 232 | 
             
                    try:
         | 
| 194 233 | 
             
                        if self.project_logger:
         | 
| 195 234 | 
             
                            self.project_logger.log_system(
         | 
| 196 | 
            -
                                "Starting agent deployment",
         | 
| 197 | 
            -
                                level="INFO",
         | 
| 198 | 
            -
                                component="deployment"
         | 
| 235 | 
            +
                                "Starting agent deployment", level="INFO", component="deployment"
         | 
| 199 236 | 
             
                            )
         | 
| 200 | 
            -
             | 
| 237 | 
            +
             | 
| 201 238 | 
             
                        results = self.deployment_service.deploy_agents()
         | 
| 202 | 
            -
             | 
| 239 | 
            +
             | 
| 203 240 | 
             
                        if results["deployed"] or results.get("updated", []):
         | 
| 204 | 
            -
                            deployed_count = len(results[ | 
| 205 | 
            -
                            updated_count = len(results.get( | 
| 206 | 
            -
             | 
| 241 | 
            +
                            deployed_count = len(results["deployed"])
         | 
| 242 | 
            +
                            updated_count = len(results.get("updated", []))
         | 
| 243 | 
            +
             | 
| 207 244 | 
             
                            if deployed_count > 0:
         | 
| 208 245 | 
             
                                print(f"✓ Deployed {deployed_count} native agents")
         | 
| 209 246 | 
             
                            if updated_count > 0:
         | 
| 210 247 | 
             
                                print(f"✓ Updated {updated_count} agents")
         | 
| 211 | 
            -
             | 
| 248 | 
            +
             | 
| 212 249 | 
             
                            if self.project_logger:
         | 
| 213 250 | 
             
                                self.project_logger.log_system(
         | 
| 214 251 | 
             
                                    f"Agent deployment successful: {deployed_count} deployed, {updated_count} updated",
         | 
| 215 252 | 
             
                                    level="INFO",
         | 
| 216 | 
            -
                                    component="deployment"
         | 
| 253 | 
            +
                                    component="deployment",
         | 
| 217 254 | 
             
                                )
         | 
| 218 | 
            -
             | 
| 255 | 
            +
             | 
| 219 256 | 
             
                            # Set Claude environment
         | 
| 220 257 | 
             
                            self.deployment_service.set_claude_environment()
         | 
| 221 258 | 
             
                            return True
         | 
| @@ -225,77 +262,86 @@ class ClaudeRunner: | |
| 225 262 | 
             
                                self.project_logger.log_system(
         | 
| 226 263 | 
             
                                    "All agents already up to date",
         | 
| 227 264 | 
             
                                    level="INFO",
         | 
| 228 | 
            -
                                    component="deployment"
         | 
| 265 | 
            +
                                    component="deployment",
         | 
| 229 266 | 
             
                                )
         | 
| 230 267 | 
             
                            return True
         | 
| 231 | 
            -
             | 
| 232 | 
            -
                    
         | 
| 268 | 
            +
             | 
| 233 269 | 
             
                    except PermissionError as e:
         | 
| 234 270 | 
             
                        error_msg = f"Permission denied deploying agents to .claude/agents/: {e}"
         | 
| 235 271 | 
             
                        self.logger.error(error_msg)
         | 
| 236 272 | 
             
                        print(f"❌ {error_msg}")
         | 
| 237 | 
            -
                        print( | 
| 273 | 
            +
                        print(
         | 
| 274 | 
            +
                            "💡 Try running with appropriate permissions or check directory ownership"
         | 
| 275 | 
            +
                        )
         | 
| 238 276 | 
             
                        if self.project_logger:
         | 
| 239 | 
            -
                            self.project_logger.log_system( | 
| 277 | 
            +
                            self.project_logger.log_system(
         | 
| 278 | 
            +
                                error_msg, level="ERROR", component="deployment"
         | 
| 279 | 
            +
                            )
         | 
| 240 280 | 
             
                        return False
         | 
| 241 | 
            -
             | 
| 281 | 
            +
             | 
| 242 282 | 
             
                    except FileNotFoundError as e:
         | 
| 243 283 | 
             
                        error_msg = f"Agent files not found: {e}"
         | 
| 244 284 | 
             
                        self.logger.error(error_msg)
         | 
| 245 285 | 
             
                        print(f"❌ {error_msg}")
         | 
| 246 286 | 
             
                        print("💡 Ensure claude-mpm is properly installed")
         | 
| 247 287 | 
             
                        if self.project_logger:
         | 
| 248 | 
            -
                            self.project_logger.log_system( | 
| 288 | 
            +
                            self.project_logger.log_system(
         | 
| 289 | 
            +
                                error_msg, level="ERROR", component="deployment"
         | 
| 290 | 
            +
                            )
         | 
| 249 291 | 
             
                        return False
         | 
| 250 | 
            -
             | 
| 292 | 
            +
             | 
| 251 293 | 
             
                    except ImportError as e:
         | 
| 252 294 | 
             
                        error_msg = f"Missing required module for agent deployment: {e}"
         | 
| 253 295 | 
             
                        self.logger.error(error_msg)
         | 
| 254 296 | 
             
                        print(f"⚠️  {error_msg}")
         | 
| 255 297 | 
             
                        print("💡 Some agent features may be limited")
         | 
| 256 298 | 
             
                        if self.project_logger:
         | 
| 257 | 
            -
                            self.project_logger.log_system( | 
| 299 | 
            +
                            self.project_logger.log_system(
         | 
| 300 | 
            +
                                error_msg, level="WARNING", component="deployment"
         | 
| 301 | 
            +
                            )
         | 
| 258 302 | 
             
                        return False
         | 
| 259 | 
            -
             | 
| 303 | 
            +
             | 
| 260 304 | 
             
                    except Exception as e:
         | 
| 261 305 | 
             
                        error_msg = f"Unexpected error during agent deployment: {e}"
         | 
| 262 306 | 
             
                        self.logger.error(error_msg)
         | 
| 263 307 | 
             
                        print(f"⚠️  {error_msg}")
         | 
| 264 308 | 
             
                        if self.project_logger:
         | 
| 265 | 
            -
                            self.project_logger.log_system( | 
| 309 | 
            +
                            self.project_logger.log_system(
         | 
| 310 | 
            +
                                error_msg, level="ERROR", component="deployment"
         | 
| 311 | 
            +
                            )
         | 
| 266 312 | 
             
                        # Continue without agents rather than failing completely
         | 
| 267 313 | 
             
                        return False
         | 
| 268 | 
            -
             | 
| 314 | 
            +
             | 
| 269 315 | 
             
                def ensure_project_agents(self) -> bool:
         | 
| 270 316 | 
             
                    """Ensure system agents are available in the project directory.
         | 
| 271 | 
            -
             | 
| 317 | 
            +
             | 
| 272 318 | 
             
                    Deploys system agents to project's .claude/agents/ directory
         | 
| 273 319 | 
             
                    if they don't exist or are outdated. This ensures agents are
         | 
| 274 320 | 
             
                    available for Claude Code to use. Project-specific JSON templates
         | 
| 275 321 | 
             
                    should be placed in .claude-mpm/agents/.
         | 
| 276 | 
            -
             | 
| 322 | 
            +
             | 
| 277 323 | 
             
                    Returns:
         | 
| 278 324 | 
             
                        bool: True if agents are available, False on error
         | 
| 279 325 | 
             
                    """
         | 
| 280 326 | 
             
                    try:
         | 
| 281 327 | 
             
                        # Use the correct user directory, not the framework directory
         | 
| 282 | 
            -
                        if  | 
| 283 | 
            -
                            project_dir = Path(os.environ[ | 
| 328 | 
            +
                        if "CLAUDE_MPM_USER_PWD" in os.environ:
         | 
| 329 | 
            +
                            project_dir = Path(os.environ["CLAUDE_MPM_USER_PWD"])
         | 
| 284 330 | 
             
                        else:
         | 
| 285 331 | 
             
                            project_dir = Path.cwd()
         | 
| 286 | 
            -
             | 
| 332 | 
            +
             | 
| 287 333 | 
             
                        project_agents_dir = project_dir / ".claude-mpm" / "agents"
         | 
| 288 | 
            -
             | 
| 334 | 
            +
             | 
| 289 335 | 
             
                        # Create directory if it doesn't exist
         | 
| 290 336 | 
             
                        project_agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 291 | 
            -
             | 
| 337 | 
            +
             | 
| 292 338 | 
             
                        if self.project_logger:
         | 
| 293 339 | 
             
                            self.project_logger.log_system(
         | 
| 294 340 | 
             
                                f"Ensuring agents are available in project: {project_agents_dir}",
         | 
| 295 341 | 
             
                                level="INFO",
         | 
| 296 | 
            -
                                component="deployment"
         | 
| 342 | 
            +
                                component="deployment",
         | 
| 297 343 | 
             
                            )
         | 
| 298 | 
            -
             | 
| 344 | 
            +
             | 
| 299 345 | 
             
                        # Deploy agents to project's .claude/agents directory (not .claude-mpm)
         | 
| 300 346 | 
             
                        # This ensures all system agents are deployed regardless of version
         | 
| 301 347 | 
             
                        # .claude-mpm/agents/ should only contain JSON source templates
         | 
| @@ -303,650 +349,313 @@ class ClaudeRunner: | |
| 303 349 | 
             
                        results = self.deployment_service.deploy_agents(
         | 
| 304 350 | 
             
                            target_dir=project_dir / ".claude",
         | 
| 305 351 | 
             
                            force_rebuild=False,
         | 
| 306 | 
            -
                            deployment_mode="project"
         | 
| 352 | 
            +
                            deployment_mode="project",
         | 
| 307 353 | 
             
                        )
         | 
| 308 | 
            -
             | 
| 354 | 
            +
             | 
| 309 355 | 
             
                        if results["deployed"] or results.get("updated", []):
         | 
| 310 | 
            -
                            deployed_count = len(results[ | 
| 311 | 
            -
                            updated_count = len(results.get( | 
| 312 | 
            -
             | 
| 356 | 
            +
                            deployed_count = len(results["deployed"])
         | 
| 357 | 
            +
                            updated_count = len(results.get("updated", []))
         | 
| 358 | 
            +
             | 
| 313 359 | 
             
                            if deployed_count > 0:
         | 
| 314 360 | 
             
                                self.logger.info(f"Deployed {deployed_count} agents to project")
         | 
| 315 361 | 
             
                            if updated_count > 0:
         | 
| 316 362 | 
             
                                self.logger.info(f"Updated {updated_count} agents in project")
         | 
| 317 | 
            -
             | 
| 363 | 
            +
             | 
| 318 364 | 
             
                            return True
         | 
| 319 365 | 
             
                        elif results.get("skipped", []):
         | 
| 320 366 | 
             
                            # Agents already exist and are current
         | 
| 321 | 
            -
                            self.logger.debug( | 
| 367 | 
            +
                            self.logger.debug(
         | 
| 368 | 
            +
                                f"Project agents up to date: {len(results['skipped'])} agents"
         | 
| 369 | 
            +
                            )
         | 
| 322 370 | 
             
                            return True
         | 
| 323 371 | 
             
                        else:
         | 
| 324 372 | 
             
                            self.logger.warning("No agents deployed to project")
         | 
| 325 373 | 
             
                            return False
         | 
| 326 | 
            -
             | 
| 374 | 
            +
             | 
| 327 375 | 
             
                    except Exception as e:
         | 
| 328 376 | 
             
                        self.logger.error(f"Failed to ensure project agents: {e}")
         | 
| 329 377 | 
             
                        if self.project_logger:
         | 
| 330 378 | 
             
                            self.project_logger.log_system(
         | 
| 331 379 | 
             
                                f"Failed to ensure project agents: {e}",
         | 
| 332 380 | 
             
                                level="ERROR",
         | 
| 333 | 
            -
                                component="deployment"
         | 
| 381 | 
            +
                                component="deployment",
         | 
| 334 382 | 
             
                            )
         | 
| 335 383 | 
             
                        return False
         | 
| 336 | 
            -
             | 
| 384 | 
            +
             | 
| 337 385 | 
             
                def deploy_project_agents_to_claude(self) -> bool:
         | 
| 338 386 | 
             
                    """Deploy project agents from .claude-mpm/agents/ to .claude/agents/.
         | 
| 339 | 
            -
             | 
| 387 | 
            +
             | 
| 340 388 | 
             
                    This method handles the deployment of project-specific agents (JSON format)
         | 
| 341 389 | 
             
                    from the project's agents directory to Claude's agent directory.
         | 
| 342 390 | 
             
                    Project agents take precedence over system agents.
         | 
| 343 | 
            -
             | 
| 391 | 
            +
             | 
| 344 392 | 
             
                    WHY: Project agents allow teams to define custom, project-specific agents
         | 
| 345 | 
            -
                    that override system agents. These are stored in JSON format in | 
| 393 | 
            +
                    that override system agents. These are stored in JSON format in
         | 
| 346 394 | 
             
                    .claude-mpm/agents/ and need to be deployed to .claude/agents/
         | 
| 347 395 | 
             
                    as MD files for Claude to use them.
         | 
| 348 | 
            -
             | 
| 396 | 
            +
             | 
| 349 397 | 
             
                    Returns:
         | 
| 350 398 | 
             
                        bool: True if deployment successful or no agents to deploy, False on error
         | 
| 351 399 | 
             
                    """
         | 
| 352 400 | 
             
                    try:
         | 
| 353 401 | 
             
                        # Use the correct user directory, not the framework directory
         | 
| 354 | 
            -
                        if  | 
| 355 | 
            -
                            project_dir = Path(os.environ[ | 
| 402 | 
            +
                        if "CLAUDE_MPM_USER_PWD" in os.environ:
         | 
| 403 | 
            +
                            project_dir = Path(os.environ["CLAUDE_MPM_USER_PWD"])
         | 
| 356 404 | 
             
                        else:
         | 
| 357 405 | 
             
                            project_dir = Path.cwd()
         | 
| 358 | 
            -
             | 
| 406 | 
            +
             | 
| 359 407 | 
             
                        project_agents_dir = project_dir / ".claude-mpm" / "agents"
         | 
| 360 408 | 
             
                        claude_agents_dir = project_dir / ".claude" / "agents"
         | 
| 361 | 
            -
             | 
| 409 | 
            +
             | 
| 362 410 | 
             
                        # Check if project agents directory exists
         | 
| 363 411 | 
             
                        if not project_agents_dir.exists():
         | 
| 364 412 | 
             
                            self.logger.debug("No project agents directory found")
         | 
| 365 413 | 
             
                            return True  # Not an error - just no project agents
         | 
| 366 | 
            -
             | 
| 414 | 
            +
             | 
| 367 415 | 
             
                        # Get JSON agent files from agents directory
         | 
| 368 416 | 
             
                        json_files = list(project_agents_dir.glob("*.json"))
         | 
| 369 417 | 
             
                        if not json_files:
         | 
| 370 418 | 
             
                            self.logger.debug("No JSON agents in project")
         | 
| 371 419 | 
             
                            return True
         | 
| 372 | 
            -
             | 
| 420 | 
            +
             | 
| 373 421 | 
             
                        # Create .claude/agents directory if needed
         | 
| 374 422 | 
             
                        claude_agents_dir.mkdir(parents=True, exist_ok=True)
         | 
| 375 | 
            -
             | 
| 376 | 
            -
                        self.logger.info( | 
| 423 | 
            +
             | 
| 424 | 
            +
                        self.logger.info(
         | 
| 425 | 
            +
                            f"Deploying {len(json_files)} project agents to .claude/agents/"
         | 
| 426 | 
            +
                        )
         | 
| 377 427 | 
             
                        if self.project_logger:
         | 
| 378 428 | 
             
                            self.project_logger.log_system(
         | 
| 379 429 | 
             
                                f"Deploying project agents from {project_agents_dir} to {claude_agents_dir}",
         | 
| 380 430 | 
             
                                level="INFO",
         | 
| 381 | 
            -
                                component="deployment"
         | 
| 431 | 
            +
                                component="deployment",
         | 
| 382 432 | 
             
                            )
         | 
| 383 | 
            -
             | 
| 433 | 
            +
             | 
| 384 434 | 
             
                        deployed_count = 0
         | 
| 385 435 | 
             
                        updated_count = 0
         | 
| 386 436 | 
             
                        errors = []
         | 
| 387 | 
            -
             | 
| 437 | 
            +
             | 
| 388 438 | 
             
                        # Deploy each JSON agent
         | 
| 389 439 | 
             
                        # CRITICAL: PM (Project Manager) must NEVER be deployed as it's the main Claude instance
         | 
| 390 | 
            -
                        EXCLUDED_AGENTS = { | 
| 391 | 
            -
             | 
| 440 | 
            +
                        EXCLUDED_AGENTS = {"pm", "project_manager"}
         | 
| 441 | 
            +
             | 
| 392 442 | 
             
                        # Initialize deployment service with proper base agent path
         | 
| 393 443 | 
             
                        # Use the existing deployment service's base agent path if available
         | 
| 394 444 | 
             
                        base_agent_path = project_agents_dir / "base_agent.json"
         | 
| 395 445 | 
             
                        if not base_agent_path.exists():
         | 
| 396 446 | 
             
                            # Fall back to system base agent
         | 
| 397 447 | 
             
                            base_agent_path = self.deployment_service.base_agent_path
         | 
| 398 | 
            -
             | 
| 448 | 
            +
             | 
| 399 449 | 
             
                        # Lazy import to avoid circular dependencies
         | 
| 400 450 | 
             
                        from claude_mpm.services.agents.deployment import AgentDeploymentService
         | 
| 401 | 
            -
             | 
| 451 | 
            +
             | 
| 402 452 | 
             
                        # Create a single deployment service instance for all agents
         | 
| 403 453 | 
             
                        project_deployment = AgentDeploymentService(
         | 
| 404 454 | 
             
                            templates_dir=project_agents_dir,
         | 
| 405 455 | 
             
                            base_agent_path=base_agent_path,
         | 
| 406 | 
            -
                            working_directory=project_dir  # Pass the project directory
         | 
| 456 | 
            +
                            working_directory=project_dir,  # Pass the project directory
         | 
| 407 457 | 
             
                        )
         | 
| 408 | 
            -
             | 
| 458 | 
            +
             | 
| 409 459 | 
             
                        # Load base agent data once
         | 
| 410 460 | 
             
                        base_agent_data = {}
         | 
| 411 461 | 
             
                        if base_agent_path and base_agent_path.exists():
         | 
| 412 462 | 
             
                            try:
         | 
| 413 463 | 
             
                                import json
         | 
| 464 | 
            +
             | 
| 414 465 | 
             
                                base_agent_data = json.loads(base_agent_path.read_text())
         | 
| 415 466 | 
             
                            except Exception as e:
         | 
| 416 467 | 
             
                                self.logger.warning(f"Could not load base agent: {e}")
         | 
| 417 | 
            -
             | 
| 468 | 
            +
             | 
| 418 469 | 
             
                        for json_file in json_files:
         | 
| 419 470 | 
             
                            try:
         | 
| 420 471 | 
             
                                agent_name = json_file.stem
         | 
| 421 | 
            -
             | 
| 472 | 
            +
             | 
| 422 473 | 
             
                                # Skip PM agent - it's the main Claude instance, not a subagent
         | 
| 423 474 | 
             
                                if agent_name.lower() in EXCLUDED_AGENTS:
         | 
| 424 | 
            -
                                    self.logger.info( | 
| 475 | 
            +
                                    self.logger.info(
         | 
| 476 | 
            +
                                        f"Skipping {agent_name} (PM is the main Claude instance)"
         | 
| 477 | 
            +
                                    )
         | 
| 425 478 | 
             
                                    continue
         | 
| 426 | 
            -
             | 
| 479 | 
            +
             | 
| 427 480 | 
             
                                target_file = claude_agents_dir / f"{agent_name}.md"
         | 
| 428 | 
            -
             | 
| 481 | 
            +
             | 
| 429 482 | 
             
                                # Check if agent needs update
         | 
| 430 483 | 
             
                                needs_update = True
         | 
| 431 484 | 
             
                                if target_file.exists():
         | 
| 432 485 | 
             
                                    # Check if it's a project agent (has project marker)
         | 
| 433 486 | 
             
                                    existing_content = target_file.read_text()
         | 
| 434 | 
            -
                                    if  | 
| 487 | 
            +
                                    if (
         | 
| 488 | 
            +
                                        "author: claude-mpm-project" in existing_content
         | 
| 489 | 
            +
                                        or "source: project" in existing_content
         | 
| 490 | 
            +
                                    ):
         | 
| 435 491 | 
             
                                        # Compare modification times
         | 
| 436 492 | 
             
                                        if target_file.stat().st_mtime >= json_file.stat().st_mtime:
         | 
| 437 493 | 
             
                                            needs_update = False
         | 
| 438 | 
            -
                                            self.logger.debug( | 
| 439 | 
            -
             | 
| 494 | 
            +
                                            self.logger.debug(
         | 
| 495 | 
            +
                                                f"Project agent {agent_name} is up to date"
         | 
| 496 | 
            +
                                            )
         | 
| 497 | 
            +
             | 
| 440 498 | 
             
                                if needs_update:
         | 
| 441 499 | 
             
                                    # Build the agent markdown using the pre-initialized service and base agent data
         | 
| 442 500 | 
             
                                    agent_content = project_deployment._build_agent_markdown(
         | 
| 443 501 | 
             
                                        agent_name, json_file, base_agent_data
         | 
| 444 502 | 
             
                                    )
         | 
| 445 | 
            -
             | 
| 503 | 
            +
             | 
| 446 504 | 
             
                                    # Mark as project agent
         | 
| 447 505 | 
             
                                    agent_content = agent_content.replace(
         | 
| 448 | 
            -
                                        "author: claude-mpm",
         | 
| 449 | 
            -
                                        "author: claude-mpm-project"
         | 
| 506 | 
            +
                                        "author: claude-mpm", "author: claude-mpm-project"
         | 
| 450 507 | 
             
                                    )
         | 
| 451 | 
            -
             | 
| 508 | 
            +
             | 
| 452 509 | 
             
                                    # Write the agent file
         | 
| 453 510 | 
             
                                    is_update = target_file.exists()
         | 
| 454 511 | 
             
                                    target_file.write_text(agent_content)
         | 
| 455 | 
            -
             | 
| 512 | 
            +
             | 
| 456 513 | 
             
                                    if is_update:
         | 
| 457 514 | 
             
                                        updated_count += 1
         | 
| 458 515 | 
             
                                        self.logger.info(f"Updated project agent: {agent_name}")
         | 
| 459 516 | 
             
                                    else:
         | 
| 460 517 | 
             
                                        deployed_count += 1
         | 
| 461 518 | 
             
                                        self.logger.info(f"Deployed project agent: {agent_name}")
         | 
| 462 | 
            -
             | 
| 519 | 
            +
             | 
| 463 520 | 
             
                            except Exception as e:
         | 
| 464 521 | 
             
                                error_msg = f"Failed to deploy project agent {json_file.name}: {e}"
         | 
| 465 522 | 
             
                                self.logger.error(error_msg)
         | 
| 466 523 | 
             
                                errors.append(error_msg)
         | 
| 467 | 
            -
             | 
| 524 | 
            +
             | 
| 468 525 | 
             
                        # Report results
         | 
| 469 526 | 
             
                        if deployed_count > 0 or updated_count > 0:
         | 
| 470 | 
            -
                            print( | 
| 527 | 
            +
                            print(
         | 
| 528 | 
            +
                                f"✓ Deployed {deployed_count} project agents, updated {updated_count}"
         | 
| 529 | 
            +
                            )
         | 
| 471 530 | 
             
                            if self.project_logger:
         | 
| 472 531 | 
             
                                self.project_logger.log_system(
         | 
| 473 532 | 
             
                                    f"Project agent deployment: {deployed_count} deployed, {updated_count} updated",
         | 
| 474 533 | 
             
                                    level="INFO",
         | 
| 475 | 
            -
                                    component="deployment"
         | 
| 534 | 
            +
                                    component="deployment",
         | 
| 476 535 | 
             
                                )
         | 
| 477 | 
            -
             | 
| 536 | 
            +
             | 
| 478 537 | 
             
                        if errors:
         | 
| 479 538 | 
             
                            for error in errors:
         | 
| 480 539 | 
             
                                print(f"⚠️  {error}")
         | 
| 481 540 | 
             
                            return False
         | 
| 482 | 
            -
             | 
| 541 | 
            +
             | 
| 483 542 | 
             
                        return True
         | 
| 484 | 
            -
             | 
| 543 | 
            +
             | 
| 485 544 | 
             
                    except Exception as e:
         | 
| 486 545 | 
             
                        error_msg = f"Failed to deploy project agents: {e}"
         | 
| 487 546 | 
             
                        self.logger.error(error_msg)
         | 
| 488 547 | 
             
                        print(f"⚠️  {error_msg}")
         | 
| 489 548 | 
             
                        if self.project_logger:
         | 
| 490 | 
            -
                            self.project_logger.log_system( | 
| 549 | 
            +
                            self.project_logger.log_system(
         | 
| 550 | 
            +
                                error_msg, level="ERROR", component="deployment"
         | 
| 551 | 
            +
                            )
         | 
| 491 552 | 
             
                        return False
         | 
| 492 | 
            -
             | 
| 553 | 
            +
             | 
| 493 554 | 
             
                def run_interactive(self, initial_context: Optional[str] = None):
         | 
| 494 | 
            -
                    """Run Claude in interactive mode.
         | 
| 495 | 
            -
             | 
| 496 | 
            -
                     | 
| 497 | 
            -
             | 
| 498 | 
            -
                    the details while this method provides the simple interface.
         | 
| 499 | 
            -
                    
         | 
| 500 | 
            -
                    DESIGN DECISION: Using delegation pattern to reduce complexity from
         | 
| 501 | 
            -
                    39 to <10 and lines from 262 to <80, while maintaining 100% backward
         | 
| 502 | 
            -
                    compatibility. All functionality including response logging through
         | 
| 503 | 
            -
                    the hook system is preserved.
         | 
| 504 | 
            -
                    
         | 
| 505 | 
            -
                    The hook system continues to capture Claude events (UserPromptSubmit,
         | 
| 506 | 
            -
                    PreToolUse, PostToolUse, Task delegations) directly from Claude Code,
         | 
| 507 | 
            -
                    providing comprehensive event capture without process control overhead.
         | 
| 508 | 
            -
                    
         | 
| 555 | 
            +
                    """Run Claude in interactive mode using the session management service.
         | 
| 556 | 
            +
             | 
| 557 | 
            +
                    Delegates to the SessionManagementService for session orchestration.
         | 
| 558 | 
            +
             | 
| 509 559 | 
             
                    Args:
         | 
| 510 560 | 
             
                        initial_context: Optional initial context to pass to Claude
         | 
| 511 561 | 
             
                    """
         | 
| 512 | 
            -
                     | 
| 513 | 
            -
             | 
| 514 | 
            -
                     | 
| 515 | 
            -
             | 
| 516 | 
            -
             | 
| 517 | 
            -
             | 
| 518 | 
            -
                        # Step 1: Initialize session
         | 
| 519 | 
            -
                        success, error = session.initialize_interactive_session()
         | 
| 520 | 
            -
                        if not success:
         | 
| 521 | 
            -
                            self.logger.error(f"Failed to initialize interactive session: {error}")
         | 
| 522 | 
            -
                            return
         | 
| 523 | 
            -
                        
         | 
| 524 | 
            -
                        # Step 2: Set up environment
         | 
| 525 | 
            -
                        success, environment = session.setup_interactive_environment()
         | 
| 526 | 
            -
                        if not success:
         | 
| 527 | 
            -
                            self.logger.error("Failed to setup interactive environment")
         | 
| 528 | 
            -
                            return
         | 
| 529 | 
            -
                        
         | 
| 530 | 
            -
                        # Step 3: Handle interactive input/output
         | 
| 531 | 
            -
                        # This is where the actual Claude process runs
         | 
| 532 | 
            -
                        session.handle_interactive_input(environment)
         | 
| 533 | 
            -
                        
         | 
| 534 | 
            -
                    finally:
         | 
| 535 | 
            -
                        # Step 4: Clean up session
         | 
| 536 | 
            -
                        session.cleanup_interactive_session()
         | 
| 537 | 
            -
                
         | 
| 562 | 
            +
                    if self.session_management_service:
         | 
| 563 | 
            +
                        self.session_management_service.run_interactive_session(initial_context)
         | 
| 564 | 
            +
                    else:
         | 
| 565 | 
            +
                        self.logger.error("Session management service not available")
         | 
| 566 | 
            +
                        print("Error: Session management service not available")
         | 
| 567 | 
            +
             | 
| 538 568 | 
             
                def run_oneshot(self, prompt: str, context: Optional[str] = None) -> bool:
         | 
| 539 | 
            -
                    """Run Claude with a single prompt  | 
| 540 | 
            -
             | 
| 541 | 
            -
                     | 
| 542 | 
            -
             | 
| 543 | 
            -
                    all the details while this method provides the simple interface.
         | 
| 544 | 
            -
                    
         | 
| 545 | 
            -
                    DESIGN DECISION: Using delegation pattern to reduce complexity from
         | 
| 546 | 
            -
                    50 to <10 and lines from 332 to <80, while maintaining 100% backward
         | 
| 547 | 
            -
                    compatibility. All functionality is preserved through the session class.
         | 
| 548 | 
            -
                    
         | 
| 569 | 
            +
                    """Run Claude with a single prompt using the session management service.
         | 
| 570 | 
            +
             | 
| 571 | 
            +
                    Delegates to the SessionManagementService for session orchestration.
         | 
| 572 | 
            +
             | 
| 549 573 | 
             
                    Args:
         | 
| 550 574 | 
             
                        prompt: The command or prompt to execute
         | 
| 551 575 | 
             
                        context: Optional context to prepend to the prompt
         | 
| 552 | 
            -
             | 
| 576 | 
            +
             | 
| 553 577 | 
             
                    Returns:
         | 
| 554 578 | 
             
                        bool: True if successful, False otherwise
         | 
| 555 579 | 
             
                    """
         | 
| 556 | 
            -
                     | 
| 557 | 
            -
             | 
| 558 | 
            -
                     | 
| 559 | 
            -
             | 
| 560 | 
            -
             | 
| 561 | 
            -
             | 
| 562 | 
            -
             | 
| 563 | 
            -
                        success, error = session.initialize_session(prompt)
         | 
| 564 | 
            -
                        if not success:
         | 
| 565 | 
            -
                            return False
         | 
| 566 | 
            -
                        
         | 
| 567 | 
            -
                        # Special case: MPM commands return early
         | 
| 568 | 
            -
                        if error is None and prompt.strip().startswith("/mpm:"):
         | 
| 569 | 
            -
                            return success
         | 
| 570 | 
            -
                        
         | 
| 571 | 
            -
                        # Step 2: Deploy agents
         | 
| 572 | 
            -
                        if not session.deploy_agents():
         | 
| 573 | 
            -
                            self.logger.warning("Agent deployment had issues, continuing...")
         | 
| 574 | 
            -
                        
         | 
| 575 | 
            -
                        # Step 3: Set up infrastructure
         | 
| 576 | 
            -
                        infrastructure = session.setup_infrastructure()
         | 
| 577 | 
            -
                        
         | 
| 578 | 
            -
                        # Step 4: Execute command
         | 
| 579 | 
            -
                        success, response = session.execute_command(prompt, context, infrastructure)
         | 
| 580 | 
            -
                        
         | 
| 581 | 
            -
                        return success
         | 
| 582 | 
            -
                        
         | 
| 583 | 
            -
                    finally:
         | 
| 584 | 
            -
                        # Step 5: Clean up session
         | 
| 585 | 
            -
                        session.cleanup_session()
         | 
| 586 | 
            -
                
         | 
| 580 | 
            +
                    if self.session_management_service:
         | 
| 581 | 
            +
                        return self.session_management_service.run_oneshot_session(prompt, context)
         | 
| 582 | 
            +
                    else:
         | 
| 583 | 
            +
                        self.logger.error("Session management service not available")
         | 
| 584 | 
            +
                        print("Error: Session management service not available")
         | 
| 585 | 
            +
                        return False
         | 
| 586 | 
            +
             | 
| 587 587 | 
             
                def _extract_tickets(self, text: str):
         | 
| 588 | 
            -
                    """Extract tickets from Claude's response."""
         | 
| 589 | 
            -
                     | 
| 590 | 
            -
             | 
| 591 | 
            -
                        
         | 
| 592 | 
            -
                    try:
         | 
| 593 | 
            -
                        # Use the ticket manager's extraction logic if available
         | 
| 594 | 
            -
                        if hasattr(self.ticket_manager, 'extract_tickets_from_text'):
         | 
| 595 | 
            -
                            tickets = self.ticket_manager.extract_tickets_from_text(text)
         | 
| 596 | 
            -
                            if tickets:
         | 
| 597 | 
            -
                                print(f"\n📋 Extracted {len(tickets)} tickets")
         | 
| 598 | 
            -
                                for ticket in tickets[:3]:  # Show first 3
         | 
| 599 | 
            -
                                    print(f"  - [{ticket.get('id', 'N/A')}] {ticket.get('title', 'No title')}")
         | 
| 600 | 
            -
                                if len(tickets) > 3:
         | 
| 601 | 
            -
                                    print(f"  ... and {len(tickets) - 3} more")
         | 
| 602 | 
            -
                        else:
         | 
| 603 | 
            -
                            self.logger.debug("Ticket extraction method not available")
         | 
| 604 | 
            -
                    except AttributeError as e:
         | 
| 605 | 
            -
                        self.logger.debug(f"Ticket manager missing expected method: {e}")
         | 
| 606 | 
            -
                    except TypeError as e:
         | 
| 607 | 
            -
                        self.logger.debug(f"Invalid ticket data format: {e}")
         | 
| 608 | 
            -
                    except Exception as e:
         | 
| 609 | 
            -
                        self.logger.debug(f"Unexpected error during ticket extraction: {e}")
         | 
| 588 | 
            +
                    """Extract tickets from Claude's response (disabled - use claude-mpm tickets CLI)."""
         | 
| 589 | 
            +
                    # Ticket extraction disabled - users should use claude-mpm tickets CLI commands
         | 
| 590 | 
            +
                    pass
         | 
| 610 591 |  | 
| 611 592 | 
             
                def _load_system_instructions(self) -> Optional[str]:
         | 
| 612 | 
            -
                    """Load and process system instructions | 
| 613 | 
            -
             | 
| 614 | 
            -
                     | 
| 615 | 
            -
                    1. First check for project-specific instructions in .claude-mpm/agents/INSTRUCTIONS.md
         | 
| 616 | 
            -
                    2. If not found, fall back to framework instructions in src/claude_mpm/agents/INSTRUCTIONS.md
         | 
| 617 | 
            -
                    
         | 
| 618 | 
            -
                    WHY: Allows projects to override the default PM instructions with project-specific
         | 
| 619 | 
            -
                    guidance, while maintaining backward compatibility with the framework defaults.
         | 
| 620 | 
            -
                    
         | 
| 621 | 
            -
                    DESIGN DECISION: Using CLAUDE_MPM_USER_PWD environment variable to locate the
         | 
| 622 | 
            -
                    correct project directory, ensuring we check the right location even when
         | 
| 623 | 
            -
                    claude-mpm is invoked from a different directory.
         | 
| 593 | 
            +
                    """Load and process system instructions.
         | 
| 594 | 
            +
             | 
| 595 | 
            +
                    Delegates to the SystemInstructionsService for loading and processing.
         | 
| 624 596 | 
             
                    """
         | 
| 625 | 
            -
                     | 
| 626 | 
            -
                         | 
| 627 | 
            -
             | 
| 628 | 
            -
             | 
| 629 | 
            -
                         | 
| 630 | 
            -
                             | 
| 631 | 
            -
                        
         | 
| 632 | 
            -
                        # Check for project-specific INSTRUCTIONS.md first
         | 
| 633 | 
            -
                        project_instructions_path = project_dir / ".claude-mpm" / "agents" / "INSTRUCTIONS.md"
         | 
| 634 | 
            -
                        
         | 
| 635 | 
            -
                        instructions_path = None
         | 
| 636 | 
            -
                        instructions_source = None
         | 
| 637 | 
            -
                        
         | 
| 638 | 
            -
                        if project_instructions_path.exists():
         | 
| 639 | 
            -
                            instructions_path = project_instructions_path
         | 
| 640 | 
            -
                            instructions_source = "PROJECT"
         | 
| 641 | 
            -
                            self.logger.info(f"Found project-specific INSTRUCTIONS.md: {instructions_path}")
         | 
| 642 | 
            -
                        else:
         | 
| 643 | 
            -
                            # Fall back to framework instructions
         | 
| 644 | 
            -
                            module_path = Path(__file__).parent.parent
         | 
| 645 | 
            -
                            framework_instructions_path = module_path / "agents" / "INSTRUCTIONS.md"
         | 
| 646 | 
            -
                            
         | 
| 647 | 
            -
                            if framework_instructions_path.exists():
         | 
| 648 | 
            -
                                instructions_path = framework_instructions_path
         | 
| 649 | 
            -
                                instructions_source = "FRAMEWORK"
         | 
| 650 | 
            -
                                self.logger.info(f"Using framework INSTRUCTIONS.md: {instructions_path}")
         | 
| 651 | 
            -
                            else:
         | 
| 652 | 
            -
                                self.logger.warning(f"No INSTRUCTIONS.md found in project or framework")
         | 
| 653 | 
            -
                                return None
         | 
| 654 | 
            -
                        
         | 
| 655 | 
            -
                        # Read raw instructions
         | 
| 656 | 
            -
                        raw_instructions = instructions_path.read_text()
         | 
| 657 | 
            -
                        
         | 
| 658 | 
            -
                        # Strip HTML metadata comments before processing
         | 
| 659 | 
            -
                        raw_instructions = self._strip_metadata_comments(raw_instructions)
         | 
| 660 | 
            -
                        
         | 
| 661 | 
            -
                        # Process template variables if ContentAssembler is available
         | 
| 662 | 
            -
                        try:
         | 
| 663 | 
            -
                            from claude_mpm.services.framework_claude_md_generator.content_assembler import ContentAssembler
         | 
| 664 | 
            -
                            assembler = ContentAssembler()
         | 
| 665 | 
            -
                            processed_instructions = assembler.apply_template_variables(raw_instructions)
         | 
| 666 | 
            -
                            
         | 
| 667 | 
            -
                            # Append BASE_PM.md framework requirements with dynamic content
         | 
| 668 | 
            -
                            base_pm_path = Path(__file__).parent.parent / "agents" / "BASE_PM.md"
         | 
| 669 | 
            -
                            if base_pm_path.exists():
         | 
| 670 | 
            -
                                base_pm_content = base_pm_path.read_text()
         | 
| 671 | 
            -
                                
         | 
| 672 | 
            -
                                # Strip metadata comments from BASE_PM.md as well
         | 
| 673 | 
            -
                                base_pm_content = self._strip_metadata_comments(base_pm_content)
         | 
| 674 | 
            -
                                
         | 
| 675 | 
            -
                                # Process BASE_PM.md with dynamic content injection
         | 
| 676 | 
            -
                                base_pm_content = self._process_base_pm_content(base_pm_content)
         | 
| 677 | 
            -
                                
         | 
| 678 | 
            -
                                processed_instructions += f"\n\n{base_pm_content}"
         | 
| 679 | 
            -
                                self.logger.info(f"Appended BASE_PM.md with dynamic capabilities from deployed agents")
         | 
| 680 | 
            -
                            
         | 
| 681 | 
            -
                            self.logger.info(f"Loaded and processed {instructions_source} PM instructions")
         | 
| 682 | 
            -
                            return processed_instructions
         | 
| 683 | 
            -
                        except ImportError:
         | 
| 684 | 
            -
                            self.logger.warning("ContentAssembler not available, using raw instructions")
         | 
| 685 | 
            -
                            self.logger.info(f"Loaded {instructions_source} PM instructions (raw)")
         | 
| 686 | 
            -
                            return raw_instructions
         | 
| 687 | 
            -
                        except Exception as e:
         | 
| 688 | 
            -
                            self.logger.warning(f"Failed to process template variables: {e}, using raw instructions")
         | 
| 689 | 
            -
                            self.logger.info(f"Loaded {instructions_source} PM instructions (raw, processing failed)")
         | 
| 690 | 
            -
                            return raw_instructions
         | 
| 691 | 
            -
                        
         | 
| 692 | 
            -
                    except Exception as e:
         | 
| 693 | 
            -
                        self.logger.error(f"Failed to load system instructions: {e}")
         | 
| 597 | 
            +
                    if self.system_instructions_service:
         | 
| 598 | 
            +
                        return self.system_instructions_service.load_system_instructions()
         | 
| 599 | 
            +
                    else:
         | 
| 600 | 
            +
                        # Fallback if service is not available
         | 
| 601 | 
            +
                        self.logger.warning(
         | 
| 602 | 
            +
                            "System instructions service not available, using basic fallback"
         | 
| 603 | 
            +
                        )
         | 
| 694 604 | 
             
                        return None
         | 
| 695 605 |  | 
| 696 606 | 
             
                def _process_base_pm_content(self, base_pm_content: str) -> str:
         | 
| 697 607 | 
             
                    """Process BASE_PM.md content with dynamic injections.
         | 
| 698 | 
            -
             | 
| 699 | 
            -
                     | 
| 700 | 
            -
                    - {{agent-capabilities}}: List of deployed agents from .claude/agents/
         | 
| 701 | 
            -
                    - {{current-date}}: Today's date for temporal context
         | 
| 608 | 
            +
             | 
| 609 | 
            +
                    Delegates to the SystemInstructionsService for processing.
         | 
| 702 610 | 
             
                    """
         | 
| 703 | 
            -
                     | 
| 704 | 
            -
             | 
| 705 | 
            -
             | 
| 706 | 
            -
             | 
| 707 | 
            -
                     | 
| 708 | 
            -
             | 
| 709 | 
            -
             | 
| 710 | 
            -
             | 
| 711 | 
            -
                         | 
| 712 | 
            -
                         | 
| 713 | 
            -
             | 
| 714 | 
            -
                    return base_pm_content
         | 
| 715 | 
            -
                
         | 
| 611 | 
            +
                    if self.system_instructions_service:
         | 
| 612 | 
            +
                        return self.system_instructions_service.process_base_pm_content(
         | 
| 613 | 
            +
                            base_pm_content
         | 
| 614 | 
            +
                        )
         | 
| 615 | 
            +
                    else:
         | 
| 616 | 
            +
                        # Fallback if service is not available
         | 
| 617 | 
            +
                        self.logger.warning(
         | 
| 618 | 
            +
                            "System instructions service not available for BASE_PM processing"
         | 
| 619 | 
            +
                        )
         | 
| 620 | 
            +
                        return base_pm_content
         | 
| 621 | 
            +
             | 
| 716 622 | 
             
                def _strip_metadata_comments(self, content: str) -> str:
         | 
| 717 623 | 
             
                    """Strip HTML metadata comments from content.
         | 
| 718 | 
            -
             | 
| 719 | 
            -
                     | 
| 720 | 
            -
                    <!-- FRAMEWORK_VERSION: 0010 -->
         | 
| 721 | 
            -
                    <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
         | 
| 722 | 
            -
                    <!-- WORKFLOW_VERSION: ... -->
         | 
| 723 | 
            -
                    <!-- PROJECT_WORKFLOW_VERSION: ... -->
         | 
| 724 | 
            -
                    <!-- CUSTOM_PROJECT_WORKFLOW -->
         | 
| 725 | 
            -
                    
         | 
| 726 | 
            -
                    WHY: These metadata comments are useful for internal tracking but should not
         | 
| 727 | 
            -
                    appear in the final instructions passed to Claude via --append-system-prompt.
         | 
| 728 | 
            -
                    They clutter the instructions and provide no value to the Claude agent.
         | 
| 729 | 
            -
                    
         | 
| 730 | 
            -
                    DESIGN DECISION: Using regex to remove all HTML comments that contain known
         | 
| 731 | 
            -
                    metadata patterns. Also removes any resulting leading blank lines.
         | 
| 624 | 
            +
             | 
| 625 | 
            +
                    Delegates to the SystemInstructionsService for processing.
         | 
| 732 626 | 
             
                    """
         | 
| 733 | 
            -
                     | 
| 734 | 
            -
             | 
| 735 | 
            -
                     | 
| 736 | 
            -
             | 
| 737 | 
            -
                         | 
| 738 | 
            -
             | 
| 739 | 
            -
                         | 
| 740 | 
            -
                         | 
| 741 | 
            -
             | 
| 742 | 
            -
                        'AGENT_VERSION',
         | 
| 743 | 
            -
                        'METADATA_VERSION'
         | 
| 744 | 
            -
                    ]
         | 
| 745 | 
            -
                    
         | 
| 746 | 
            -
                    # Build regex pattern to match any of these metadata comments
         | 
| 747 | 
            -
                    pattern = r'<!--\s*(' + '|'.join(patterns_to_strip) + r')[^>]*-->\n?'
         | 
| 748 | 
            -
                    cleaned = re.sub(pattern, '', content)
         | 
| 749 | 
            -
                    
         | 
| 750 | 
            -
                    # Also remove any leading blank lines that might result
         | 
| 751 | 
            -
                    cleaned = cleaned.lstrip('\n')
         | 
| 752 | 
            -
                    
         | 
| 753 | 
            -
                    return cleaned
         | 
| 754 | 
            -
                
         | 
| 627 | 
            +
                    if self.system_instructions_service:
         | 
| 628 | 
            +
                        return self.system_instructions_service.strip_metadata_comments(content)
         | 
| 629 | 
            +
                    else:
         | 
| 630 | 
            +
                        # Fallback if service is not available
         | 
| 631 | 
            +
                        self.logger.warning(
         | 
| 632 | 
            +
                            "System instructions service not available for metadata stripping"
         | 
| 633 | 
            +
                        )
         | 
| 634 | 
            +
                        return content
         | 
| 635 | 
            +
             | 
| 755 636 | 
             
                def _generate_deployed_agent_capabilities(self) -> str:
         | 
| 756 | 
            -
                    """Generate agent capabilities from deployed agents | 
| 757 | 
            -
             | 
| 758 | 
            -
                     | 
| 759 | 
            -
                    1. Project agents (.claude/agents/) - highest priority
         | 
| 760 | 
            -
                    2. User agents (~/.config/claude/agents/) - middle priority  
         | 
| 761 | 
            -
                    3. System agents (claude-desktop installation) - lowest priority
         | 
| 762 | 
            -
                    
         | 
| 763 | 
            -
                    Project agents override user/system agents with the same ID.
         | 
| 764 | 
            -
                    """
         | 
| 765 | 
            -
                    try:
         | 
| 766 | 
            -
                        # Track discovered agents by ID to handle overrides
         | 
| 767 | 
            -
                        discovered_agents = {}
         | 
| 768 | 
            -
                        
         | 
| 769 | 
            -
                        # 1. First read system agents (lowest priority)
         | 
| 770 | 
            -
                        system_agents_dirs = [
         | 
| 771 | 
            -
                            Path.home() / "Library" / "Application Support" / "Claude" / "agents",  # macOS
         | 
| 772 | 
            -
                            Path.home() / ".config" / "claude" / "agents",  # Linux
         | 
| 773 | 
            -
                            Path.home() / "AppData" / "Roaming" / "Claude" / "agents",  # Windows
         | 
| 774 | 
            -
                        ]
         | 
| 775 | 
            -
                        
         | 
| 776 | 
            -
                        for system_dir in system_agents_dirs:
         | 
| 777 | 
            -
                            if system_dir.exists():
         | 
| 778 | 
            -
                                self._discover_agents_from_dir(system_dir, discovered_agents, "system")
         | 
| 779 | 
            -
                                break
         | 
| 780 | 
            -
                        
         | 
| 781 | 
            -
                        # 2. Then read user agents (middle priority, overrides system)
         | 
| 782 | 
            -
                        user_agents_dir = Path.home() / ".config" / "claude" / "agents"
         | 
| 783 | 
            -
                        if user_agents_dir.exists():
         | 
| 784 | 
            -
                            self._discover_agents_from_dir(user_agents_dir, discovered_agents, "user")
         | 
| 785 | 
            -
                        
         | 
| 786 | 
            -
                        # 3. Finally read project agents (highest priority, overrides all)
         | 
| 787 | 
            -
                        project_agents_dir = Path.cwd() / ".claude" / "agents"
         | 
| 788 | 
            -
                        if project_agents_dir.exists():
         | 
| 789 | 
            -
                            self._discover_agents_from_dir(project_agents_dir, discovered_agents, "project")
         | 
| 790 | 
            -
                        
         | 
| 791 | 
            -
                        if not discovered_agents:
         | 
| 792 | 
            -
                            self.logger.warning("No agents found in any tier")
         | 
| 793 | 
            -
                            return self._get_fallback_capabilities()
         | 
| 794 | 
            -
                        
         | 
| 795 | 
            -
                        # Build capabilities section from discovered agents
         | 
| 796 | 
            -
                        section = "\n## Available Agent Capabilities\n\n"
         | 
| 797 | 
            -
                        section += "You have the following specialized agents available for delegation:\n\n"
         | 
| 798 | 
            -
                        
         | 
| 799 | 
            -
                        # Group agents by category
         | 
| 800 | 
            -
                        agents_by_category = {}
         | 
| 801 | 
            -
                        for agent_id, agent_info in discovered_agents.items():
         | 
| 802 | 
            -
                            category = agent_info['category']
         | 
| 803 | 
            -
                            if category not in agents_by_category:
         | 
| 804 | 
            -
                                agents_by_category[category] = []
         | 
| 805 | 
            -
                            agents_by_category[category].append(agent_info)
         | 
| 806 | 
            -
                        
         | 
| 807 | 
            -
                        # Output agents by category
         | 
| 808 | 
            -
                        for category in sorted(agents_by_category.keys()):
         | 
| 809 | 
            -
                            section += f"\n### {category} Agents\n"
         | 
| 810 | 
            -
                            for agent in sorted(agents_by_category[category], key=lambda x: x['name']):
         | 
| 811 | 
            -
                                tier_indicator = f" [{agent['tier']}]" if agent['tier'] != 'project' else ""
         | 
| 812 | 
            -
                                section += f"- **{agent['name']}** (`{agent['id']}`{tier_indicator}): {agent['description']}\n"
         | 
| 813 | 
            -
                        
         | 
| 814 | 
            -
                        # Add summary
         | 
| 815 | 
            -
                        section += f"\n**Total Available Agents**: {len(discovered_agents)}\n"
         | 
| 816 | 
            -
                        
         | 
| 817 | 
            -
                        # Show tier distribution
         | 
| 818 | 
            -
                        tier_counts = {}
         | 
| 819 | 
            -
                        for agent in discovered_agents.values():
         | 
| 820 | 
            -
                            tier = agent['tier']
         | 
| 821 | 
            -
                            tier_counts[tier] = tier_counts.get(tier, 0) + 1
         | 
| 822 | 
            -
                        
         | 
| 823 | 
            -
                        if len(tier_counts) > 1:
         | 
| 824 | 
            -
                            section += f"**Agent Sources**: "
         | 
| 825 | 
            -
                            tier_summary = []
         | 
| 826 | 
            -
                            for tier in ['project', 'user', 'system']:
         | 
| 827 | 
            -
                                if tier in tier_counts:
         | 
| 828 | 
            -
                                    tier_summary.append(f"{tier_counts[tier]} {tier}")
         | 
| 829 | 
            -
                            section += ", ".join(tier_summary) + "\n"
         | 
| 830 | 
            -
                        
         | 
| 831 | 
            -
                        section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
         | 
| 832 | 
            -
                        
         | 
| 833 | 
            -
                        self.logger.info(f"Generated capabilities for {len(discovered_agents)} agents " +
         | 
| 834 | 
            -
                                       f"(project: {tier_counts.get('project', 0)}, " +
         | 
| 835 | 
            -
                                       f"user: {tier_counts.get('user', 0)}, " +
         | 
| 836 | 
            -
                                       f"system: {tier_counts.get('system', 0)})")
         | 
| 837 | 
            -
                        return section
         | 
| 838 | 
            -
                        
         | 
| 839 | 
            -
                    except Exception as e:
         | 
| 840 | 
            -
                        self.logger.error(f"Failed to generate deployed agent capabilities: {e}")
         | 
| 841 | 
            -
                        return self._get_fallback_capabilities()
         | 
| 842 | 
            -
                
         | 
| 843 | 
            -
                def _discover_agents_from_dir(self, agents_dir: Path, discovered_agents: dict, tier: str):
         | 
| 844 | 
            -
                    """Discover agents from a specific directory and add/override in discovered_agents.
         | 
| 845 | 
            -
                    
         | 
| 846 | 
            -
                    Args:
         | 
| 847 | 
            -
                        agents_dir: Directory to search for agent .md files
         | 
| 848 | 
            -
                        discovered_agents: Dictionary to update with discovered agents
         | 
| 849 | 
            -
                        tier: The tier this directory represents (system/user/project)
         | 
| 637 | 
            +
                    """Generate agent capabilities from deployed agents.
         | 
| 638 | 
            +
             | 
| 639 | 
            +
                    Delegates to the AgentCapabilitiesService for agent discovery and formatting.
         | 
| 850 640 | 
             
                    """
         | 
| 851 | 
            -
                    if  | 
| 852 | 
            -
                        return
         | 
| 853 | 
            -
             | 
| 854 | 
            -
             | 
| 855 | 
            -
                    for agent_file in sorted(agent_files):
         | 
| 856 | 
            -
                        agent_id = agent_file.stem
         | 
| 857 | 
            -
                        
         | 
| 858 | 
            -
                        # Skip pm.md if it exists (PM is not a deployable agent)
         | 
| 859 | 
            -
                        if agent_id.lower() == 'pm':
         | 
| 860 | 
            -
                            continue
         | 
| 861 | 
            -
                        
         | 
| 862 | 
            -
                        # Read agent content and extract metadata
         | 
| 863 | 
            -
                        try:
         | 
| 864 | 
            -
                            content = agent_file.read_text()
         | 
| 865 | 
            -
                            import re
         | 
| 866 | 
            -
                            
         | 
| 867 | 
            -
                            # Check for YAML frontmatter
         | 
| 868 | 
            -
                            name = agent_id.replace('_', ' ').title()
         | 
| 869 | 
            -
                            desc = "Specialized agent for delegation"
         | 
| 870 | 
            -
                            
         | 
| 871 | 
            -
                            if content.startswith('---'):
         | 
| 872 | 
            -
                                # Parse YAML frontmatter
         | 
| 873 | 
            -
                                frontmatter_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
         | 
| 874 | 
            -
                                if frontmatter_match:
         | 
| 875 | 
            -
                                    frontmatter = frontmatter_match.group(1)
         | 
| 876 | 
            -
                                    # Extract name from frontmatter
         | 
| 877 | 
            -
                                    name_fm_match = re.search(r'^name:\s*(.+)$', frontmatter, re.MULTILINE)
         | 
| 878 | 
            -
                                    if name_fm_match:
         | 
| 879 | 
            -
                                        name_value = name_fm_match.group(1).strip()
         | 
| 880 | 
            -
                                        # Format the name nicely
         | 
| 881 | 
            -
                                        name = name_value.replace('_', ' ').title()
         | 
| 882 | 
            -
                                    
         | 
| 883 | 
            -
                                    # Extract description from frontmatter
         | 
| 884 | 
            -
                                    desc_fm_match = re.search(r'^description:\s*(.+)$', frontmatter, re.MULTILINE)
         | 
| 885 | 
            -
                                    if desc_fm_match:
         | 
| 886 | 
            -
                                        desc = desc_fm_match.group(1).strip()
         | 
| 887 | 
            -
                            else:
         | 
| 888 | 
            -
                                # No frontmatter, extract from content
         | 
| 889 | 
            -
                                name_match = re.search(r'^#\s+(.+?)(?:\s+Agent)?$', content, re.MULTILINE)
         | 
| 890 | 
            -
                                if name_match:
         | 
| 891 | 
            -
                                    name = name_match.group(1)
         | 
| 892 | 
            -
                                
         | 
| 893 | 
            -
                                # Get first non-heading line after the title
         | 
| 894 | 
            -
                                lines = content.split('\n')
         | 
| 895 | 
            -
                                for i, line in enumerate(lines):
         | 
| 896 | 
            -
                                    if line.startswith('#'):
         | 
| 897 | 
            -
                                        # Found title, look for description after it
         | 
| 898 | 
            -
                                        for desc_line in lines[i+1:]:
         | 
| 899 | 
            -
                                            desc_line = desc_line.strip()
         | 
| 900 | 
            -
                                            if desc_line and not desc_line.startswith('#'):
         | 
| 901 | 
            -
                                                desc = desc_line
         | 
| 902 | 
            -
                                                break
         | 
| 903 | 
            -
                                        break
         | 
| 904 | 
            -
                            
         | 
| 905 | 
            -
                            # Categorize based on agent name/type
         | 
| 906 | 
            -
                            category = self._categorize_agent(agent_id, content)
         | 
| 907 | 
            -
                            
         | 
| 908 | 
            -
                            # Add or override agent in discovered_agents
         | 
| 909 | 
            -
                            discovered_agents[agent_id] = {
         | 
| 910 | 
            -
                                'id': agent_id,
         | 
| 911 | 
            -
                                'name': name,
         | 
| 912 | 
            -
                                'description': desc[:150] + '...' if len(desc) > 150 else desc,
         | 
| 913 | 
            -
                                'category': category,
         | 
| 914 | 
            -
                                'tier': tier,
         | 
| 915 | 
            -
                                'path': str(agent_file)
         | 
| 916 | 
            -
                            }
         | 
| 917 | 
            -
                            
         | 
| 918 | 
            -
                            self.logger.debug(f"Discovered {tier} agent: {agent_id} from {agent_file}")
         | 
| 919 | 
            -
                            
         | 
| 920 | 
            -
                        except Exception as e:
         | 
| 921 | 
            -
                            self.logger.debug(f"Could not parse agent {agent_file}: {e}")
         | 
| 922 | 
            -
                            continue
         | 
| 923 | 
            -
                def _categorize_agent(self, agent_id: str, content: str) -> str:
         | 
| 924 | 
            -
                    """Categorize an agent based on its ID and content."""
         | 
| 925 | 
            -
                    agent_id_lower = agent_id.lower()
         | 
| 926 | 
            -
                    content_lower = content.lower()
         | 
| 927 | 
            -
                    
         | 
| 928 | 
            -
                    if 'engineer' in agent_id_lower or 'engineering' in content_lower:
         | 
| 929 | 
            -
                        return "Engineering"
         | 
| 930 | 
            -
                    elif 'research' in agent_id_lower or 'analysis' in content_lower or 'analyzer' in agent_id_lower:
         | 
| 931 | 
            -
                        return "Research"
         | 
| 932 | 
            -
                    elif 'qa' in agent_id_lower or 'quality' in content_lower or 'test' in agent_id_lower:
         | 
| 933 | 
            -
                        return "Quality"
         | 
| 934 | 
            -
                    elif 'security' in agent_id_lower or 'security' in content_lower:
         | 
| 935 | 
            -
                        return "Security"
         | 
| 936 | 
            -
                    elif 'doc' in agent_id_lower or 'documentation' in content_lower:
         | 
| 937 | 
            -
                        return "Documentation"
         | 
| 938 | 
            -
                    elif 'data' in agent_id_lower:
         | 
| 939 | 
            -
                        return "Data"
         | 
| 940 | 
            -
                    elif 'ops' in agent_id_lower or 'deploy' in agent_id_lower or 'operations' in content_lower:
         | 
| 941 | 
            -
                        return "Operations"
         | 
| 942 | 
            -
                    elif 'version' in agent_id_lower or 'git' in content_lower:
         | 
| 943 | 
            -
                        return "Version Control"
         | 
| 641 | 
            +
                    if self.agent_capabilities_service:
         | 
| 642 | 
            +
                        return (
         | 
| 643 | 
            +
                            self.agent_capabilities_service.generate_deployed_agent_capabilities()
         | 
| 644 | 
            +
                        )
         | 
| 944 645 | 
             
                    else:
         | 
| 945 | 
            -
                         | 
| 946 | 
            -
             | 
| 646 | 
            +
                        # Fallback if service is not available
         | 
| 647 | 
            +
                        self.logger.warning(
         | 
| 648 | 
            +
                            "Agent capabilities service not available, using fallback"
         | 
| 649 | 
            +
                        )
         | 
| 650 | 
            +
                        return self._get_fallback_capabilities()
         | 
| 651 | 
            +
             | 
| 947 652 | 
             
                def _get_fallback_capabilities(self) -> str:
         | 
| 948 653 | 
             
                    """Return fallback agent capabilities when deployed agents can't be read."""
         | 
| 949 | 
            -
                     | 
| 654 | 
            +
                    # Delegate to the service if available, otherwise use basic fallback
         | 
| 655 | 
            +
                    if self.agent_capabilities_service:
         | 
| 656 | 
            +
                        return self.agent_capabilities_service._get_fallback_capabilities()
         | 
| 657 | 
            +
                    else:
         | 
| 658 | 
            +
                        return """
         | 
| 950 659 | 
             
            ## Available Agent Capabilities
         | 
| 951 660 |  | 
| 952 661 | 
             
            You have the following specialized agents available for delegation:
         | 
| @@ -955,490 +664,86 @@ You have the following specialized agents available for delegation: | |
| 955 664 | 
             
            - **Research Agent**: Investigation and analysis
         | 
| 956 665 | 
             
            - **QA Agent**: Testing and quality assurance
         | 
| 957 666 | 
             
            - **Documentation Agent**: Documentation creation and maintenance
         | 
| 958 | 
            -
            - **Security Agent**: Security analysis and protection
         | 
| 959 | 
            -
            - **Data Engineer Agent**: Data management and pipelines
         | 
| 960 | 
            -
            - **Ops Agent**: Deployment and operations
         | 
| 961 | 
            -
            - **Version Control Agent**: Git operations and version management
         | 
| 962 667 |  | 
| 963 668 | 
             
            Use these agents to delegate specialized work via the Task tool.
         | 
| 964 669 | 
             
            """
         | 
| 965 | 
            -
             | 
| 966 | 
            -
                def _generate_agent_capabilities_section(self, agents: dict) -> str:
         | 
| 967 | 
            -
                    """Generate dynamic agent capabilities section from available agents."""
         | 
| 968 | 
            -
                    if not agents:
         | 
| 969 | 
            -
                        return ""
         | 
| 970 | 
            -
                    
         | 
| 971 | 
            -
                    # Build capabilities section
         | 
| 972 | 
            -
                    section = "\n\n## Available Agent Capabilities\n\n"
         | 
| 973 | 
            -
                    section += "You have the following specialized agents available for delegation:\n\n"
         | 
| 974 | 
            -
                    
         | 
| 975 | 
            -
                    # Group agents by category
         | 
| 976 | 
            -
                    categories = {}
         | 
| 977 | 
            -
                    for agent_id, info in agents.items():
         | 
| 978 | 
            -
                        category = info.get('category', 'general')
         | 
| 979 | 
            -
                        if category not in categories:
         | 
| 980 | 
            -
                            categories[category] = []
         | 
| 981 | 
            -
                        categories[category].append((agent_id, info))
         | 
| 982 | 
            -
                    
         | 
| 983 | 
            -
                    # List agents by category
         | 
| 984 | 
            -
                    for category in sorted(categories.keys()):
         | 
| 985 | 
            -
                        section += f"\n### {category.title()} Agents\n"
         | 
| 986 | 
            -
                        for agent_id, info in sorted(categories[category]):
         | 
| 987 | 
            -
                            name = info.get('name', agent_id)
         | 
| 988 | 
            -
                            desc = info.get('description', 'Specialized agent')
         | 
| 989 | 
            -
                            tools = info.get('tools', [])
         | 
| 990 | 
            -
                            section += f"- **{name}** (`{agent_id}`): {desc}\n"
         | 
| 991 | 
            -
                            if tools:
         | 
| 992 | 
            -
                                section += f"  - Tools: {', '.join(tools[:5])}"
         | 
| 993 | 
            -
                                if len(tools) > 5:
         | 
| 994 | 
            -
                                    section += f" (+{len(tools)-5} more)"
         | 
| 995 | 
            -
                                section += "\n"
         | 
| 996 | 
            -
                    
         | 
| 997 | 
            -
                    # Add summary
         | 
| 998 | 
            -
                    section += f"\n**Total Available Agents**: {len(agents)}\n"
         | 
| 999 | 
            -
                    section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
         | 
| 1000 | 
            -
                    
         | 
| 1001 | 
            -
                    return section
         | 
| 1002 | 
            -
                
         | 
| 670 | 
            +
             | 
| 1003 671 | 
             
                def _create_system_prompt(self) -> str:
         | 
| 1004 | 
            -
                    """Create the complete system prompt including instructions. | 
| 1005 | 
            -
             | 
| 1006 | 
            -
             | 
| 672 | 
            +
                    """Create the complete system prompt including instructions.
         | 
| 673 | 
            +
             | 
| 674 | 
            +
                    Delegates to the SystemInstructionsService for prompt creation.
         | 
| 675 | 
            +
                    """
         | 
| 676 | 
            +
                    if self.system_instructions_service:
         | 
| 677 | 
            +
                        return self.system_instructions_service.create_system_prompt(
         | 
| 678 | 
            +
                            self.system_instructions
         | 
| 679 | 
            +
                        )
         | 
| 1007 680 | 
             
                    else:
         | 
| 1008 | 
            -
                        # Fallback  | 
| 1009 | 
            -
                         | 
| 1010 | 
            -
             | 
| 681 | 
            +
                        # Fallback if service is not available
         | 
| 682 | 
            +
                        if self.system_instructions:
         | 
| 683 | 
            +
                            return self.system_instructions
         | 
| 684 | 
            +
                        else:
         | 
| 685 | 
            +
                            return create_simple_context()
         | 
| 686 | 
            +
             | 
| 1011 687 | 
             
                def _contains_delegation(self, text: str) -> bool:
         | 
| 1012 | 
            -
                    """Check if text contains signs of agent delegation."""
         | 
| 1013 | 
            -
                     | 
| 1014 | 
            -
             | 
| 1015 | 
            -
             | 
| 1016 | 
            -
                         | 
| 1017 | 
            -
                         | 
| 1018 | 
            -
             | 
| 1019 | 
            -
                        "engineer agent",
         | 
| 1020 | 
            -
                        "qa agent",
         | 
| 1021 | 
            -
                        "documentation agent",
         | 
| 1022 | 
            -
                        "research agent",
         | 
| 1023 | 
            -
                        "security agent",
         | 
| 1024 | 
            -
                        "ops agent",
         | 
| 1025 | 
            -
                        "version_control agent",
         | 
| 1026 | 
            -
                        "data_engineer agent"
         | 
| 1027 | 
            -
                    ]
         | 
| 1028 | 
            -
                    
         | 
| 1029 | 
            -
                    text_lower = text.lower()
         | 
| 1030 | 
            -
                    return any(pattern.lower() in text_lower for pattern in delegation_patterns)
         | 
| 1031 | 
            -
                
         | 
| 688 | 
            +
                    """Check if text contains signs of agent delegation using the utility service."""
         | 
| 689 | 
            +
                    if self.utility_service:
         | 
| 690 | 
            +
                        return self.utility_service.contains_delegation(text)
         | 
| 691 | 
            +
                    else:
         | 
| 692 | 
            +
                        # Fallback if service not available
         | 
| 693 | 
            +
                        return False
         | 
| 694 | 
            +
             | 
| 1032 695 | 
             
                def _extract_agent_from_response(self, text: str) -> Optional[str]:
         | 
| 1033 | 
            -
                    """Try to extract agent name from delegation response."""
         | 
| 1034 | 
            -
                     | 
| 1035 | 
            -
             | 
| 1036 | 
            -
                    
         | 
| 1037 | 
            -
             | 
| 1038 | 
            -
             | 
| 1039 | 
            -
             | 
| 1040 | 
            -
                        return match.group(1)
         | 
| 1041 | 
            -
                    
         | 
| 1042 | 
            -
                    # Pattern 2: "engineer agent" etc
         | 
| 1043 | 
            -
                    agent_names = [
         | 
| 1044 | 
            -
                        "engineer", "qa", "documentation", "research", 
         | 
| 1045 | 
            -
                        "security", "ops", "version_control", "data_engineer"
         | 
| 1046 | 
            -
                    ]
         | 
| 1047 | 
            -
                    text_lower = text.lower()
         | 
| 1048 | 
            -
                    for agent in agent_names:
         | 
| 1049 | 
            -
                        if f"{agent} agent" in text_lower or f"agent: {agent}" in text_lower:
         | 
| 1050 | 
            -
                            return agent
         | 
| 1051 | 
            -
                    
         | 
| 1052 | 
            -
                    return None
         | 
| 1053 | 
            -
                
         | 
| 696 | 
            +
                    """Try to extract agent name from delegation response using the utility service."""
         | 
| 697 | 
            +
                    if self.utility_service:
         | 
| 698 | 
            +
                        return self.utility_service.extract_agent_from_response(text)
         | 
| 699 | 
            +
                    else:
         | 
| 700 | 
            +
                        # Fallback if service not available
         | 
| 701 | 
            +
                        return None
         | 
| 702 | 
            +
             | 
| 1054 703 | 
             
                def _handle_mpm_command(self, prompt: str) -> bool:
         | 
| 1055 | 
            -
                    """Handle /mpm: commands  | 
| 1056 | 
            -
             | 
| 1057 | 
            -
             | 
| 1058 | 
            -
             | 
| 1059 | 
            -
             | 
| 1060 | 
            -
                        
         | 
| 1061 | 
            -
             | 
| 1062 | 
            -
             | 
| 1063 | 
            -
             | 
| 1064 | 
            -
                        
         | 
| 1065 | 
            -
                        command = parts[0]
         | 
| 1066 | 
            -
                        args = parts[1:]
         | 
| 1067 | 
            -
                        
         | 
| 1068 | 
            -
                        # Handle commands
         | 
| 1069 | 
            -
                        if command == "test":
         | 
| 1070 | 
            -
                            print("Hello World")
         | 
| 1071 | 
            -
                            if self.project_logger:
         | 
| 1072 | 
            -
                                self.project_logger.log_system(
         | 
| 1073 | 
            -
                                    "Executed /mpm:test command",
         | 
| 1074 | 
            -
                                    level="INFO",
         | 
| 1075 | 
            -
                                    component="command"
         | 
| 1076 | 
            -
                                )
         | 
| 1077 | 
            -
                            return True
         | 
| 1078 | 
            -
                        elif command == "agents":
         | 
| 1079 | 
            -
                            # Handle agents command - display deployed agent versions
         | 
| 1080 | 
            -
                            # WHY: This provides users with a quick way to check deployed agent versions
         | 
| 1081 | 
            -
                            # directly from within Claude Code, maintaining consistency with CLI behavior
         | 
| 1082 | 
            -
                            try:
         | 
| 1083 | 
            -
                                from claude_mpm.cli import _get_agent_versions_display
         | 
| 1084 | 
            -
                                agent_versions = _get_agent_versions_display()
         | 
| 1085 | 
            -
                                if agent_versions:
         | 
| 1086 | 
            -
                                    print(agent_versions)
         | 
| 1087 | 
            -
                                else:
         | 
| 1088 | 
            -
                                    print("No deployed agents found")
         | 
| 1089 | 
            -
                                    print("\nTo deploy agents, run: claude-mpm --mpm:agents deploy")
         | 
| 1090 | 
            -
                                
         | 
| 1091 | 
            -
                                if self.project_logger:
         | 
| 1092 | 
            -
                                    self.project_logger.log_system(
         | 
| 1093 | 
            -
                                        "Executed /mpm:agents command",
         | 
| 1094 | 
            -
                                        level="INFO",
         | 
| 1095 | 
            -
                                        component="command"
         | 
| 1096 | 
            -
                                    )
         | 
| 1097 | 
            -
                                return True
         | 
| 1098 | 
            -
                            except ImportError as e:
         | 
| 1099 | 
            -
                                print(f"Error: CLI module not available: {e}")
         | 
| 1100 | 
            -
                                return False
         | 
| 1101 | 
            -
                            except Exception as e:
         | 
| 1102 | 
            -
                                print(f"Error getting agent versions: {e}")
         | 
| 1103 | 
            -
                                return False
         | 
| 1104 | 
            -
                        else:
         | 
| 1105 | 
            -
                            print(f"Unknown command: {command}")
         | 
| 1106 | 
            -
                            print("Available commands: test, agents")
         | 
| 1107 | 
            -
                            return True
         | 
| 1108 | 
            -
                            
         | 
| 1109 | 
            -
                    except KeyboardInterrupt:
         | 
| 1110 | 
            -
                        print("\nCommand interrupted")
         | 
| 1111 | 
            -
                        return False
         | 
| 1112 | 
            -
                    except Exception as e:
         | 
| 1113 | 
            -
                        print(f"Error executing command: {e}")
         | 
| 1114 | 
            -
                        if self.project_logger:
         | 
| 1115 | 
            -
                            self.project_logger.log_system(
         | 
| 1116 | 
            -
                                f"Failed to execute /mpm: command: {e}",
         | 
| 1117 | 
            -
                                level="ERROR",
         | 
| 1118 | 
            -
                                component="command"
         | 
| 1119 | 
            -
                            )
         | 
| 704 | 
            +
                    """Handle /mpm: commands using the command handler service.
         | 
| 705 | 
            +
             | 
| 706 | 
            +
                    Delegates to the CommandHandlerService for command processing.
         | 
| 707 | 
            +
                    """
         | 
| 708 | 
            +
                    if self.command_handler_service:
         | 
| 709 | 
            +
                        return self.command_handler_service.handle_mpm_command(prompt)
         | 
| 710 | 
            +
                    else:
         | 
| 711 | 
            +
                        # Fallback if service not available
         | 
| 712 | 
            +
                        print("Command handler service not available")
         | 
| 1120 713 | 
             
                        return False
         | 
| 1121 | 
            -
             | 
| 714 | 
            +
             | 
| 1122 715 | 
             
                def _log_session_event(self, event_data: dict):
         | 
| 1123 | 
            -
                    """Log an event to the session log file."""
         | 
| 1124 | 
            -
                    if self. | 
| 1125 | 
            -
                         | 
| 1126 | 
            -
             | 
| 1127 | 
            -
             | 
| 1128 | 
            -
             | 
| 1129 | 
            -
             | 
| 1130 | 
            -
                            
         | 
| 1131 | 
            -
                            with open(self.session_log_file, 'a') as f:
         | 
| 1132 | 
            -
                                f.write(json.dumps(log_entry) + '\n')
         | 
| 1133 | 
            -
                        except (OSError, IOError) as e:
         | 
| 1134 | 
            -
                            self.logger.debug(f"IO error logging session event: {e}")
         | 
| 1135 | 
            -
                        except Exception as e:
         | 
| 1136 | 
            -
                            self.logger.debug(f"Failed to log session event: {e}")
         | 
| 1137 | 
            -
                
         | 
| 716 | 
            +
                    """Log an event to the session log file using the utility service."""
         | 
| 717 | 
            +
                    if self.utility_service:
         | 
| 718 | 
            +
                        self.utility_service.log_session_event(self.session_log_file, event_data)
         | 
| 719 | 
            +
                    else:
         | 
| 720 | 
            +
                        # Fallback if service not available
         | 
| 721 | 
            +
                        self.logger.debug("Utility service not available for session logging")
         | 
| 722 | 
            +
             | 
| 1138 723 | 
             
                def _get_version(self) -> str:
         | 
| 724 | 
            +
                    """Get version string using the version service.
         | 
| 725 | 
            +
             | 
| 726 | 
            +
                    Delegates to the VersionService for version detection and formatting.
         | 
| 1139 727 | 
             
                    """
         | 
| 1140 | 
            -
                     | 
| 1141 | 
            -
             | 
| 1142 | 
            -
                    WHY: The version display is critical for debugging and user experience.
         | 
| 1143 | 
            -
                    This implementation ensures we always show the correct version with build
         | 
| 1144 | 
            -
                    number for precise tracking of code changes.
         | 
| 1145 | 
            -
                    
         | 
| 1146 | 
            -
                    DESIGN DECISION: We combine semantic version with build number:
         | 
| 1147 | 
            -
                    - Semantic version (X.Y.Z) for API compatibility tracking
         | 
| 1148 | 
            -
                    - Build number for fine-grained code change tracking
         | 
| 1149 | 
            -
                    - Format: vX.Y.Z-BBBBB (5-digit zero-padded build number)
         | 
| 1150 | 
            -
                    
         | 
| 1151 | 
            -
                    Returns version string formatted as "vX.Y.Z-BBBBB"
         | 
| 1152 | 
            -
                    """
         | 
| 1153 | 
            -
                    version = "0.0.0"
         | 
| 1154 | 
            -
                    method_used = "default"
         | 
| 1155 | 
            -
                    build_number = None
         | 
| 1156 | 
            -
                    
         | 
| 1157 | 
            -
                    # Method 1: Try package import (fastest, most common)
         | 
| 1158 | 
            -
                    try:
         | 
| 1159 | 
            -
                        from claude_mpm import __version__
         | 
| 1160 | 
            -
                        version = __version__
         | 
| 1161 | 
            -
                        method_used = "package_import"
         | 
| 1162 | 
            -
                        self.logger.debug(f"Version obtained via package import: {version}")
         | 
| 1163 | 
            -
                        # If version already includes build number (PEP 440 format), extract it
         | 
| 1164 | 
            -
                        if '+build.' in version:
         | 
| 1165 | 
            -
                            parts = version.split('+build.')
         | 
| 1166 | 
            -
                            version = parts[0]  # Base version without build
         | 
| 1167 | 
            -
                            build_number = int(parts[1]) if len(parts) > 1 else None
         | 
| 1168 | 
            -
                            self.logger.debug(f"Extracted base version: {version}, build: {build_number}")
         | 
| 1169 | 
            -
                    except ImportError as e:
         | 
| 1170 | 
            -
                        self.logger.debug(f"Package import failed: {e}")
         | 
| 1171 | 
            -
                    except Exception as e:
         | 
| 1172 | 
            -
                        self.logger.warning(f"Unexpected error in package import: {e}")
         | 
| 1173 | 
            -
                    
         | 
| 1174 | 
            -
                    # Method 2: Try importlib.metadata (standard for installed packages)
         | 
| 1175 | 
            -
                    if version == "0.0.0":
         | 
| 1176 | 
            -
                        try:
         | 
| 1177 | 
            -
                            import importlib.metadata
         | 
| 1178 | 
            -
                            version = importlib.metadata.version('claude-mpm')
         | 
| 1179 | 
            -
                            method_used = "importlib_metadata"
         | 
| 1180 | 
            -
                            self.logger.debug(f"Version obtained via importlib.metadata: {version}")
         | 
| 1181 | 
            -
                        except importlib.metadata.PackageNotFoundError:
         | 
| 1182 | 
            -
                            self.logger.debug("Package not found in importlib.metadata (likely development install)")
         | 
| 1183 | 
            -
                        except ImportError:
         | 
| 1184 | 
            -
                            self.logger.debug("importlib.metadata not available (Python < 3.8)")
         | 
| 1185 | 
            -
                        except Exception as e:
         | 
| 1186 | 
            -
                            self.logger.warning(f"Unexpected error in importlib.metadata: {e}")
         | 
| 1187 | 
            -
                    
         | 
| 1188 | 
            -
                    # Method 3: Try reading VERSION file directly (development fallback)
         | 
| 1189 | 
            -
                    if version == "0.0.0":
         | 
| 1190 | 
            -
                        try:
         | 
| 1191 | 
            -
                            # Use centralized path management for VERSION file
         | 
| 1192 | 
            -
                            if paths.version_file.exists():
         | 
| 1193 | 
            -
                                version = paths.version_file.read_text().strip()
         | 
| 1194 | 
            -
                                method_used = "version_file"
         | 
| 1195 | 
            -
                                self.logger.debug(f"Version obtained via VERSION file: {version}")
         | 
| 1196 | 
            -
                            else:
         | 
| 1197 | 
            -
                                self.logger.debug(f"VERSION file not found at: {paths.version_file}")
         | 
| 1198 | 
            -
                        except Exception as e:
         | 
| 1199 | 
            -
                            self.logger.warning(f"Failed to read VERSION file: {e}")
         | 
| 1200 | 
            -
                    
         | 
| 1201 | 
            -
                    # Try to read build number (only if not already obtained from version string)
         | 
| 1202 | 
            -
                    if build_number is None:
         | 
| 1203 | 
            -
                        try:
         | 
| 1204 | 
            -
                            build_file = paths.project_root / "BUILD_NUMBER"
         | 
| 1205 | 
            -
                            if build_file.exists():
         | 
| 1206 | 
            -
                                build_content = build_file.read_text().strip()
         | 
| 1207 | 
            -
                                build_number = int(build_content)
         | 
| 1208 | 
            -
                                self.logger.debug(f"Build number obtained from file: {build_number}")
         | 
| 1209 | 
            -
                        except (ValueError, IOError) as e:
         | 
| 1210 | 
            -
                            self.logger.debug(f"Could not read BUILD_NUMBER: {e}")
         | 
| 1211 | 
            -
                            build_number = None
         | 
| 1212 | 
            -
                        except Exception as e:
         | 
| 1213 | 
            -
                            self.logger.debug(f"Unexpected error reading BUILD_NUMBER: {e}")
         | 
| 1214 | 
            -
                            build_number = None
         | 
| 1215 | 
            -
                    
         | 
| 1216 | 
            -
                    # Log final result
         | 
| 1217 | 
            -
                    if version == "0.0.0":
         | 
| 1218 | 
            -
                        self.logger.error(
         | 
| 1219 | 
            -
                            "All version detection methods failed. This indicates a packaging or installation issue."
         | 
| 1220 | 
            -
                        )
         | 
| 1221 | 
            -
                    else:
         | 
| 1222 | 
            -
                        self.logger.debug(f"Final version: {version} (method: {method_used})")
         | 
| 1223 | 
            -
                    
         | 
| 1224 | 
            -
                    # Format version with build number if available
         | 
| 1225 | 
            -
                    # For development: Use PEP 440 format (e.g., "3.9.5+build.275")
         | 
| 1226 | 
            -
                    # For UI/logging: Use dash format (e.g., "v3.9.5-build.275")
         | 
| 1227 | 
            -
                    # For PyPI releases: Use clean version (e.g., "3.9.5")
         | 
| 1228 | 
            -
                    
         | 
| 1229 | 
            -
                    # Determine formatting context (default to UI format for claude_runner)
         | 
| 1230 | 
            -
                    if build_number is not None:
         | 
| 1231 | 
            -
                        # UI/logging format with 'v' prefix and dash separator
         | 
| 1232 | 
            -
                        return f"v{version}-build.{build_number}"
         | 
| 728 | 
            +
                    if self.version_service:
         | 
| 729 | 
            +
                        return self.version_service.get_version()
         | 
| 1233 730 | 
             
                    else:
         | 
| 1234 | 
            -
                         | 
| 1235 | 
            -
             | 
| 1236 | 
            -
             | 
| 1237 | 
            -
                    """Register memory integration hooks with the hook service.
         | 
| 1238 | 
            -
                    
         | 
| 1239 | 
            -
                    WHY: This activates the memory system by registering hooks that automatically
         | 
| 1240 | 
            -
                    inject agent memory before delegation and extract learnings after delegation.
         | 
| 1241 | 
            -
                    This is the critical connection point between the memory system and the CLI.
         | 
| 1242 | 
            -
                    
         | 
| 1243 | 
            -
                    DESIGN DECISION: We register hooks here instead of in __init__ to ensure
         | 
| 1244 | 
            -
                    all services are initialized first. Hooks are only registered if the memory
         | 
| 1245 | 
            -
                    system is enabled in configuration.
         | 
| 1246 | 
            -
                    """
         | 
| 1247 | 
            -
                    try:
         | 
| 1248 | 
            -
                        # Only register if memory system is enabled
         | 
| 1249 | 
            -
                        if not self.config.get('memory.enabled', True):
         | 
| 1250 | 
            -
                            self.logger.debug("Memory system disabled - skipping hook registration")
         | 
| 1251 | 
            -
                            return
         | 
| 1252 | 
            -
                        
         | 
| 1253 | 
            -
                        # Import hook classes (lazy import to avoid circular dependencies)
         | 
| 1254 | 
            -
                        try:
         | 
| 1255 | 
            -
                            from claude_mpm.hooks.memory_integration_hook import (
         | 
| 1256 | 
            -
                                MemoryPreDelegationHook,
         | 
| 1257 | 
            -
                                MemoryPostDelegationHook
         | 
| 1258 | 
            -
                            )
         | 
| 1259 | 
            -
                        except ImportError as e:
         | 
| 1260 | 
            -
                            self.logger.warning(f"Memory integration hooks not available: {e}")
         | 
| 1261 | 
            -
                            return
         | 
| 1262 | 
            -
                        
         | 
| 1263 | 
            -
                        # Register pre-delegation hook for memory injection
         | 
| 1264 | 
            -
                        pre_hook = MemoryPreDelegationHook(self.config)
         | 
| 1265 | 
            -
                        success = self.hook_service.register_hook(pre_hook)
         | 
| 1266 | 
            -
                        if success:
         | 
| 1267 | 
            -
                            self.logger.info(f"✅ Registered memory pre-delegation hook (priority: {pre_hook.priority})")
         | 
| 1268 | 
            -
                        else:
         | 
| 1269 | 
            -
                            self.logger.warning("❌ Failed to register memory pre-delegation hook")
         | 
| 1270 | 
            -
                        
         | 
| 1271 | 
            -
                        # Register post-delegation hook if auto-learning is enabled
         | 
| 1272 | 
            -
                        if self.config.get('memory.auto_learning', True):  # Default to True now
         | 
| 1273 | 
            -
                            post_hook = MemoryPostDelegationHook(self.config)
         | 
| 1274 | 
            -
                            success = self.hook_service.register_hook(post_hook)
         | 
| 1275 | 
            -
                            if success:
         | 
| 1276 | 
            -
                                self.logger.info(f"✅ Registered memory post-delegation hook (priority: {post_hook.priority})")
         | 
| 1277 | 
            -
                            else:
         | 
| 1278 | 
            -
                                self.logger.warning("❌ Failed to register memory post-delegation hook")
         | 
| 1279 | 
            -
                        else:
         | 
| 1280 | 
            -
                            self.logger.info("ℹ️  Auto-learning disabled - skipping post-delegation hook")
         | 
| 1281 | 
            -
                        
         | 
| 1282 | 
            -
                        # Log summary of registered hooks
         | 
| 1283 | 
            -
                        hooks = self.hook_service.list_hooks()
         | 
| 1284 | 
            -
                        pre_count = len(hooks.get('pre_delegation', []))
         | 
| 1285 | 
            -
                        post_count = len(hooks.get('post_delegation', []))
         | 
| 1286 | 
            -
                        self.logger.info(f"📋 Hook Service initialized: {pre_count} pre-delegation, {post_count} post-delegation hooks")
         | 
| 1287 | 
            -
                        
         | 
| 1288 | 
            -
                    except AttributeError as e:
         | 
| 1289 | 
            -
                        self.logger.warning(f"Hook service not initialized properly: {e}")
         | 
| 1290 | 
            -
                    except Exception as e:
         | 
| 1291 | 
            -
                        self.logger.error(f"❌ Failed to register memory hooks: {e}")
         | 
| 1292 | 
            -
                        # Don't fail the entire initialization - memory system is optional
         | 
| 1293 | 
            -
                
         | 
| 731 | 
            +
                        # Fallback if service not available
         | 
| 732 | 
            +
                        return "v0.0.0"
         | 
| 733 | 
            +
             | 
| 1294 734 | 
             
                def _launch_subprocess_interactive(self, cmd: list, env: dict):
         | 
| 1295 735 | 
             
                    """Launch Claude as a subprocess with PTY for interactive mode.
         | 
| 1296 | 
            -
             | 
| 1297 | 
            -
                     | 
| 1298 | 
            -
                    (via --launch-method subprocess). Subprocess mode maintains the parent process,
         | 
| 1299 | 
            -
                    which can be useful for:
         | 
| 1300 | 
            -
                    1. Maintaining WebSocket connections and monitoring
         | 
| 1301 | 
            -
                    2. Providing proper cleanup and error handling
         | 
| 1302 | 
            -
                    3. Debugging and development scenarios
         | 
| 1303 | 
            -
                    
         | 
| 1304 | 
            -
                    DESIGN DECISION: We use PTY (pseudo-terminal) to maintain full interactive
         | 
| 1305 | 
            -
                    capabilities. Response logging is handled through the hook system, not I/O
         | 
| 1306 | 
            -
                    interception, for better performance and compatibility.
         | 
| 736 | 
            +
             | 
| 737 | 
            +
                    Delegates to the SubprocessLauncherService for subprocess management.
         | 
| 1307 738 | 
             
                    """
         | 
| 1308 | 
            -
                     | 
| 1309 | 
            -
             | 
| 1310 | 
            -
                     | 
| 1311 | 
            -
             | 
| 1312 | 
            -
             | 
| 1313 | 
            -
             | 
| 1314 | 
            -
                    # Note: Response logging is handled through the hook system,
         | 
| 1315 | 
            -
                    # not through I/O interception (better performance)
         | 
| 1316 | 
            -
                    
         | 
| 1317 | 
            -
                    # Save original terminal settings
         | 
| 1318 | 
            -
                    original_tty = None
         | 
| 1319 | 
            -
                    if sys.stdin.isatty():
         | 
| 1320 | 
            -
                        original_tty = termios.tcgetattr(sys.stdin)
         | 
| 1321 | 
            -
                    
         | 
| 1322 | 
            -
                    # Create PTY
         | 
| 1323 | 
            -
                    master_fd, slave_fd = pty.openpty()
         | 
| 1324 | 
            -
                    
         | 
| 1325 | 
            -
                    try:
         | 
| 1326 | 
            -
                        # Start Claude process
         | 
| 1327 | 
            -
                        process = subprocess.Popen(
         | 
| 1328 | 
            -
                            cmd,
         | 
| 1329 | 
            -
                            stdin=slave_fd,
         | 
| 1330 | 
            -
                            stdout=slave_fd,
         | 
| 1331 | 
            -
                            stderr=slave_fd,
         | 
| 1332 | 
            -
                            env=env
         | 
| 739 | 
            +
                    if self.subprocess_launcher_service:
         | 
| 740 | 
            +
                        self.subprocess_launcher_service.launch_subprocess_interactive(cmd, env)
         | 
| 741 | 
            +
                    else:
         | 
| 742 | 
            +
                        # Fallback if service is not available
         | 
| 743 | 
            +
                        self.logger.warning(
         | 
| 744 | 
            +
                            "Subprocess launcher service not available, cannot launch subprocess"
         | 
| 1333 745 | 
             
                        )
         | 
| 1334 | 
            -
                        
         | 
| 1335 | 
            -
                        # Close slave in parent
         | 
| 1336 | 
            -
                        os.close(slave_fd)
         | 
| 1337 | 
            -
                        
         | 
| 1338 | 
            -
                        if self.project_logger:
         | 
| 1339 | 
            -
                            self.project_logger.log_system(
         | 
| 1340 | 
            -
                                f"Claude subprocess started with PID {process.pid}",
         | 
| 1341 | 
            -
                                level="INFO",
         | 
| 1342 | 
            -
                                component="subprocess"
         | 
| 1343 | 
            -
                            )
         | 
| 1344 | 
            -
                        
         | 
| 1345 | 
            -
                        # Notify WebSocket clients
         | 
| 1346 | 
            -
                        if self.websocket_server:
         | 
| 1347 | 
            -
                            self.websocket_server.claude_status_changed(
         | 
| 1348 | 
            -
                                status="running",
         | 
| 1349 | 
            -
                                pid=process.pid,
         | 
| 1350 | 
            -
                                message="Claude subprocess started"
         | 
| 1351 | 
            -
                            )
         | 
| 1352 | 
            -
                        
         | 
| 1353 | 
            -
                        # Set terminal to raw mode for proper interaction
         | 
| 1354 | 
            -
                        if sys.stdin.isatty():
         | 
| 1355 | 
            -
                            tty.setraw(sys.stdin)
         | 
| 1356 | 
            -
                        
         | 
| 1357 | 
            -
                        # Handle Ctrl+C gracefully
         | 
| 1358 | 
            -
                        def signal_handler(signum, frame):
         | 
| 1359 | 
            -
                            if process.poll() is None:
         | 
| 1360 | 
            -
                                process.terminate()
         | 
| 1361 | 
            -
                            raise KeyboardInterrupt()
         | 
| 1362 | 
            -
                        
         | 
| 1363 | 
            -
                        signal.signal(signal.SIGINT, signal_handler)
         | 
| 1364 | 
            -
                        
         | 
| 1365 | 
            -
                        # I/O loop
         | 
| 1366 | 
            -
                        while True:
         | 
| 1367 | 
            -
                            # Check if process is still running
         | 
| 1368 | 
            -
                            if process.poll() is not None:
         | 
| 1369 | 
            -
                                break
         | 
| 1370 | 
            -
                            
         | 
| 1371 | 
            -
                            # Check for data from Claude or stdin
         | 
| 1372 | 
            -
                            r, _, _ = select.select([master_fd, sys.stdin], [], [], 0)
         | 
| 1373 | 
            -
                            
         | 
| 1374 | 
            -
                            if master_fd in r:
         | 
| 1375 | 
            -
                                try:
         | 
| 1376 | 
            -
                                    data = os.read(master_fd, 4096)
         | 
| 1377 | 
            -
                                    if data:
         | 
| 1378 | 
            -
                                        os.write(sys.stdout.fileno(), data)
         | 
| 1379 | 
            -
                                        # Broadcast output to WebSocket clients
         | 
| 1380 | 
            -
                                        if self.websocket_server:
         | 
| 1381 | 
            -
                                            try:
         | 
| 1382 | 
            -
                                                # Decode and send
         | 
| 1383 | 
            -
                                                output = data.decode('utf-8', errors='replace')
         | 
| 1384 | 
            -
                                                self.websocket_server.claude_output(output, "stdout")
         | 
| 1385 | 
            -
                                            except Exception as e:
         | 
| 1386 | 
            -
                                                self.logger.debug(f"Failed to broadcast output: {e}")
         | 
| 1387 | 
            -
                                    else:
         | 
| 1388 | 
            -
                                        break  # EOF
         | 
| 1389 | 
            -
                                except OSError:
         | 
| 1390 | 
            -
                                    break
         | 
| 1391 | 
            -
                            
         | 
| 1392 | 
            -
                            if sys.stdin in r:
         | 
| 1393 | 
            -
                                try:
         | 
| 1394 | 
            -
                                    data = os.read(sys.stdin.fileno(), 4096)
         | 
| 1395 | 
            -
                                    if data:
         | 
| 1396 | 
            -
                                        os.write(master_fd, data)
         | 
| 1397 | 
            -
                                except OSError:
         | 
| 1398 | 
            -
                                    break
         | 
| 1399 | 
            -
                        
         | 
| 1400 | 
            -
                        # Wait for process to complete
         | 
| 1401 | 
            -
                        process.wait()
         | 
| 1402 | 
            -
                        
         | 
| 1403 | 
            -
                        # Note: Response logging is handled through the hook system
         | 
| 1404 | 
            -
                        
         | 
| 1405 | 
            -
                        if self.project_logger:
         | 
| 1406 | 
            -
                            self.project_logger.log_system(
         | 
| 1407 | 
            -
                                f"Claude subprocess exited with code {process.returncode}",
         | 
| 1408 | 
            -
                                level="INFO",
         | 
| 1409 | 
            -
                                component="subprocess"
         | 
| 1410 | 
            -
                            )
         | 
| 1411 | 
            -
                        
         | 
| 1412 | 
            -
                        # Notify WebSocket clients
         | 
| 1413 | 
            -
                        if self.websocket_server:
         | 
| 1414 | 
            -
                            self.websocket_server.claude_status_changed(
         | 
| 1415 | 
            -
                                status="stopped",
         | 
| 1416 | 
            -
                                message=f"Claude subprocess exited with code {process.returncode}"
         | 
| 1417 | 
            -
                            )
         | 
| 1418 | 
            -
                        
         | 
| 1419 | 
            -
                    finally:
         | 
| 1420 | 
            -
                        # Restore terminal
         | 
| 1421 | 
            -
                        if original_tty and sys.stdin.isatty():
         | 
| 1422 | 
            -
                            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, original_tty)
         | 
| 1423 | 
            -
                        
         | 
| 1424 | 
            -
                        # Close PTY
         | 
| 1425 | 
            -
                        try:
         | 
| 1426 | 
            -
                            os.close(master_fd)
         | 
| 1427 | 
            -
                        except:
         | 
| 1428 | 
            -
                            pass
         | 
| 1429 | 
            -
                        
         | 
| 1430 | 
            -
                        # Ensure process is terminated
         | 
| 1431 | 
            -
                        if 'process' in locals() and process.poll() is None:
         | 
| 1432 | 
            -
                            process.terminate()
         | 
| 1433 | 
            -
                            try:
         | 
| 1434 | 
            -
                                process.wait(timeout=2)
         | 
| 1435 | 
            -
                            except subprocess.TimeoutExpired:
         | 
| 1436 | 
            -
                                process.kill()
         | 
| 1437 | 
            -
                                process.wait()
         | 
| 1438 | 
            -
                        
         | 
| 1439 | 
            -
                        # End WebSocket session if in subprocess mode
         | 
| 1440 | 
            -
                        if self.websocket_server:
         | 
| 1441 | 
            -
                            self.websocket_server.session_ended()
         | 
| 746 | 
            +
                        raise RuntimeError("Subprocess launcher service not available")
         | 
| 1442 747 |  | 
| 1443 748 |  | 
| 1444 749 | 
             
            def create_simple_context() -> str:
         | 
| @@ -1447,7 +752,7 @@ def create_simple_context() -> str: | |
| 1447 752 |  | 
| 1448 753 | 
             
            You have access to native subagents via the Task tool with subagent_type parameter:
         | 
| 1449 754 | 
             
            - engineer: For coding, implementation, and technical tasks
         | 
| 1450 | 
            -
            - qa: For testing, validation, and quality assurance | 
| 755 | 
            +
            - qa: For testing, validation, and quality assurance
         | 
| 1451 756 | 
             
            - documentation: For docs, guides, and explanations
         | 
| 1452 757 | 
             
            - research: For investigation and analysis
         | 
| 1453 758 | 
             
            - security: For security-related tasks
         | 
| @@ -1461,7 +766,7 @@ IMPORTANT: The Task tool accepts both naming formats: | |
| 1461 766 | 
             
            - Capitalized format: "Research", "Engineer", "QA", "Version Control", "Data Engineer"
         | 
| 1462 767 | 
             
            - Lowercase format: "research", "engineer", "qa", "version-control", "data-engineer"
         | 
| 1463 768 |  | 
| 1464 | 
            -
            Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes), | 
| 769 | 
            +
            Both formats work correctly. When you see capitalized names (matching TodoWrite prefixes),
         | 
| 1465 770 | 
             
            automatically normalize them to lowercase-hyphenated format for the Task tool.
         | 
| 1466 771 |  | 
| 1467 772 | 
             
            Work efficiently and delegate appropriately to subagents when needed."""
         | 
| @@ -1485,4 +790,4 @@ def run_claude_oneshot(prompt: str, context: Optional[str] = None) -> bool: | |
| 1485 790 | 
             
                runner = ClaudeRunner()
         | 
| 1486 791 | 
             
                if context is None:
         | 
| 1487 792 | 
             
                    context = create_simple_context()
         | 
| 1488 | 
            -
                return runner.run_oneshot(prompt, context)
         | 
| 793 | 
            +
                return runner.run_oneshot(prompt, context)
         |