claude-mpm 3.1.3__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/__init__.py +3 -3
 - claude_mpm/__main__.py +0 -17
 - claude_mpm/agents/INSTRUCTIONS.md +149 -17
 - claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
 - claude_mpm/agents/base_agent.json +1 -1
 - claude_mpm/agents/templates/pm.json +25 -0
 - claude_mpm/agents/templates/research.json +2 -1
 - claude_mpm/cli/__init__.py +19 -23
 - claude_mpm/cli/commands/__init__.py +3 -1
 - claude_mpm/cli/commands/agents.py +7 -18
 - claude_mpm/cli/commands/info.py +5 -10
 - claude_mpm/cli/commands/memory.py +232 -0
 - claude_mpm/cli/commands/run.py +501 -28
 - claude_mpm/cli/commands/tickets.py +10 -17
 - claude_mpm/cli/commands/ui.py +15 -37
 - claude_mpm/cli/parser.py +91 -1
 - claude_mpm/cli/utils.py +9 -28
 - claude_mpm/config/socketio_config.py +256 -0
 - claude_mpm/constants.py +9 -0
 - claude_mpm/core/__init__.py +2 -2
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/claude_runner.py +919 -0
 - claude_mpm/core/config.py +21 -1
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/hook_manager.py +196 -0
 - claude_mpm/core/pm_hook_interceptor.py +205 -0
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +323 -33
 - claude_mpm/core/socketio_pool.py +582 -0
 - claude_mpm/core/websocket_handler.py +233 -0
 - claude_mpm/deployment_paths.py +261 -0
 - claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
 - claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
 - claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
 - claude_mpm/hooks/memory_integration_hook.py +312 -0
 - claude_mpm/models/__init__.py +9 -91
 - claude_mpm/orchestration/__init__.py +1 -1
 - claude_mpm/scripts/claude-mpm-socketio +32 -0
 - claude_mpm/scripts/claude_mpm_monitor.html +567 -0
 - claude_mpm/scripts/install_socketio_server.py +407 -0
 - claude_mpm/scripts/launch_monitor.py +132 -0
 - claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
 - claude_mpm/scripts/manage_version.py +479 -0
 - claude_mpm/scripts/socketio_daemon.py +181 -0
 - claude_mpm/scripts/socketio_server_manager.py +428 -0
 - claude_mpm/services/__init__.py +5 -0
 - claude_mpm/services/agent_lifecycle_manager.py +76 -25
 - claude_mpm/services/agent_memory_manager.py +684 -0
 - claude_mpm/services/agent_modification_tracker.py +98 -17
 - claude_mpm/services/agent_persistence_service.py +33 -13
 - claude_mpm/services/agent_registry.py +82 -43
 - claude_mpm/services/hook_service.py +362 -0
 - claude_mpm/services/socketio_client_manager.py +474 -0
 - claude_mpm/services/socketio_server.py +922 -0
 - claude_mpm/services/standalone_socketio_server.py +631 -0
 - claude_mpm/services/ticket_manager.py +4 -5
 - claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
 - claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
 - claude_mpm/services/version_control/semantic_versioning.py +9 -10
 - claude_mpm/services/websocket_server.py +376 -0
 - claude_mpm/utils/dependency_manager.py +211 -0
 - claude_mpm/utils/import_migration_example.py +80 -0
 - claude_mpm/utils/path_operations.py +0 -20
 - claude_mpm/web/open_dashboard.py +34 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/METADATA +20 -9
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/RECORD +71 -50
 - claude_mpm-3.3.0.dist-info/entry_points.txt +7 -0
 - claude_mpm/cli_old.py +0 -728
 - claude_mpm/models/common.py +0 -41
 - claude_mpm/models/lifecycle.py +0 -97
 - claude_mpm/models/modification.py +0 -126
 - claude_mpm/models/persistence.py +0 -57
 - claude_mpm/models/registry.py +0 -91
 - claude_mpm/security/__init__.py +0 -8
 - claude_mpm/security/bash_validator.py +0 -393
 - claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
 - /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/top_level.txt +0 -0
 
| 
         @@ -0,0 +1,362 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Hook service for managing pre and post delegation hooks.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            WHY: The agent system needs a centralized way to manage hooks that can modify
         
     | 
| 
      
 4 
     | 
    
         
            +
            behavior before and after agent delegation. This service provides a clean API
         
     | 
| 
      
 5 
     | 
    
         
            +
            for registering, prioritizing, and executing hooks while handling errors gracefully.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            DESIGN DECISION: We chose to have separate lists for pre/post delegation hooks
         
     | 
| 
      
 8 
     | 
    
         
            +
            rather than a single list with type checking because:
         
     | 
| 
      
 9 
     | 
    
         
            +
            - It's more performant (no filtering needed during execution)
         
     | 
| 
      
 10 
     | 
    
         
            +
            - It's clearer in the API (register_pre_delegation_hook vs checking type)
         
     | 
| 
      
 11 
     | 
    
         
            +
            - It prevents accidentally mixing hook types
         
     | 
| 
      
 12 
     | 
    
         
            +
            """
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            from typing import List, Optional, Dict, Any
         
     | 
| 
      
 15 
     | 
    
         
            +
            import time
         
     | 
| 
      
 16 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            from claude_mpm.core.config import Config
         
     | 
| 
      
 19 
     | 
    
         
            +
            from claude_mpm.core.logger import get_logger
         
     | 
| 
      
 20 
     | 
    
         
            +
            from claude_mpm.hooks.base_hook import (
         
     | 
| 
      
 21 
     | 
    
         
            +
                BaseHook,
         
     | 
| 
      
 22 
     | 
    
         
            +
                PreDelegationHook,
         
     | 
| 
      
 23 
     | 
    
         
            +
                PostDelegationHook,
         
     | 
| 
      
 24 
     | 
    
         
            +
                HookContext,
         
     | 
| 
      
 25 
     | 
    
         
            +
                HookResult,
         
     | 
| 
      
 26 
     | 
    
         
            +
                HookType
         
     | 
| 
      
 27 
     | 
    
         
            +
            )
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            class HookService:
         
     | 
| 
      
 31 
     | 
    
         
            +
                """Service for managing and executing hooks in the delegation workflow.
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                WHY: Provides a centralized place to register and execute hooks, ensuring
         
     | 
| 
      
 34 
     | 
    
         
            +
                they run in the correct order and that failures in individual hooks don't
         
     | 
| 
      
 35 
     | 
    
         
            +
                break the entire delegation flow.
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                DESIGN DECISION: We execute hooks even if previous ones fail because:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - Hooks should be independent and not rely on each other
         
     | 
| 
      
 39 
     | 
    
         
            +
                - A logging hook failure shouldn't prevent memory hooks from running
         
     | 
| 
      
 40 
     | 
    
         
            +
                - We log all failures for debugging but keep the system resilient
         
     | 
| 
      
 41 
     | 
    
         
            +
                """
         
     | 
| 
      
 42 
     | 
    
         
            +
                
         
     | 
| 
      
 43 
     | 
    
         
            +
                def __init__(self, config: Optional[Config] = None):
         
     | 
| 
      
 44 
     | 
    
         
            +
                    """Initialize the hook service.
         
     | 
| 
      
 45 
     | 
    
         
            +
                    
         
     | 
| 
      
 46 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 47 
     | 
    
         
            +
                        config: Optional configuration object for controlling hook behavior
         
     | 
| 
      
 48 
     | 
    
         
            +
                    """
         
     | 
| 
      
 49 
     | 
    
         
            +
                    self.config = config or Config()
         
     | 
| 
      
 50 
     | 
    
         
            +
                    self.logger = get_logger("hook_service")
         
     | 
| 
      
 51 
     | 
    
         
            +
                    
         
     | 
| 
      
 52 
     | 
    
         
            +
                    # Separate lists for different hook types for performance
         
     | 
| 
      
 53 
     | 
    
         
            +
                    self.pre_delegation_hooks: List[PreDelegationHook] = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                    self.post_delegation_hooks: List[PostDelegationHook] = []
         
     | 
| 
      
 55 
     | 
    
         
            +
                    
         
     | 
| 
      
 56 
     | 
    
         
            +
                    # Track execution statistics for monitoring
         
     | 
| 
      
 57 
     | 
    
         
            +
                    self.stats = {
         
     | 
| 
      
 58 
     | 
    
         
            +
                        "pre_delegation_executed": 0,
         
     | 
| 
      
 59 
     | 
    
         
            +
                        "post_delegation_executed": 0,
         
     | 
| 
      
 60 
     | 
    
         
            +
                        "errors": 0
         
     | 
| 
      
 61 
     | 
    
         
            +
                    }
         
     | 
| 
      
 62 
     | 
    
         
            +
                
         
     | 
| 
      
 63 
     | 
    
         
            +
                def register_hook(self, hook: BaseHook) -> bool:
         
     | 
| 
      
 64 
     | 
    
         
            +
                    """Register a hook with the service.
         
     | 
| 
      
 65 
     | 
    
         
            +
                    
         
     | 
| 
      
 66 
     | 
    
         
            +
                    WHY: We need a way to add hooks dynamically, and we validate the hook
         
     | 
| 
      
 67 
     | 
    
         
            +
                    type to ensure it goes into the correct execution list.
         
     | 
| 
      
 68 
     | 
    
         
            +
                    
         
     | 
| 
      
 69 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 70 
     | 
    
         
            +
                        hook: The hook to register
         
     | 
| 
      
 71 
     | 
    
         
            +
                        
         
     | 
| 
      
 72 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 73 
     | 
    
         
            +
                        True if successfully registered, False otherwise
         
     | 
| 
      
 74 
     | 
    
         
            +
                    """
         
     | 
| 
      
 75 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 76 
     | 
    
         
            +
                        if isinstance(hook, PreDelegationHook):
         
     | 
| 
      
 77 
     | 
    
         
            +
                            self.pre_delegation_hooks.append(hook)
         
     | 
| 
      
 78 
     | 
    
         
            +
                            # Sort by priority (lower number = higher priority = executes first)
         
     | 
| 
      
 79 
     | 
    
         
            +
                            self.pre_delegation_hooks.sort(key=lambda h: h.priority)
         
     | 
| 
      
 80 
     | 
    
         
            +
                            self.logger.info(f"Registered pre-delegation hook: {hook.name} (priority: {hook.priority})")
         
     | 
| 
      
 81 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 82 
     | 
    
         
            +
                            
         
     | 
| 
      
 83 
     | 
    
         
            +
                        elif isinstance(hook, PostDelegationHook):
         
     | 
| 
      
 84 
     | 
    
         
            +
                            self.post_delegation_hooks.append(hook)
         
     | 
| 
      
 85 
     | 
    
         
            +
                            # Sort by priority
         
     | 
| 
      
 86 
     | 
    
         
            +
                            self.post_delegation_hooks.sort(key=lambda h: h.priority)
         
     | 
| 
      
 87 
     | 
    
         
            +
                            self.logger.info(f"Registered post-delegation hook: {hook.name} (priority: {hook.priority})")
         
     | 
| 
      
 88 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 89 
     | 
    
         
            +
                            
         
     | 
| 
      
 90 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 91 
     | 
    
         
            +
                            self.logger.warning(f"Attempted to register unsupported hook type: {type(hook).__name__}")
         
     | 
| 
      
 92 
     | 
    
         
            +
                            return False
         
     | 
| 
      
 93 
     | 
    
         
            +
                            
         
     | 
| 
      
 94 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 95 
     | 
    
         
            +
                        self.logger.error(f"Failed to register hook {hook.name}: {e}")
         
     | 
| 
      
 96 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 97 
     | 
    
         
            +
                
         
     | 
| 
      
 98 
     | 
    
         
            +
                def execute_pre_delegation_hooks(self, context: HookContext) -> HookResult:
         
     | 
| 
      
 99 
     | 
    
         
            +
                    """Execute all pre-delegation hooks in priority order.
         
     | 
| 
      
 100 
     | 
    
         
            +
                    
         
     | 
| 
      
 101 
     | 
    
         
            +
                    WHY: Pre-delegation hooks need to modify the context before it's sent
         
     | 
| 
      
 102 
     | 
    
         
            +
                    to the agent. Each hook can add or modify context data.
         
     | 
| 
      
 103 
     | 
    
         
            +
                    
         
     | 
| 
      
 104 
     | 
    
         
            +
                    DESIGN DECISION: We pass the context through all hooks sequentially,
         
     | 
| 
      
 105 
     | 
    
         
            +
                    allowing each to modify it. This enables powerful composition patterns.
         
     | 
| 
      
 106 
     | 
    
         
            +
                    
         
     | 
| 
      
 107 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 108 
     | 
    
         
            +
                        context: The HookContext to be processed by hooks
         
     | 
| 
      
 109 
     | 
    
         
            +
                        
         
     | 
| 
      
 110 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 111 
     | 
    
         
            +
                        HookResult containing the final processed data
         
     | 
| 
      
 112 
     | 
    
         
            +
                    """
         
     | 
| 
      
 113 
     | 
    
         
            +
                    # Check if hooks are enabled via config
         
     | 
| 
      
 114 
     | 
    
         
            +
                    if not self._are_hooks_enabled("pre_delegation"):
         
     | 
| 
      
 115 
     | 
    
         
            +
                        return HookResult(
         
     | 
| 
      
 116 
     | 
    
         
            +
                            success=True,
         
     | 
| 
      
 117 
     | 
    
         
            +
                            data=context.data,
         
     | 
| 
      
 118 
     | 
    
         
            +
                            modified=False
         
     | 
| 
      
 119 
     | 
    
         
            +
                        )
         
     | 
| 
      
 120 
     | 
    
         
            +
                        
         
     | 
| 
      
 121 
     | 
    
         
            +
                    # Create a working copy of the data to preserve original
         
     | 
| 
      
 122 
     | 
    
         
            +
                    working_data = context.data.copy()
         
     | 
| 
      
 123 
     | 
    
         
            +
                    
         
     | 
| 
      
 124 
     | 
    
         
            +
                    executed_count = 0
         
     | 
| 
      
 125 
     | 
    
         
            +
                    has_modifications = False
         
     | 
| 
      
 126 
     | 
    
         
            +
                    
         
     | 
| 
      
 127 
     | 
    
         
            +
                    for hook in self.pre_delegation_hooks:
         
     | 
| 
      
 128 
     | 
    
         
            +
                        if not hook.enabled:
         
     | 
| 
      
 129 
     | 
    
         
            +
                            self.logger.debug(f"Skipping disabled hook: {hook.name}")
         
     | 
| 
      
 130 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 131 
     | 
    
         
            +
                            
         
     | 
| 
      
 132 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 133 
     | 
    
         
            +
                            # Validate if hook should run
         
     | 
| 
      
 134 
     | 
    
         
            +
                            if not hook.validate(context):
         
     | 
| 
      
 135 
     | 
    
         
            +
                                self.logger.debug(f"Hook {hook.name} validation failed, skipping")
         
     | 
| 
      
 136 
     | 
    
         
            +
                                continue
         
     | 
| 
      
 137 
     | 
    
         
            +
                            
         
     | 
| 
      
 138 
     | 
    
         
            +
                            # Create a hook context with the current working data
         
     | 
| 
      
 139 
     | 
    
         
            +
                            hook_context = HookContext(
         
     | 
| 
      
 140 
     | 
    
         
            +
                                hook_type=context.hook_type,
         
     | 
| 
      
 141 
     | 
    
         
            +
                                data=working_data,
         
     | 
| 
      
 142 
     | 
    
         
            +
                                metadata=context.metadata,
         
     | 
| 
      
 143 
     | 
    
         
            +
                                timestamp=context.timestamp,
         
     | 
| 
      
 144 
     | 
    
         
            +
                                session_id=context.session_id,
         
     | 
| 
      
 145 
     | 
    
         
            +
                                user_id=context.user_id
         
     | 
| 
      
 146 
     | 
    
         
            +
                            )
         
     | 
| 
      
 147 
     | 
    
         
            +
                            
         
     | 
| 
      
 148 
     | 
    
         
            +
                            # Execute with timing
         
     | 
| 
      
 149 
     | 
    
         
            +
                            start_time = time.time()
         
     | 
| 
      
 150 
     | 
    
         
            +
                            result = hook.execute(hook_context)
         
     | 
| 
      
 151 
     | 
    
         
            +
                            execution_time = (time.time() - start_time) * 1000  # ms
         
     | 
| 
      
 152 
     | 
    
         
            +
                            
         
     | 
| 
      
 153 
     | 
    
         
            +
                            if result.success:
         
     | 
| 
      
 154 
     | 
    
         
            +
                                if result.modified and result.data:
         
     | 
| 
      
 155 
     | 
    
         
            +
                                    # Update working data with modified data
         
     | 
| 
      
 156 
     | 
    
         
            +
                                    working_data.update(result.data)
         
     | 
| 
      
 157 
     | 
    
         
            +
                                    has_modifications = True
         
     | 
| 
      
 158 
     | 
    
         
            +
                                    self.logger.debug(f"Hook {hook.name} modified context")
         
     | 
| 
      
 159 
     | 
    
         
            +
                                
         
     | 
| 
      
 160 
     | 
    
         
            +
                                self.logger.info(
         
     | 
| 
      
 161 
     | 
    
         
            +
                                    f"Executed pre-delegation hook {hook.name} "
         
     | 
| 
      
 162 
     | 
    
         
            +
                                    f"(took {execution_time:.2f}ms)"
         
     | 
| 
      
 163 
     | 
    
         
            +
                                )
         
     | 
| 
      
 164 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 165 
     | 
    
         
            +
                                self.logger.warning(f"Hook {hook.name} failed: {result.error}")
         
     | 
| 
      
 166 
     | 
    
         
            +
                                self.stats["errors"] += 1
         
     | 
| 
      
 167 
     | 
    
         
            +
                                
         
     | 
| 
      
 168 
     | 
    
         
            +
                            executed_count += 1
         
     | 
| 
      
 169 
     | 
    
         
            +
                            
         
     | 
| 
      
 170 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 171 
     | 
    
         
            +
                            # Log error but continue with other hooks
         
     | 
| 
      
 172 
     | 
    
         
            +
                            self.logger.error(f"Exception in hook {hook.name}: {e}", exc_info=True)
         
     | 
| 
      
 173 
     | 
    
         
            +
                            self.stats["errors"] += 1
         
     | 
| 
      
 174 
     | 
    
         
            +
                    
         
     | 
| 
      
 175 
     | 
    
         
            +
                    self.stats["pre_delegation_executed"] += executed_count
         
     | 
| 
      
 176 
     | 
    
         
            +
                    self.logger.info(f"Executed {executed_count} pre-delegation hooks")
         
     | 
| 
      
 177 
     | 
    
         
            +
                    
         
     | 
| 
      
 178 
     | 
    
         
            +
                    return HookResult(
         
     | 
| 
      
 179 
     | 
    
         
            +
                        success=True,
         
     | 
| 
      
 180 
     | 
    
         
            +
                        data=working_data,
         
     | 
| 
      
 181 
     | 
    
         
            +
                        modified=has_modifications
         
     | 
| 
      
 182 
     | 
    
         
            +
                    )
         
     | 
| 
      
 183 
     | 
    
         
            +
                
         
     | 
| 
      
 184 
     | 
    
         
            +
                def execute_post_delegation_hooks(self, context: HookContext) -> HookResult:
         
     | 
| 
      
 185 
     | 
    
         
            +
                    """Execute all post-delegation hooks in priority order.
         
     | 
| 
      
 186 
     | 
    
         
            +
                    
         
     | 
| 
      
 187 
     | 
    
         
            +
                    WHY: Post-delegation hooks need to process agent results, extract learnings,
         
     | 
| 
      
 188 
     | 
    
         
            +
                    log outcomes, or perform other post-processing tasks.
         
     | 
| 
      
 189 
     | 
    
         
            +
                    
         
     | 
| 
      
 190 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 191 
     | 
    
         
            +
                        context: The HookContext containing delegation results
         
     | 
| 
      
 192 
     | 
    
         
            +
                        
         
     | 
| 
      
 193 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 194 
     | 
    
         
            +
                        HookResult containing the final processed data
         
     | 
| 
      
 195 
     | 
    
         
            +
                    """
         
     | 
| 
      
 196 
     | 
    
         
            +
                    # Check if hooks are enabled via config
         
     | 
| 
      
 197 
     | 
    
         
            +
                    if not self._are_hooks_enabled("post_delegation"):
         
     | 
| 
      
 198 
     | 
    
         
            +
                        return HookResult(
         
     | 
| 
      
 199 
     | 
    
         
            +
                            success=True,
         
     | 
| 
      
 200 
     | 
    
         
            +
                            data=context.data,
         
     | 
| 
      
 201 
     | 
    
         
            +
                            modified=False
         
     | 
| 
      
 202 
     | 
    
         
            +
                        )
         
     | 
| 
      
 203 
     | 
    
         
            +
                        
         
     | 
| 
      
 204 
     | 
    
         
            +
                    # Create a working copy of the data to preserve original
         
     | 
| 
      
 205 
     | 
    
         
            +
                    working_data = context.data.copy()
         
     | 
| 
      
 206 
     | 
    
         
            +
                    
         
     | 
| 
      
 207 
     | 
    
         
            +
                    executed_count = 0
         
     | 
| 
      
 208 
     | 
    
         
            +
                    has_modifications = False
         
     | 
| 
      
 209 
     | 
    
         
            +
                    
         
     | 
| 
      
 210 
     | 
    
         
            +
                    for hook in self.post_delegation_hooks:
         
     | 
| 
      
 211 
     | 
    
         
            +
                        if not hook.enabled:
         
     | 
| 
      
 212 
     | 
    
         
            +
                            self.logger.debug(f"Skipping disabled hook: {hook.name}")
         
     | 
| 
      
 213 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 214 
     | 
    
         
            +
                            
         
     | 
| 
      
 215 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 216 
     | 
    
         
            +
                            # Validate if hook should run
         
     | 
| 
      
 217 
     | 
    
         
            +
                            if not hook.validate(context):
         
     | 
| 
      
 218 
     | 
    
         
            +
                                self.logger.debug(f"Hook {hook.name} validation failed, skipping")
         
     | 
| 
      
 219 
     | 
    
         
            +
                                continue
         
     | 
| 
      
 220 
     | 
    
         
            +
                            
         
     | 
| 
      
 221 
     | 
    
         
            +
                            # Create a hook context with the current working data
         
     | 
| 
      
 222 
     | 
    
         
            +
                            hook_context = HookContext(
         
     | 
| 
      
 223 
     | 
    
         
            +
                                hook_type=context.hook_type,
         
     | 
| 
      
 224 
     | 
    
         
            +
                                data=working_data,
         
     | 
| 
      
 225 
     | 
    
         
            +
                                metadata=context.metadata,
         
     | 
| 
      
 226 
     | 
    
         
            +
                                timestamp=context.timestamp,
         
     | 
| 
      
 227 
     | 
    
         
            +
                                session_id=context.session_id,
         
     | 
| 
      
 228 
     | 
    
         
            +
                                user_id=context.user_id
         
     | 
| 
      
 229 
     | 
    
         
            +
                            )
         
     | 
| 
      
 230 
     | 
    
         
            +
                            
         
     | 
| 
      
 231 
     | 
    
         
            +
                            # Execute with timing
         
     | 
| 
      
 232 
     | 
    
         
            +
                            start_time = time.time()
         
     | 
| 
      
 233 
     | 
    
         
            +
                            result = hook.execute(hook_context)
         
     | 
| 
      
 234 
     | 
    
         
            +
                            execution_time = (time.time() - start_time) * 1000  # ms
         
     | 
| 
      
 235 
     | 
    
         
            +
                            
         
     | 
| 
      
 236 
     | 
    
         
            +
                            if result.success:
         
     | 
| 
      
 237 
     | 
    
         
            +
                                if result.modified and result.data:
         
     | 
| 
      
 238 
     | 
    
         
            +
                                    # Update working data with modified data
         
     | 
| 
      
 239 
     | 
    
         
            +
                                    working_data.update(result.data)
         
     | 
| 
      
 240 
     | 
    
         
            +
                                    has_modifications = True
         
     | 
| 
      
 241 
     | 
    
         
            +
                                    self.logger.debug(f"Hook {hook.name} modified context")
         
     | 
| 
      
 242 
     | 
    
         
            +
                                
         
     | 
| 
      
 243 
     | 
    
         
            +
                                self.logger.info(
         
     | 
| 
      
 244 
     | 
    
         
            +
                                    f"Executed post-delegation hook {hook.name} "
         
     | 
| 
      
 245 
     | 
    
         
            +
                                    f"(took {execution_time:.2f}ms)"
         
     | 
| 
      
 246 
     | 
    
         
            +
                                )
         
     | 
| 
      
 247 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 248 
     | 
    
         
            +
                                self.logger.warning(f"Hook {hook.name} failed: {result.error}")
         
     | 
| 
      
 249 
     | 
    
         
            +
                                self.stats["errors"] += 1
         
     | 
| 
      
 250 
     | 
    
         
            +
                                
         
     | 
| 
      
 251 
     | 
    
         
            +
                            executed_count += 1
         
     | 
| 
      
 252 
     | 
    
         
            +
                            
         
     | 
| 
      
 253 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 254 
     | 
    
         
            +
                            # Log error but continue with other hooks
         
     | 
| 
      
 255 
     | 
    
         
            +
                            self.logger.error(f"Exception in hook {hook.name}: {e}", exc_info=True)
         
     | 
| 
      
 256 
     | 
    
         
            +
                            self.stats["errors"] += 1
         
     | 
| 
      
 257 
     | 
    
         
            +
                    
         
     | 
| 
      
 258 
     | 
    
         
            +
                    self.stats["post_delegation_executed"] += executed_count
         
     | 
| 
      
 259 
     | 
    
         
            +
                    self.logger.info(f"Executed {executed_count} post-delegation hooks")
         
     | 
| 
      
 260 
     | 
    
         
            +
                    
         
     | 
| 
      
 261 
     | 
    
         
            +
                    return HookResult(
         
     | 
| 
      
 262 
     | 
    
         
            +
                        success=True,
         
     | 
| 
      
 263 
     | 
    
         
            +
                        data=working_data,
         
     | 
| 
      
 264 
     | 
    
         
            +
                        modified=has_modifications
         
     | 
| 
      
 265 
     | 
    
         
            +
                    )
         
     | 
| 
      
 266 
     | 
    
         
            +
                
         
     | 
| 
      
 267 
     | 
    
         
            +
                def _are_hooks_enabled(self, hook_type: str) -> bool:
         
     | 
| 
      
 268 
     | 
    
         
            +
                    """Check if hooks are enabled in configuration.
         
     | 
| 
      
 269 
     | 
    
         
            +
                    
         
     | 
| 
      
 270 
     | 
    
         
            +
                    WHY: We need fine-grained control over hook execution. This allows
         
     | 
| 
      
 271 
     | 
    
         
            +
                    disabling hooks for debugging or in specific environments.
         
     | 
| 
      
 272 
     | 
    
         
            +
                    
         
     | 
| 
      
 273 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 274 
     | 
    
         
            +
                        hook_type: Type of hooks to check (pre_delegation, post_delegation)
         
     | 
| 
      
 275 
     | 
    
         
            +
                        
         
     | 
| 
      
 276 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 277 
     | 
    
         
            +
                        True if hooks are enabled
         
     | 
| 
      
 278 
     | 
    
         
            +
                    """
         
     | 
| 
      
 279 
     | 
    
         
            +
                    # Check global hook enable flag
         
     | 
| 
      
 280 
     | 
    
         
            +
                    if not self.config.get("hooks.enabled", True):
         
     | 
| 
      
 281 
     | 
    
         
            +
                        self.logger.debug("All hooks disabled via configuration")
         
     | 
| 
      
 282 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 283 
     | 
    
         
            +
                        
         
     | 
| 
      
 284 
     | 
    
         
            +
                    # Check specific hook type enable flag
         
     | 
| 
      
 285 
     | 
    
         
            +
                    if not self.config.get(f"hooks.{hook_type}.enabled", True):
         
     | 
| 
      
 286 
     | 
    
         
            +
                        self.logger.debug(f"{hook_type} hooks disabled via configuration")
         
     | 
| 
      
 287 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 288 
     | 
    
         
            +
                        
         
     | 
| 
      
 289 
     | 
    
         
            +
                    # Special check for memory hooks if they depend on memory system
         
     | 
| 
      
 290 
     | 
    
         
            +
                    if hook_type in ["pre_delegation", "post_delegation"]:
         
     | 
| 
      
 291 
     | 
    
         
            +
                        # If any registered hooks are memory-related, check memory.enabled
         
     | 
| 
      
 292 
     | 
    
         
            +
                        for hook in getattr(self, f"{hook_type}_hooks", []):
         
     | 
| 
      
 293 
     | 
    
         
            +
                            if "memory" in hook.name.lower():
         
     | 
| 
      
 294 
     | 
    
         
            +
                                if not self.config.get("memory.enabled", True):
         
     | 
| 
      
 295 
     | 
    
         
            +
                                    self.logger.debug("Memory hooks disabled via memory.enabled config")
         
     | 
| 
      
 296 
     | 
    
         
            +
                                    return False
         
     | 
| 
      
 297 
     | 
    
         
            +
                                    
         
     | 
| 
      
 298 
     | 
    
         
            +
                    return True
         
     | 
| 
      
 299 
     | 
    
         
            +
                
         
     | 
| 
      
 300 
     | 
    
         
            +
                def get_stats(self) -> Dict[str, int]:
         
     | 
| 
      
 301 
     | 
    
         
            +
                    """Get execution statistics.
         
     | 
| 
      
 302 
     | 
    
         
            +
                    
         
     | 
| 
      
 303 
     | 
    
         
            +
                    WHY: Monitoring hook execution helps identify performance issues
         
     | 
| 
      
 304 
     | 
    
         
            +
                    and debug problems in production.
         
     | 
| 
      
 305 
     | 
    
         
            +
                    
         
     | 
| 
      
 306 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 307 
     | 
    
         
            +
                        Dictionary of execution statistics
         
     | 
| 
      
 308 
     | 
    
         
            +
                    """
         
     | 
| 
      
 309 
     | 
    
         
            +
                    return self.stats.copy()
         
     | 
| 
      
 310 
     | 
    
         
            +
                
         
     | 
| 
      
 311 
     | 
    
         
            +
                def reset_stats(self):
         
     | 
| 
      
 312 
     | 
    
         
            +
                    """Reset execution statistics.
         
     | 
| 
      
 313 
     | 
    
         
            +
                    
         
     | 
| 
      
 314 
     | 
    
         
            +
                    WHY: Allows periodic cleanup of stats for long-running services.
         
     | 
| 
      
 315 
     | 
    
         
            +
                    """
         
     | 
| 
      
 316 
     | 
    
         
            +
                    self.stats = {
         
     | 
| 
      
 317 
     | 
    
         
            +
                        "pre_delegation_executed": 0,
         
     | 
| 
      
 318 
     | 
    
         
            +
                        "post_delegation_executed": 0,
         
     | 
| 
      
 319 
     | 
    
         
            +
                        "errors": 0
         
     | 
| 
      
 320 
     | 
    
         
            +
                    }
         
     | 
| 
      
 321 
     | 
    
         
            +
                
         
     | 
| 
      
 322 
     | 
    
         
            +
                def list_hooks(self) -> Dict[str, List[str]]:
         
     | 
| 
      
 323 
     | 
    
         
            +
                    """List all registered hooks by type.
         
     | 
| 
      
 324 
     | 
    
         
            +
                    
         
     | 
| 
      
 325 
     | 
    
         
            +
                    WHY: Useful for debugging and understanding what hooks are active.
         
     | 
| 
      
 326 
     | 
    
         
            +
                    
         
     | 
| 
      
 327 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 328 
     | 
    
         
            +
                        Dictionary mapping hook type to list of hook names
         
     | 
| 
      
 329 
     | 
    
         
            +
                    """
         
     | 
| 
      
 330 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 331 
     | 
    
         
            +
                        "pre_delegation": [h.name for h in self.pre_delegation_hooks],
         
     | 
| 
      
 332 
     | 
    
         
            +
                        "post_delegation": [h.name for h in self.post_delegation_hooks]
         
     | 
| 
      
 333 
     | 
    
         
            +
                    }
         
     | 
| 
      
 334 
     | 
    
         
            +
                
         
     | 
| 
      
 335 
     | 
    
         
            +
                def remove_hook(self, hook_name: str) -> bool:
         
     | 
| 
      
 336 
     | 
    
         
            +
                    """Remove a hook by name.
         
     | 
| 
      
 337 
     | 
    
         
            +
                    
         
     | 
| 
      
 338 
     | 
    
         
            +
                    WHY: Allows dynamic hook management, useful for testing or
         
     | 
| 
      
 339 
     | 
    
         
            +
                    conditional hook usage.
         
     | 
| 
      
 340 
     | 
    
         
            +
                    
         
     | 
| 
      
 341 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 342 
     | 
    
         
            +
                        hook_name: Name of the hook to remove
         
     | 
| 
      
 343 
     | 
    
         
            +
                        
         
     | 
| 
      
 344 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 345 
     | 
    
         
            +
                        True if hook was found and removed
         
     | 
| 
      
 346 
     | 
    
         
            +
                    """
         
     | 
| 
      
 347 
     | 
    
         
            +
                    # Check pre-delegation hooks
         
     | 
| 
      
 348 
     | 
    
         
            +
                    for i, hook in enumerate(self.pre_delegation_hooks):
         
     | 
| 
      
 349 
     | 
    
         
            +
                        if hook.name == hook_name:
         
     | 
| 
      
 350 
     | 
    
         
            +
                            self.pre_delegation_hooks.pop(i)
         
     | 
| 
      
 351 
     | 
    
         
            +
                            self.logger.info(f"Removed pre-delegation hook: {hook_name}")
         
     | 
| 
      
 352 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 353 
     | 
    
         
            +
                            
         
     | 
| 
      
 354 
     | 
    
         
            +
                    # Check post-delegation hooks
         
     | 
| 
      
 355 
     | 
    
         
            +
                    for i, hook in enumerate(self.post_delegation_hooks):
         
     | 
| 
      
 356 
     | 
    
         
            +
                        if hook.name == hook_name:
         
     | 
| 
      
 357 
     | 
    
         
            +
                            self.post_delegation_hooks.pop(i)
         
     | 
| 
      
 358 
     | 
    
         
            +
                            self.logger.info(f"Removed post-delegation hook: {hook_name}")
         
     | 
| 
      
 359 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 360 
     | 
    
         
            +
                            
         
     | 
| 
      
 361 
     | 
    
         
            +
                    self.logger.warning(f"Hook not found: {hook_name}")
         
     | 
| 
      
 362 
     | 
    
         
            +
                    return False
         
     |