claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__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 +1 -1
- 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 +203 -81
- 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 +305 -197
- 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 +104 -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/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 +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- 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 +120 -54
- 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 +223 -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 +145 -65
- 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 +170 -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.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.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.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            """Event Aggregator Service for Claude MPM.
         | 
| 2 4 |  | 
| 3 5 | 
             
            WHY: This service connects to the Socket.IO dashboard server as a client and
         | 
| @@ -11,43 +13,44 @@ aggregator to run alongside the dashboard without any conflicts. | |
| 11 13 |  | 
| 12 14 | 
             
            import asyncio
         | 
| 13 15 | 
             
            import json
         | 
| 14 | 
            -
            import logging
         | 
| 15 16 | 
             
            import os
         | 
| 16 17 | 
             
            import signal
         | 
| 17 18 | 
             
            import sys
         | 
| 18 19 | 
             
            import threading
         | 
| 19 20 | 
             
            import time
         | 
| 20 | 
            -
            from datetime import datetime
         | 
| 21 | 
            -
            from pathlib import Path
         | 
| 22 | 
            -
            from typing import Dict, Any, Optional, List
         | 
| 23 21 | 
             
            from collections import defaultdict
         | 
| 22 | 
            +
            from datetime import datetime
         | 
| 23 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 24 24 |  | 
| 25 25 | 
             
            try:
         | 
| 26 26 | 
             
                import socketio
         | 
| 27 | 
            +
             | 
| 27 28 | 
             
                SOCKETIO_AVAILABLE = True
         | 
| 28 29 | 
             
            except ImportError:
         | 
| 29 30 | 
             
                SOCKETIO_AVAILABLE = False
         | 
| 30 31 | 
             
                socketio = None
         | 
| 31 32 |  | 
| 32 | 
            -
            from ..models.agent_session import AgentSession, EventCategory
         | 
| 33 33 | 
             
            from ..core.logger import get_logger
         | 
| 34 | 
            +
            from ..models.agent_session import AgentSession, EventCategory
         | 
| 34 35 |  | 
| 35 36 |  | 
| 36 37 | 
             
            class EventAggregator:
         | 
| 37 38 | 
             
                """Aggregates Socket.IO events into complete agent sessions.
         | 
| 38 | 
            -
             | 
| 39 | 
            +
             | 
| 39 40 | 
             
                WHY: The dashboard emits events in real-time but doesn't persist complete
         | 
| 40 41 | 
             
                sessions. This service captures those events and builds structured session
         | 
| 41 42 | 
             
                documents for analysis and debugging.
         | 
| 42 | 
            -
             | 
| 43 | 
            +
             | 
| 43 44 | 
             
                DESIGN DECISION: We maintain active sessions in memory and save them when
         | 
| 44 45 | 
             
                they complete or after a timeout. This balances memory usage with the need
         | 
| 45 46 | 
             
                to capture all events even if a session doesn't complete cleanly.
         | 
| 46 47 | 
             
                """
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                def __init__( | 
| 48 | 
            +
             | 
| 49 | 
            +
                def __init__(
         | 
| 50 | 
            +
                    self, host: str = "localhost", port: int = 8765, save_dir: Optional[str] = None
         | 
| 51 | 
            +
                ):
         | 
| 49 52 | 
             
                    """Initialize the event aggregator.
         | 
| 50 | 
            -
             | 
| 53 | 
            +
             | 
| 51 54 | 
             
                    Args:
         | 
| 52 55 | 
             
                        host: Socket.IO server host
         | 
| 53 56 | 
             
                        port: Socket.IO server port
         | 
| @@ -56,65 +59,78 @@ class EventAggregator: | |
| 56 59 | 
             
                    self.host = host
         | 
| 57 60 | 
             
                    self.port = port
         | 
| 58 61 | 
             
                    self.logger = get_logger("event_aggregator")
         | 
| 59 | 
            -
             | 
| 62 | 
            +
             | 
| 60 63 | 
             
                    # Load configuration
         | 
| 61 64 | 
             
                    from claude_mpm.core.config import Config
         | 
| 65 | 
            +
             | 
| 62 66 | 
             
                    self.config = Config()
         | 
| 63 | 
            -
             | 
| 67 | 
            +
             | 
| 64 68 | 
             
                    # Session storage
         | 
| 65 69 | 
             
                    self.active_sessions: Dict[str, AgentSession] = {}
         | 
| 66 | 
            -
                    self.session_timeout =  | 
| 70 | 
            +
                    self.session_timeout = (
         | 
| 71 | 
            +
                        self.config.get("event_aggregator.session_timeout_minutes", 60) * 60
         | 
| 72 | 
            +
                    )
         | 
| 67 73 | 
             
                    self.last_activity: Dict[str, float] = {}
         | 
| 68 | 
            -
             | 
| 74 | 
            +
             | 
| 69 75 | 
             
                    # Save directory - use config or provided dir or default to .claude-mpm/activity
         | 
| 70 76 | 
             
                    if save_dir is None:
         | 
| 71 | 
            -
                        activity_dir = self.config.get( | 
| 72 | 
            -
             | 
| 77 | 
            +
                        activity_dir = self.config.get(
         | 
| 78 | 
            +
                            "event_aggregator.activity_directory", ".claude-mpm/activity"
         | 
| 79 | 
            +
                        )
         | 
| 80 | 
            +
                        self.save_dir = (
         | 
| 81 | 
            +
                            Path.cwd() / activity_dir
         | 
| 82 | 
            +
                            if not Path(activity_dir).is_absolute()
         | 
| 83 | 
            +
                            else Path(activity_dir)
         | 
| 84 | 
            +
                        )
         | 
| 73 85 | 
             
                    else:
         | 
| 74 86 | 
             
                        self.save_dir = Path(save_dir)
         | 
| 75 87 | 
             
                    self.save_dir.mkdir(parents=True, exist_ok=True)
         | 
| 76 | 
            -
             | 
| 88 | 
            +
             | 
| 77 89 | 
             
                    # Socket.IO client
         | 
| 78 90 | 
             
                    self.sio_client = None
         | 
| 79 91 | 
             
                    self.connected = False
         | 
| 80 92 | 
             
                    self.running = False
         | 
| 81 93 | 
             
                    self.client_thread = None
         | 
| 82 94 | 
             
                    self.client_loop = None
         | 
| 83 | 
            -
             | 
| 95 | 
            +
             | 
| 84 96 | 
             
                    # Event statistics
         | 
| 85 97 | 
             
                    self.total_events_captured = 0
         | 
| 86 98 | 
             
                    self.events_by_type = defaultdict(int)
         | 
| 87 99 | 
             
                    self.sessions_completed = 0
         | 
| 88 | 
            -
             | 
| 100 | 
            +
             | 
| 89 101 | 
             
                    # Cleanup task
         | 
| 90 102 | 
             
                    self.cleanup_task = None
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                    self.logger.info( | 
| 103 | 
            +
             | 
| 104 | 
            +
                    self.logger.info(
         | 
| 105 | 
            +
                        f"Event Aggregator initialized - will connect to {host}:{port}"
         | 
| 106 | 
            +
                    )
         | 
| 93 107 | 
             
                    self.logger.info(f"Sessions will be saved to: {self.save_dir}")
         | 
| 94 | 
            -
             | 
| 108 | 
            +
             | 
| 95 109 | 
             
                def start(self) -> bool:
         | 
| 96 110 | 
             
                    """Start the aggregator service.
         | 
| 97 | 
            -
             | 
| 111 | 
            +
             | 
| 98 112 | 
             
                    Returns:
         | 
| 99 113 | 
             
                        True if started successfully, False otherwise
         | 
| 100 114 | 
             
                    """
         | 
| 101 115 | 
             
                    if not SOCKETIO_AVAILABLE:
         | 
| 102 | 
            -
                        self.logger.error( | 
| 116 | 
            +
                        self.logger.error(
         | 
| 117 | 
            +
                            "Socket.IO client not available. Install python-socketio package."
         | 
| 118 | 
            +
                        )
         | 
| 103 119 | 
             
                        return False
         | 
| 104 | 
            -
             | 
| 120 | 
            +
             | 
| 105 121 | 
             
                    if self.running:
         | 
| 106 122 | 
             
                        self.logger.warning("Aggregator already running")
         | 
| 107 123 | 
             
                        return True
         | 
| 108 | 
            -
             | 
| 124 | 
            +
             | 
| 109 125 | 
             
                    self.running = True
         | 
| 110 | 
            -
             | 
| 126 | 
            +
             | 
| 111 127 | 
             
                    # Start the Socket.IO client in a background thread
         | 
| 112 128 | 
             
                    self.client_thread = threading.Thread(target=self._run_client, daemon=True)
         | 
| 113 129 | 
             
                    self.client_thread.start()
         | 
| 114 | 
            -
             | 
| 130 | 
            +
             | 
| 115 131 | 
             
                    # Wait a moment for connection
         | 
| 116 132 | 
             
                    time.sleep(1)
         | 
| 117 | 
            -
             | 
| 133 | 
            +
             | 
| 118 134 | 
             
                    if self.connected:
         | 
| 119 135 | 
             
                        self.logger.info("Event Aggregator started successfully")
         | 
| 120 136 | 
             
                        return True
         | 
| @@ -122,44 +138,45 @@ class EventAggregator: | |
| 122 138 | 
             
                        self.logger.error("Failed to connect to Socket.IO server")
         | 
| 123 139 | 
             
                        self.running = False
         | 
| 124 140 | 
             
                        return False
         | 
| 125 | 
            -
             | 
| 141 | 
            +
             | 
| 126 142 | 
             
                def stop(self):
         | 
| 127 143 | 
             
                    """Stop the aggregator service."""
         | 
| 128 144 | 
             
                    self.logger.info("Stopping Event Aggregator...")
         | 
| 129 145 | 
             
                    self.running = False
         | 
| 130 | 
            -
             | 
| 146 | 
            +
             | 
| 131 147 | 
             
                    # Save all active sessions
         | 
| 132 148 | 
             
                    self._save_all_sessions()
         | 
| 133 | 
            -
             | 
| 149 | 
            +
             | 
| 134 150 | 
             
                    # Disconnect Socket.IO client
         | 
| 135 151 | 
             
                    if self.sio_client and self.connected:
         | 
| 136 152 | 
             
                        try:
         | 
| 137 153 | 
             
                            asyncio.run_coroutine_threadsafe(
         | 
| 138 | 
            -
                                self.sio_client.disconnect(),
         | 
| 139 | 
            -
                                self.client_loop
         | 
| 154 | 
            +
                                self.sio_client.disconnect(), self.client_loop
         | 
| 140 155 | 
             
                            ).result(timeout=2)
         | 
| 141 156 | 
             
                        except:
         | 
| 142 157 | 
             
                            pass
         | 
| 143 | 
            -
             | 
| 158 | 
            +
             | 
| 144 159 | 
             
                    # Stop the client thread
         | 
| 145 160 | 
             
                    if self.client_thread and self.client_thread.is_alive():
         | 
| 146 161 | 
             
                        self.client_thread.join(timeout=3)
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                    self.logger.info( | 
| 162 | 
            +
             | 
| 163 | 
            +
                    self.logger.info(
         | 
| 164 | 
            +
                        f"Event Aggregator stopped - captured {self.total_events_captured} events"
         | 
| 165 | 
            +
                    )
         | 
| 149 166 | 
             
                    self.logger.info(f"Completed sessions: {self.sessions_completed}")
         | 
| 150 | 
            -
             | 
| 167 | 
            +
             | 
| 151 168 | 
             
                def _run_client(self):
         | 
| 152 169 | 
             
                    """Run the Socket.IO client in a background thread."""
         | 
| 153 170 | 
             
                    self.client_loop = asyncio.new_event_loop()
         | 
| 154 171 | 
             
                    asyncio.set_event_loop(self.client_loop)
         | 
| 155 | 
            -
             | 
| 172 | 
            +
             | 
| 156 173 | 
             
                    try:
         | 
| 157 174 | 
             
                        self.client_loop.run_until_complete(self._connect_and_listen())
         | 
| 158 175 | 
             
                    except Exception as e:
         | 
| 159 176 | 
             
                        self.logger.error(f"Client thread error: {e}")
         | 
| 160 177 | 
             
                    finally:
         | 
| 161 178 | 
             
                        self.client_loop.close()
         | 
| 162 | 
            -
             | 
| 179 | 
            +
             | 
| 163 180 | 
             
                async def _connect_and_listen(self):
         | 
| 164 181 | 
             
                    """Connect to Socket.IO server and listen for events."""
         | 
| 165 182 | 
             
                    try:
         | 
| @@ -167,24 +184,24 @@ class EventAggregator: | |
| 167 184 | 
             
                            reconnection=True,
         | 
| 168 185 | 
             
                            reconnection_attempts=0,  # Infinite retries
         | 
| 169 186 | 
             
                            reconnection_delay=1,
         | 
| 170 | 
            -
                            reconnection_delay_max=5
         | 
| 187 | 
            +
                            reconnection_delay_max=5,
         | 
| 171 188 | 
             
                        )
         | 
| 172 | 
            -
             | 
| 189 | 
            +
             | 
| 173 190 | 
             
                        # Register event handlers
         | 
| 174 191 | 
             
                        self._register_handlers()
         | 
| 175 | 
            -
             | 
| 192 | 
            +
             | 
| 176 193 | 
             
                        # Connect to server
         | 
| 177 | 
            -
                        url = f | 
| 194 | 
            +
                        url = f"http://{self.host}:{self.port}"
         | 
| 178 195 | 
             
                        self.logger.info(f"Connecting to Socket.IO server at {url}")
         | 
| 179 196 | 
             
                        await self.sio_client.connect(url)
         | 
| 180 | 
            -
             | 
| 197 | 
            +
             | 
| 181 198 | 
             
                        # Start cleanup task
         | 
| 182 199 | 
             
                        self.cleanup_task = asyncio.create_task(self._periodic_cleanup())
         | 
| 183 | 
            -
             | 
| 200 | 
            +
             | 
| 184 201 | 
             
                        # Keep running until stopped
         | 
| 185 202 | 
             
                        while self.running:
         | 
| 186 203 | 
             
                            await asyncio.sleep(0.5)
         | 
| 187 | 
            -
             | 
| 204 | 
            +
             | 
| 188 205 | 
             
                        # Cancel cleanup task
         | 
| 189 206 | 
             
                        if self.cleanup_task:
         | 
| 190 207 | 
             
                            self.cleanup_task.cancel()
         | 
| @@ -192,35 +209,33 @@ class EventAggregator: | |
| 192 209 | 
             
                                await self.cleanup_task
         | 
| 193 210 | 
             
                            except asyncio.CancelledError:
         | 
| 194 211 | 
             
                                pass
         | 
| 195 | 
            -
             | 
| 212 | 
            +
             | 
| 196 213 | 
             
                    except Exception as e:
         | 
| 197 214 | 
             
                        self.logger.error(f"Connection error: {e}")
         | 
| 198 215 | 
             
                        self.connected = False
         | 
| 199 | 
            -
             | 
| 216 | 
            +
             | 
| 200 217 | 
             
                def _register_handlers(self):
         | 
| 201 218 | 
             
                    """Register Socket.IO event handlers."""
         | 
| 202 | 
            -
             | 
| 219 | 
            +
             | 
| 203 220 | 
             
                    @self.sio_client.event
         | 
| 204 221 | 
             
                    async def connect():
         | 
| 205 222 | 
             
                        """Handle connection to server."""
         | 
| 206 223 | 
             
                        self.connected = True
         | 
| 207 224 | 
             
                        self.logger.info("Connected to Socket.IO server")
         | 
| 208 | 
            -
             | 
| 225 | 
            +
             | 
| 209 226 | 
             
                        # Request event history to catch up on any missed events
         | 
| 210 | 
            -
                        await self.sio_client.emit( | 
| 211 | 
            -
             | 
| 212 | 
            -
                        })
         | 
| 213 | 
            -
                    
         | 
| 227 | 
            +
                        await self.sio_client.emit("get_history", {"limit": 100})
         | 
| 228 | 
            +
             | 
| 214 229 | 
             
                    @self.sio_client.event
         | 
| 215 230 | 
             
                    async def disconnect():
         | 
| 216 231 | 
             
                        """Handle disconnection from server."""
         | 
| 217 232 | 
             
                        self.connected = False
         | 
| 218 233 | 
             
                        self.logger.warning("Disconnected from Socket.IO server")
         | 
| 219 | 
            -
             | 
| 234 | 
            +
             | 
| 220 235 | 
             
                    @self.sio_client.event
         | 
| 221 236 | 
             
                    async def claude_event(data):
         | 
| 222 237 | 
             
                        """Handle Claude events from the server.
         | 
| 223 | 
            -
             | 
| 238 | 
            +
             | 
| 224 239 | 
             
                        WHY: This is the main event handler that captures all events
         | 
| 225 240 | 
             
                        emitted by the dashboard and processes them into sessions.
         | 
| 226 241 | 
             
                        """
         | 
| @@ -228,143 +243,151 @@ class EventAggregator: | |
| 228 243 | 
             
                            await self._process_event(data)
         | 
| 229 244 | 
             
                        except Exception as e:
         | 
| 230 245 | 
             
                            self.logger.error(f"Error processing event: {e}")
         | 
| 231 | 
            -
             | 
| 246 | 
            +
             | 
| 232 247 | 
             
                    @self.sio_client.event
         | 
| 233 248 | 
             
                    async def history(data):
         | 
| 234 249 | 
             
                        """Handle historical events from the server.
         | 
| 235 | 
            -
             | 
| 250 | 
            +
             | 
| 236 251 | 
             
                        WHY: When we connect, we request recent history to ensure we
         | 
| 237 252 | 
             
                        don't miss events from sessions that started before we connected.
         | 
| 238 253 | 
             
                        """
         | 
| 239 254 | 
             
                        try:
         | 
| 240 | 
            -
                            events = data.get( | 
| 255 | 
            +
                            events = data.get("events", [])
         | 
| 241 256 | 
             
                            self.logger.info(f"Received {len(events)} historical events")
         | 
| 242 | 
            -
             | 
| 257 | 
            +
             | 
| 243 258 | 
             
                            for event in events:
         | 
| 244 259 | 
             
                                await self._process_event(event)
         | 
| 245 | 
            -
             | 
| 260 | 
            +
             | 
| 246 261 | 
             
                        except Exception as e:
         | 
| 247 262 | 
             
                            self.logger.error(f"Error processing history: {e}")
         | 
| 248 | 
            -
             | 
| 263 | 
            +
             | 
| 249 264 | 
             
                async def _process_event(self, event_data: Dict[str, Any]):
         | 
| 250 265 | 
             
                    """Process a single event and add it to the appropriate session.
         | 
| 251 | 
            -
             | 
| 266 | 
            +
             | 
| 252 267 | 
             
                    WHY: Each event needs to be routed to the correct session and
         | 
| 253 268 | 
             
                    processed according to its type.
         | 
| 254 269 | 
             
                    """
         | 
| 255 270 | 
             
                    try:
         | 
| 256 271 | 
             
                        # Extract event metadata
         | 
| 257 | 
            -
                        event_type = event_data.get( | 
| 258 | 
            -
                        timestamp = event_data.get( | 
| 259 | 
            -
                        data = event_data.get( | 
| 260 | 
            -
             | 
| 272 | 
            +
                        event_type = event_data.get("type", "unknown")
         | 
| 273 | 
            +
                        timestamp = event_data.get("timestamp", datetime.utcnow().isoformat() + "Z")
         | 
| 274 | 
            +
                        data = event_data.get("data", {})
         | 
| 275 | 
            +
             | 
| 261 276 | 
             
                        # Update statistics
         | 
| 262 277 | 
             
                        self.total_events_captured += 1
         | 
| 263 278 | 
             
                        self.events_by_type[event_type] += 1
         | 
| 264 | 
            -
             | 
| 279 | 
            +
             | 
| 265 280 | 
             
                        # Determine session ID
         | 
| 266 281 | 
             
                        session_id = self._extract_session_id(event_type, data)
         | 
| 267 | 
            -
             | 
| 282 | 
            +
             | 
| 268 283 | 
             
                        if not session_id:
         | 
| 269 284 | 
             
                            # Some events don't belong to a specific session
         | 
| 270 285 | 
             
                            self.logger.debug(f"Event {event_type} has no session ID, skipping")
         | 
| 271 286 | 
             
                            return
         | 
| 272 | 
            -
             | 
| 287 | 
            +
             | 
| 273 288 | 
             
                        # Get or create session
         | 
| 274 | 
            -
                        session = self._get_or_create_session( | 
| 275 | 
            -
             | 
| 289 | 
            +
                        session = self._get_or_create_session(
         | 
| 290 | 
            +
                            session_id, event_type, data, timestamp
         | 
| 291 | 
            +
                        )
         | 
| 292 | 
            +
             | 
| 276 293 | 
             
                        # Add event to session
         | 
| 277 294 | 
             
                        session.add_event(event_type, data, timestamp)
         | 
| 278 | 
            -
             | 
| 295 | 
            +
             | 
| 279 296 | 
             
                        # Update last activity time
         | 
| 280 297 | 
             
                        self.last_activity[session_id] = time.time()
         | 
| 281 | 
            -
             | 
| 298 | 
            +
             | 
| 282 299 | 
             
                        # Check if session ended
         | 
| 283 | 
            -
                        if event_type in [ | 
| 300 | 
            +
                        if event_type in ["session.end", "Stop"]:
         | 
| 284 301 | 
             
                            await self._finalize_session(session_id)
         | 
| 285 | 
            -
             | 
| 302 | 
            +
             | 
| 286 303 | 
             
                        # Log progress periodically
         | 
| 287 304 | 
             
                        if self.total_events_captured % 100 == 0:
         | 
| 288 | 
            -
                            self.logger.info( | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 305 | 
            +
                            self.logger.info(
         | 
| 306 | 
            +
                                f"Processed {self.total_events_captured} events, "
         | 
| 307 | 
            +
                                f"active sessions: {len(self.active_sessions)}"
         | 
| 308 | 
            +
                            )
         | 
| 309 | 
            +
             | 
| 291 310 | 
             
                    except Exception as e:
         | 
| 292 | 
            -
                        self.logger.error( | 
| 293 | 
            -
             | 
| 294 | 
            -
             | 
| 311 | 
            +
                        self.logger.error(
         | 
| 312 | 
            +
                            f"Error processing event {event_data.get('type', 'unknown')}: {e}"
         | 
| 313 | 
            +
                        )
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                def _extract_session_id(
         | 
| 316 | 
            +
                    self, event_type: str, data: Dict[str, Any]
         | 
| 317 | 
            +
                ) -> Optional[str]:
         | 
| 295 318 | 
             
                    """Extract session ID from event data.
         | 
| 296 | 
            -
             | 
| 319 | 
            +
             | 
| 297 320 | 
             
                    WHY: Events use different field names for session ID depending on
         | 
| 298 321 | 
             
                    their source and type.
         | 
| 299 322 | 
             
                    """
         | 
| 300 323 | 
             
                    # Try common session ID fields
         | 
| 301 324 | 
             
                    session_id = (
         | 
| 302 | 
            -
                        data.get( | 
| 303 | 
            -
                        data.get( | 
| 304 | 
            -
                        data.get( | 
| 305 | 
            -
                        data.get( | 
| 325 | 
            +
                        data.get("session_id")
         | 
| 326 | 
            +
                        or data.get("sessionId")
         | 
| 327 | 
            +
                        or data.get("session")
         | 
| 328 | 
            +
                        or data.get("sid")
         | 
| 306 329 | 
             
                    )
         | 
| 307 | 
            -
             | 
| 330 | 
            +
             | 
| 308 331 | 
             
                    # For session.start events, the session_id is the key piece of data
         | 
| 309 | 
            -
                    if event_type ==  | 
| 310 | 
            -
                        return data[ | 
| 311 | 
            -
             | 
| 332 | 
            +
                    if event_type == "session.start" and "session_id" in data:
         | 
| 333 | 
            +
                        return data["session_id"]
         | 
| 334 | 
            +
             | 
| 312 335 | 
             
                    # For hook events, check nested data
         | 
| 313 336 | 
             
                    if not session_id and isinstance(data, dict):
         | 
| 314 | 
            -
                        for key in [ | 
| 337 | 
            +
                        for key in ["hook_data", "event_data", "context"]:
         | 
| 315 338 | 
             
                            if key in data and isinstance(data[key], dict):
         | 
| 316 | 
            -
                                nested_id = data[key].get( | 
| 339 | 
            +
                                nested_id = data[key].get("session_id") or data[key].get(
         | 
| 340 | 
            +
                                    "sessionId"
         | 
| 341 | 
            +
                                )
         | 
| 317 342 | 
             
                                if nested_id:
         | 
| 318 343 | 
             
                                    return nested_id
         | 
| 319 | 
            -
             | 
| 344 | 
            +
             | 
| 320 345 | 
             
                    return session_id
         | 
| 321 | 
            -
             | 
| 322 | 
            -
                def _get_or_create_session( | 
| 323 | 
            -
             | 
| 346 | 
            +
             | 
| 347 | 
            +
                def _get_or_create_session(
         | 
| 348 | 
            +
                    self, session_id: str, event_type: str, data: Dict[str, Any], timestamp: str
         | 
| 349 | 
            +
                ) -> AgentSession:
         | 
| 324 350 | 
             
                    """Get existing session or create a new one.
         | 
| 325 | 
            -
             | 
| 351 | 
            +
             | 
| 326 352 | 
             
                    WHY: Sessions are created on demand when we see the first event
         | 
| 327 353 | 
             
                    for a new session ID.
         | 
| 328 354 | 
             
                    """
         | 
| 329 355 | 
             
                    if session_id not in self.active_sessions:
         | 
| 330 356 | 
             
                        # Create new session
         | 
| 331 | 
            -
                        session = AgentSession(
         | 
| 332 | 
            -
             | 
| 333 | 
            -
                            start_time=timestamp
         | 
| 334 | 
            -
                        )
         | 
| 335 | 
            -
                        
         | 
| 357 | 
            +
                        session = AgentSession(session_id=session_id, start_time=timestamp)
         | 
| 358 | 
            +
             | 
| 336 359 | 
             
                        # Extract initial metadata if this is a session.start event
         | 
| 337 | 
            -
                        if event_type ==  | 
| 338 | 
            -
                            session.working_directory = data.get( | 
| 339 | 
            -
                            session.launch_method = data.get( | 
| 340 | 
            -
                            session.claude_pid = data.get( | 
| 341 | 
            -
             | 
| 360 | 
            +
                        if event_type == "session.start":
         | 
| 361 | 
            +
                            session.working_directory = data.get("working_directory", "")
         | 
| 362 | 
            +
                            session.launch_method = data.get("launch_method", "")
         | 
| 363 | 
            +
                            session.claude_pid = data.get("pid")
         | 
| 364 | 
            +
             | 
| 342 365 | 
             
                            # Try to get git branch and project info
         | 
| 343 | 
            -
                            instance_info = data.get( | 
| 344 | 
            -
                            session.git_branch = instance_info.get( | 
| 345 | 
            -
                            session.project_root = instance_info.get( | 
| 346 | 
            -
             | 
| 366 | 
            +
                            instance_info = data.get("instance_info", {})
         | 
| 367 | 
            +
                            session.git_branch = instance_info.get("git_branch")
         | 
| 368 | 
            +
                            session.project_root = instance_info.get("working_dir")
         | 
| 369 | 
            +
             | 
| 347 370 | 
             
                        self.active_sessions[session_id] = session
         | 
| 348 371 | 
             
                        self.last_activity[session_id] = time.time()
         | 
| 349 | 
            -
             | 
| 372 | 
            +
             | 
| 350 373 | 
             
                        self.logger.info(f"Created new session: {session_id[:8]}...")
         | 
| 351 | 
            -
             | 
| 374 | 
            +
             | 
| 352 375 | 
             
                    return self.active_sessions[session_id]
         | 
| 353 | 
            -
             | 
| 376 | 
            +
             | 
| 354 377 | 
             
                async def _finalize_session(self, session_id: str):
         | 
| 355 378 | 
             
                    """Finalize and save a completed session.
         | 
| 356 | 
            -
             | 
| 379 | 
            +
             | 
| 357 380 | 
             
                    WHY: When a session ends, we need to calculate final metrics
         | 
| 358 381 | 
             
                    and persist it to disk.
         | 
| 359 382 | 
             
                    """
         | 
| 360 383 | 
             
                    if session_id not in self.active_sessions:
         | 
| 361 384 | 
             
                        return
         | 
| 362 | 
            -
             | 
| 385 | 
            +
             | 
| 363 386 | 
             
                    session = self.active_sessions[session_id]
         | 
| 364 | 
            -
             | 
| 387 | 
            +
             | 
| 365 388 | 
             
                    # Finalize the session
         | 
| 366 389 | 
             
                    session.finalize()
         | 
| 367 | 
            -
             | 
| 390 | 
            +
             | 
| 368 391 | 
             
                    # Save to file
         | 
| 369 392 | 
             
                    try:
         | 
| 370 393 | 
             
                        filepath = session.save_to_file(self.save_dir)
         | 
| @@ -372,20 +395,22 @@ class EventAggregator: | |
| 372 395 | 
             
                        self.logger.info(f"  - Events: {session.metrics.total_events}")
         | 
| 373 396 | 
             
                        self.logger.info(f"  - Delegations: {session.metrics.total_delegations}")
         | 
| 374 397 | 
             
                        self.logger.info(f"  - Tools used: {len(session.metrics.tools_used)}")
         | 
| 375 | 
            -
                        self.logger.info( | 
| 376 | 
            -
             | 
| 398 | 
            +
                        self.logger.info(
         | 
| 399 | 
            +
                            f"  - Files modified: {len(session.metrics.files_modified)}"
         | 
| 400 | 
            +
                        )
         | 
| 401 | 
            +
             | 
| 377 402 | 
             
                        self.sessions_completed += 1
         | 
| 378 403 | 
             
                    except Exception as e:
         | 
| 379 404 | 
             
                        self.logger.error(f"Failed to save session {session_id}: {e}")
         | 
| 380 | 
            -
             | 
| 405 | 
            +
             | 
| 381 406 | 
             
                    # Remove from active sessions
         | 
| 382 407 | 
             
                    del self.active_sessions[session_id]
         | 
| 383 408 | 
             
                    if session_id in self.last_activity:
         | 
| 384 409 | 
             
                        del self.last_activity[session_id]
         | 
| 385 | 
            -
             | 
| 410 | 
            +
             | 
| 386 411 | 
             
                async def _periodic_cleanup(self):
         | 
| 387 412 | 
             
                    """Periodically clean up inactive sessions.
         | 
| 388 | 
            -
             | 
| 413 | 
            +
             | 
| 389 414 | 
             
                    WHY: Some sessions may not complete cleanly, so we need to
         | 
| 390 415 | 
             
                    periodically save and remove inactive sessions to prevent
         | 
| 391 416 | 
             
                    memory leaks.
         | 
| @@ -393,26 +418,28 @@ class EventAggregator: | |
| 393 418 | 
             
                    while self.running:
         | 
| 394 419 | 
             
                        try:
         | 
| 395 420 | 
             
                            await asyncio.sleep(60)  # Check every minute
         | 
| 396 | 
            -
             | 
| 421 | 
            +
             | 
| 397 422 | 
             
                            current_time = time.time()
         | 
| 398 423 | 
             
                            sessions_to_finalize = []
         | 
| 399 | 
            -
             | 
| 424 | 
            +
             | 
| 400 425 | 
             
                            for session_id, last_time in list(self.last_activity.items()):
         | 
| 401 426 | 
             
                                if current_time - last_time > self.session_timeout:
         | 
| 402 427 | 
             
                                    sessions_to_finalize.append(session_id)
         | 
| 403 | 
            -
             | 
| 428 | 
            +
             | 
| 404 429 | 
             
                            for session_id in sessions_to_finalize:
         | 
| 405 | 
            -
                                self.logger.info( | 
| 430 | 
            +
                                self.logger.info(
         | 
| 431 | 
            +
                                    f"Finalizing inactive session: {session_id[:8]}..."
         | 
| 432 | 
            +
                                )
         | 
| 406 433 | 
             
                                await self._finalize_session(session_id)
         | 
| 407 | 
            -
             | 
| 434 | 
            +
             | 
| 408 435 | 
             
                        except asyncio.CancelledError:
         | 
| 409 436 | 
             
                            break
         | 
| 410 437 | 
             
                        except Exception as e:
         | 
| 411 438 | 
             
                            self.logger.error(f"Error in cleanup task: {e}")
         | 
| 412 | 
            -
             | 
| 439 | 
            +
             | 
| 413 440 | 
             
                def _save_all_sessions(self):
         | 
| 414 441 | 
             
                    """Save all active sessions to disk.
         | 
| 415 | 
            -
             | 
| 442 | 
            +
             | 
| 416 443 | 
             
                    WHY: Called on shutdown to ensure we don't lose any data.
         | 
| 417 444 | 
             
                    """
         | 
| 418 445 | 
             
                    for session_id in list(self.active_sessions.keys()):
         | 
| @@ -420,84 +447,95 @@ class EventAggregator: | |
| 420 447 | 
             
                            session = self.active_sessions[session_id]
         | 
| 421 448 | 
             
                            session.finalize()
         | 
| 422 449 | 
             
                            filepath = session.save_to_file(self.save_dir)
         | 
| 423 | 
            -
                            self.logger.info( | 
| 450 | 
            +
                            self.logger.info(
         | 
| 451 | 
            +
                                f"Saved active session {session_id[:8]}... to {filepath}"
         | 
| 452 | 
            +
                            )
         | 
| 424 453 | 
             
                        except Exception as e:
         | 
| 425 454 | 
             
                            self.logger.error(f"Failed to save session {session_id}: {e}")
         | 
| 426 | 
            -
             | 
| 455 | 
            +
             | 
| 427 456 | 
             
                def get_status(self) -> Dict[str, Any]:
         | 
| 428 457 | 
             
                    """Get current status of the aggregator.
         | 
| 429 | 
            -
             | 
| 458 | 
            +
             | 
| 430 459 | 
             
                    Returns:
         | 
| 431 460 | 
             
                        Status dictionary with metrics and state
         | 
| 432 461 | 
             
                    """
         | 
| 433 462 | 
             
                    return {
         | 
| 434 | 
            -
                         | 
| 435 | 
            -
                         | 
| 436 | 
            -
                         | 
| 437 | 
            -
                         | 
| 438 | 
            -
                         | 
| 439 | 
            -
                         | 
| 440 | 
            -
                         | 
| 441 | 
            -
                         | 
| 442 | 
            -
                         | 
| 463 | 
            +
                        "running": self.running,
         | 
| 464 | 
            +
                        "connected": self.connected,
         | 
| 465 | 
            +
                        "server": f"{self.host}:{self.port}",
         | 
| 466 | 
            +
                        "save_directory": str(self.save_dir),
         | 
| 467 | 
            +
                        "active_sessions": len(self.active_sessions),
         | 
| 468 | 
            +
                        "sessions_completed": self.sessions_completed,
         | 
| 469 | 
            +
                        "total_events": self.total_events_captured,
         | 
| 470 | 
            +
                        "events_by_type": dict(self.events_by_type),
         | 
| 471 | 
            +
                        "active_session_ids": [
         | 
| 472 | 
            +
                            sid[:8] + "..." for sid in self.active_sessions.keys()
         | 
| 473 | 
            +
                        ],
         | 
| 443 474 | 
             
                    }
         | 
| 444 | 
            -
             | 
| 475 | 
            +
             | 
| 445 476 | 
             
                def list_sessions(self, limit: int = 10) -> List[Dict[str, Any]]:
         | 
| 446 477 | 
             
                    """List captured sessions.
         | 
| 447 | 
            -
             | 
| 478 | 
            +
             | 
| 448 479 | 
             
                    Args:
         | 
| 449 480 | 
             
                        limit: Maximum number of sessions to return
         | 
| 450 | 
            -
             | 
| 481 | 
            +
             | 
| 451 482 | 
             
                    Returns:
         | 
| 452 483 | 
             
                        List of session summaries
         | 
| 453 484 | 
             
                    """
         | 
| 454 485 | 
             
                    sessions = []
         | 
| 455 | 
            -
             | 
| 486 | 
            +
             | 
| 456 487 | 
             
                    # Get saved session files
         | 
| 457 488 | 
             
                    session_files = sorted(
         | 
| 458 | 
            -
                        self.save_dir.glob( | 
| 489 | 
            +
                        self.save_dir.glob("session_*.json"),
         | 
| 459 490 | 
             
                        key=lambda p: p.stat().st_mtime,
         | 
| 460 | 
            -
                        reverse=True
         | 
| 491 | 
            +
                        reverse=True,
         | 
| 461 492 | 
             
                    )[:limit]
         | 
| 462 | 
            -
             | 
| 493 | 
            +
             | 
| 463 494 | 
             
                    for filepath in session_files:
         | 
| 464 495 | 
             
                        try:
         | 
| 465 496 | 
             
                            # Load just the metadata, not the full session
         | 
| 466 | 
            -
                            with open(filepath,  | 
| 497 | 
            +
                            with open(filepath, "r") as f:
         | 
| 467 498 | 
             
                                data = json.load(f)
         | 
| 468 | 
            -
             | 
| 469 | 
            -
                            sessions.append( | 
| 470 | 
            -
                                 | 
| 471 | 
            -
             | 
| 472 | 
            -
             | 
| 473 | 
            -
             | 
| 474 | 
            -
             | 
| 475 | 
            -
             | 
| 476 | 
            -
             | 
| 477 | 
            -
             | 
| 478 | 
            -
             | 
| 499 | 
            +
             | 
| 500 | 
            +
                            sessions.append(
         | 
| 501 | 
            +
                                {
         | 
| 502 | 
            +
                                    "file": filepath.name,
         | 
| 503 | 
            +
                                    "session_id": data.get("session_id", "unknown")[:8] + "...",
         | 
| 504 | 
            +
                                    "start_time": data.get("start_time", "unknown"),
         | 
| 505 | 
            +
                                    "end_time": data.get("end_time", "unknown"),
         | 
| 506 | 
            +
                                    "events": data.get("metrics", {}).get("total_events", 0),
         | 
| 507 | 
            +
                                    "delegations": data.get("metrics", {}).get(
         | 
| 508 | 
            +
                                        "total_delegations", 0
         | 
| 509 | 
            +
                                    ),
         | 
| 510 | 
            +
                                    "initial_prompt": (
         | 
| 511 | 
            +
                                        (data.get("initial_prompt", "")[:50] + "...")
         | 
| 512 | 
            +
                                        if data.get("initial_prompt")
         | 
| 513 | 
            +
                                        else "N/A"
         | 
| 514 | 
            +
                                    ),
         | 
| 515 | 
            +
                                }
         | 
| 516 | 
            +
                            )
         | 
| 479 517 | 
             
                        except Exception as e:
         | 
| 480 518 | 
             
                            self.logger.error(f"Error reading session file {filepath}: {e}")
         | 
| 481 | 
            -
             | 
| 519 | 
            +
             | 
| 482 520 | 
             
                    return sessions
         | 
| 483 | 
            -
             | 
| 521 | 
            +
             | 
| 484 522 | 
             
                def load_session(self, session_id_prefix: str) -> Optional[AgentSession]:
         | 
| 485 523 | 
             
                    """Load a session by ID prefix.
         | 
| 486 | 
            -
             | 
| 524 | 
            +
             | 
| 487 525 | 
             
                    Args:
         | 
| 488 526 | 
             
                        session_id_prefix: First few characters of session ID
         | 
| 489 | 
            -
             | 
| 527 | 
            +
             | 
| 490 528 | 
             
                    Returns:
         | 
| 491 529 | 
             
                        AgentSession if found, None otherwise
         | 
| 492 530 | 
             
                    """
         | 
| 493 531 | 
             
                    # Search for matching session file
         | 
| 494 | 
            -
                    for filepath in self.save_dir.glob( | 
| 532 | 
            +
                    for filepath in self.save_dir.glob("session_*.json"):
         | 
| 495 533 | 
             
                        if session_id_prefix in filepath.name:
         | 
| 496 534 | 
             
                            try:
         | 
| 497 535 | 
             
                                return AgentSession.load_from_file(str(filepath))
         | 
| 498 536 | 
             
                            except Exception as e:
         | 
| 499 537 | 
             
                                self.logger.error(f"Error loading session from {filepath}: {e}")
         | 
| 500 | 
            -
             | 
| 538 | 
            +
             | 
| 501 539 | 
             
                    return None
         | 
| 502 540 |  | 
| 503 541 |  | 
| @@ -544,4 +582,4 @@ def _signal_handler(signum, frame): | |
| 544 582 |  | 
| 545 583 | 
             
            # Register signal handlers
         | 
| 546 584 | 
             
            signal.signal(signal.SIGINT, _signal_handler)
         | 
| 547 | 
            -
            signal.signal(signal.SIGTERM, _signal_handler)
         | 
| 585 | 
            +
            signal.signal(signal.SIGTERM, _signal_handler)
         |