claude-mpm 3.5.6__py3-none-any.whl → 3.7.1__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/agents/BASE_AGENT_TEMPLATE.md +96 -23
- claude_mpm/agents/BASE_PM.md +273 -0
- claude_mpm/agents/INSTRUCTIONS.md +114 -103
- claude_mpm/agents/agent_loader.py +36 -1
- claude_mpm/agents/async_agent_loader.py +421 -0
- claude_mpm/agents/templates/code_analyzer.json +81 -0
- claude_mpm/agents/templates/data_engineer.json +18 -3
- claude_mpm/agents/templates/documentation.json +18 -3
- claude_mpm/agents/templates/engineer.json +19 -4
- claude_mpm/agents/templates/ops.json +18 -3
- claude_mpm/agents/templates/qa.json +20 -4
- claude_mpm/agents/templates/research.json +20 -4
- claude_mpm/agents/templates/security.json +18 -3
- claude_mpm/agents/templates/version_control.json +16 -3
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +212 -3
- claude_mpm/cli/commands/aggregate.py +462 -0
- claude_mpm/cli/commands/config.py +277 -0
- claude_mpm/cli/commands/run.py +224 -36
- claude_mpm/cli/parser.py +176 -1
- claude_mpm/constants.py +19 -0
- claude_mpm/core/claude_runner.py +320 -44
- claude_mpm/core/config.py +161 -4
- claude_mpm/core/framework_loader.py +81 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +391 -9
- claude_mpm/init.py +40 -5
- claude_mpm/models/agent_session.py +511 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/start_activity_logging.py +86 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +165 -19
- claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
- claude_mpm/services/event_aggregator.py +547 -0
- claude_mpm/utils/agent_dependency_loader.py +655 -0
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/dependency_cache.py +376 -0
- claude_mpm/utils/dependency_strategies.py +343 -0
- claude_mpm/utils/environment_context.py +310 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/METADATA +47 -3
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/RECORD +45 -31
- claude_mpm/agents/templates/pm.json +0 -122
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.6.dist-info → claude_mpm-3.7.1.dist-info}/top_level.txt +0 -0
| @@ -274,6 +274,13 @@ class FrameworkLoader: | |
| 274 274 | 
             
                            if self.framework_last_modified:
         | 
| 275 275 | 
             
                                content["instructions_last_modified"] = self.framework_last_modified
         | 
| 276 276 |  | 
| 277 | 
            +
                    # Load BASE_PM.md for core framework requirements
         | 
| 278 | 
            +
                    base_pm_path = self.framework_path / "src" / "claude_mpm" / "agents" / "BASE_PM.md"
         | 
| 279 | 
            +
                    if base_pm_path.exists():
         | 
| 280 | 
            +
                        base_pm_content = self._try_load_file(base_pm_path, "BASE_PM framework requirements")
         | 
| 281 | 
            +
                        if base_pm_content:
         | 
| 282 | 
            +
                            content["base_pm_instructions"] = base_pm_content
         | 
| 283 | 
            +
                    
         | 
| 277 284 | 
             
                    # Discover agent directories
         | 
| 278 285 | 
             
                    agents_dir, templates_dir, main_dir = self._discover_framework_paths()
         | 
| 279 286 |  | 
| @@ -308,6 +315,17 @@ class FrameworkLoader: | |
| 308 315 | 
             
                        if self.framework_content["working_claude_md"]:
         | 
| 309 316 | 
             
                            instructions += f"\n\n## Working Directory Instructions\n{self.framework_content['working_claude_md']}\n"
         | 
| 310 317 |  | 
| 318 | 
            +
                        # Add dynamic agent capabilities section
         | 
| 319 | 
            +
                        instructions += self._generate_agent_capabilities_section()
         | 
| 320 | 
            +
                        
         | 
| 321 | 
            +
                        # Add current date for temporal awareness
         | 
| 322 | 
            +
                        instructions += f"\n\n## Temporal Context\n**Today's Date**: {datetime.now().strftime('%Y-%m-%d')}\n"
         | 
| 323 | 
            +
                        instructions += "Apply date awareness to all time-sensitive tasks and decisions.\n"
         | 
| 324 | 
            +
                        
         | 
| 325 | 
            +
                        # Add BASE_PM.md framework requirements AFTER INSTRUCTIONS.md
         | 
| 326 | 
            +
                        if self.framework_content.get("base_pm_instructions"):
         | 
| 327 | 
            +
                            instructions += f"\n\n{self.framework_content['base_pm_instructions']}\n"
         | 
| 328 | 
            +
                        
         | 
| 311 329 | 
             
                        return instructions
         | 
| 312 330 |  | 
| 313 331 | 
             
                    # Otherwise fall back to generating framework
         | 
| @@ -413,6 +431,69 @@ Extract tickets from these patterns: | |
| 413 431 |  | 
| 414 432 | 
             
                    return instructions
         | 
| 415 433 |  | 
| 434 | 
            +
                def _generate_agent_capabilities_section(self) -> str:
         | 
| 435 | 
            +
                    """Generate dynamic agent capabilities section from deployed agents."""
         | 
| 436 | 
            +
                    try:
         | 
| 437 | 
            +
                        # Try to get agents from agent_loader
         | 
| 438 | 
            +
                        from claude_mpm.agents.agent_loader import list_available_agents
         | 
| 439 | 
            +
                        agents = list_available_agents()
         | 
| 440 | 
            +
                        
         | 
| 441 | 
            +
                        if not agents:
         | 
| 442 | 
            +
                            return ""
         | 
| 443 | 
            +
                        
         | 
| 444 | 
            +
                        # Build capabilities section
         | 
| 445 | 
            +
                        section = "\n\n## Available Agent Capabilities\n\n"
         | 
| 446 | 
            +
                        section += "You have the following specialized agents available for delegation:\n\n"
         | 
| 447 | 
            +
                        
         | 
| 448 | 
            +
                        # Group agents by category
         | 
| 449 | 
            +
                        categories = {}
         | 
| 450 | 
            +
                        for agent_id, info in agents.items():
         | 
| 451 | 
            +
                            category = info.get('category', 'general')
         | 
| 452 | 
            +
                            if category not in categories:
         | 
| 453 | 
            +
                                categories[category] = []
         | 
| 454 | 
            +
                            categories[category].append((agent_id, info))
         | 
| 455 | 
            +
                        
         | 
| 456 | 
            +
                        # List agents by category
         | 
| 457 | 
            +
                        for category in sorted(categories.keys()):
         | 
| 458 | 
            +
                            section += f"\n### {category.title()} Agents\n"
         | 
| 459 | 
            +
                            for agent_id, info in sorted(categories[category]):
         | 
| 460 | 
            +
                                name = info.get('name', agent_id)
         | 
| 461 | 
            +
                                desc = info.get('description', 'Specialized agent')
         | 
| 462 | 
            +
                                tools = info.get('tools', [])
         | 
| 463 | 
            +
                                section += f"- **{name}** (`{agent_id}`): {desc}\n"
         | 
| 464 | 
            +
                                if tools:
         | 
| 465 | 
            +
                                    section += f"  - Tools: {', '.join(tools[:5])}"
         | 
| 466 | 
            +
                                    if len(tools) > 5:
         | 
| 467 | 
            +
                                        section += f" (+{len(tools)-5} more)"
         | 
| 468 | 
            +
                                    section += "\n"
         | 
| 469 | 
            +
                        
         | 
| 470 | 
            +
                        # Add summary
         | 
| 471 | 
            +
                        section += f"\n**Total Available Agents**: {len(agents)}\n"
         | 
| 472 | 
            +
                        section += "Use the agent ID in parentheses when delegating tasks via the Task tool.\n"
         | 
| 473 | 
            +
                        
         | 
| 474 | 
            +
                        return section
         | 
| 475 | 
            +
                        
         | 
| 476 | 
            +
                    except Exception as e:
         | 
| 477 | 
            +
                        self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
         | 
| 478 | 
            +
                        # Return static fallback
         | 
| 479 | 
            +
                        return """
         | 
| 480 | 
            +
             | 
| 481 | 
            +
            ## Available Agent Capabilities
         | 
| 482 | 
            +
             | 
| 483 | 
            +
            You have the following specialized agents available for delegation:
         | 
| 484 | 
            +
             | 
| 485 | 
            +
            - **Engineer Agent**: Code implementation and development
         | 
| 486 | 
            +
            - **Research Agent**: Investigation and analysis
         | 
| 487 | 
            +
            - **QA Agent**: Testing and quality assurance
         | 
| 488 | 
            +
            - **Documentation Agent**: Documentation creation and maintenance
         | 
| 489 | 
            +
            - **Security Agent**: Security analysis and protection
         | 
| 490 | 
            +
            - **Data Engineer Agent**: Data management and pipelines
         | 
| 491 | 
            +
            - **Ops Agent**: Deployment and operations
         | 
| 492 | 
            +
            - **Version Control Agent**: Git operations and version management
         | 
| 493 | 
            +
             | 
| 494 | 
            +
            Use these agents to delegate specialized work via the Task tool.
         | 
| 495 | 
            +
            """
         | 
| 496 | 
            +
                
         | 
| 416 497 | 
             
                def _format_minimal_framework(self) -> str:
         | 
| 417 498 | 
             
                    """Format minimal framework instructions when full framework not available."""
         | 
| 418 499 | 
             
                    return """
         | 
| @@ -18,11 +18,13 @@ import os | |
| 18 18 | 
             
            import subprocess
         | 
| 19 19 | 
             
            from datetime import datetime
         | 
| 20 20 | 
             
            import time
         | 
| 21 | 
            +
            import asyncio
         | 
| 21 22 | 
             
            from pathlib import Path
         | 
| 22 23 | 
             
            from collections import deque
         | 
| 23 24 |  | 
| 24 | 
            -
            #  | 
| 25 | 
            -
             | 
| 25 | 
            +
            # Debug mode is enabled by default for better visibility into hook processing
         | 
| 26 | 
            +
            # Set CLAUDE_MPM_HOOK_DEBUG=false to disable debug output
         | 
| 27 | 
            +
            DEBUG = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', 'true').lower() != 'false'
         | 
| 26 28 |  | 
| 27 29 | 
             
            # Add imports for memory hook integration with comprehensive error handling
         | 
| 28 30 | 
             
            MEMORY_HOOKS_AVAILABLE = False
         | 
| @@ -103,13 +105,24 @@ class ClaudeHookHandler: | |
| 103 105 | 
             
                    # Initialize response tracking if available and enabled
         | 
| 104 106 | 
             
                    self.response_tracker = None
         | 
| 105 107 | 
             
                    self.response_tracking_enabled = False
         | 
| 108 | 
            +
                    self.track_all_interactions = False  # Track all Claude interactions, not just delegations
         | 
| 106 109 | 
             
                    if RESPONSE_TRACKING_AVAILABLE:
         | 
| 107 110 | 
             
                        self._initialize_response_tracking()
         | 
| 108 111 |  | 
| 112 | 
            +
                    # Store current user prompts for comprehensive response tracking
         | 
| 113 | 
            +
                    self.pending_prompts = {}  # session_id -> prompt data
         | 
| 114 | 
            +
                    
         | 
| 109 115 | 
             
                    # No fallback server needed - we only use Socket.IO now
         | 
| 110 116 |  | 
| 111 117 | 
             
                def _track_delegation(self, session_id: str, agent_type: str, request_data: dict = None):
         | 
| 112 118 | 
             
                    """Track a new agent delegation with optional request data for response correlation."""
         | 
| 119 | 
            +
                    if DEBUG:
         | 
| 120 | 
            +
                        print(f"\n[DEBUG] _track_delegation called:", file=sys.stderr)
         | 
| 121 | 
            +
                        print(f"  - session_id: {session_id[:16] if session_id else 'None'}...", file=sys.stderr)
         | 
| 122 | 
            +
                        print(f"  - agent_type: {agent_type}", file=sys.stderr)
         | 
| 123 | 
            +
                        print(f"  - request_data provided: {bool(request_data)}", file=sys.stderr)
         | 
| 124 | 
            +
                        print(f"  - delegation_requests size before: {len(self.delegation_requests)}", file=sys.stderr)
         | 
| 125 | 
            +
                    
         | 
| 113 126 | 
             
                    if session_id and agent_type and agent_type != 'unknown':
         | 
| 114 127 | 
             
                        self.active_delegations[session_id] = agent_type
         | 
| 115 128 | 
             
                        key = f"{session_id}:{datetime.now().timestamp()}"
         | 
| @@ -122,6 +135,9 @@ class ClaudeHookHandler: | |
| 122 135 | 
             
                                'request': request_data,
         | 
| 123 136 | 
             
                                'timestamp': datetime.now().isoformat()
         | 
| 124 137 | 
             
                            }
         | 
| 138 | 
            +
                            if DEBUG:
         | 
| 139 | 
            +
                                print(f"  - ✅ Stored in delegation_requests[{session_id[:16]}...]", file=sys.stderr)
         | 
| 140 | 
            +
                                print(f"  - delegation_requests size after: {len(self.delegation_requests)}", file=sys.stderr)
         | 
| 125 141 |  | 
| 126 142 | 
             
                        # Clean up old delegations (older than 5 minutes)
         | 
| 127 143 | 
             
                        cutoff_time = datetime.now().timestamp() - 300
         | 
| @@ -229,8 +245,13 @@ class ClaudeHookHandler: | |
| 229 245 | 
             
                        self.response_tracker = ResponseTracker(config=config)
         | 
| 230 246 | 
             
                        self.response_tracking_enabled = self.response_tracker.is_enabled()
         | 
| 231 247 |  | 
| 248 | 
            +
                        # Check if we should track all interactions (not just delegations)
         | 
| 249 | 
            +
                        self.track_all_interactions = config.get('response_tracking.track_all_interactions', False) or \
         | 
| 250 | 
            +
                                                     config.get('response_logging.track_all_interactions', False)
         | 
| 251 | 
            +
                        
         | 
| 232 252 | 
             
                        if DEBUG:
         | 
| 233 | 
            -
                             | 
| 253 | 
            +
                            mode = "all interactions" if self.track_all_interactions else "Task delegations only"
         | 
| 254 | 
            +
                            print(f"✅ Response tracking initialized (mode: {mode})", file=sys.stderr)
         | 
| 234 255 |  | 
| 235 256 | 
             
                    except Exception as e:
         | 
| 236 257 | 
             
                        if DEBUG:
         | 
| @@ -273,6 +294,20 @@ class ClaudeHookHandler: | |
| 273 294 | 
             
                        # Convert response to string if it's not already
         | 
| 274 295 | 
             
                        response_text = str(response)
         | 
| 275 296 |  | 
| 297 | 
            +
                        # Try to extract structured JSON response from agent output
         | 
| 298 | 
            +
                        structured_response = None
         | 
| 299 | 
            +
                        try:
         | 
| 300 | 
            +
                            # Look for JSON block in the response (agents should return JSON at the end)
         | 
| 301 | 
            +
                            import re
         | 
| 302 | 
            +
                            json_match = re.search(r'```json\s*(\{.*?\})\s*```', response_text, re.DOTALL)
         | 
| 303 | 
            +
                            if json_match:
         | 
| 304 | 
            +
                                structured_response = json.loads(json_match.group(1))
         | 
| 305 | 
            +
                                if DEBUG:
         | 
| 306 | 
            +
                                    print(f"Extracted structured response from {agent_type} agent", file=sys.stderr)
         | 
| 307 | 
            +
                        except (json.JSONDecodeError, AttributeError) as e:
         | 
| 308 | 
            +
                            if DEBUG:
         | 
| 309 | 
            +
                                print(f"No structured JSON response found in {agent_type} agent output: {e}", file=sys.stderr)
         | 
| 310 | 
            +
                        
         | 
| 276 311 | 
             
                        # Get the original request (prompt + description)
         | 
| 277 312 | 
             
                        original_request = request_info.get('request', {})
         | 
| 278 313 | 
             
                        prompt = original_request.get('prompt', '')
         | 
| @@ -289,7 +324,7 @@ class ClaudeHookHandler: | |
| 289 324 | 
             
                        if not full_request:
         | 
| 290 325 | 
             
                            full_request = f"Task delegation to {agent_type} agent"
         | 
| 291 326 |  | 
| 292 | 
            -
                        # Prepare metadata
         | 
| 327 | 
            +
                        # Prepare metadata with structured response data if available
         | 
| 293 328 | 
             
                        metadata = {
         | 
| 294 329 | 
             
                            'exit_code': event.get('exit_code', 0),
         | 
| 295 330 | 
             
                            'success': event.get('exit_code', 0) == 0,
         | 
| @@ -301,6 +336,26 @@ class ClaudeHookHandler: | |
| 301 336 | 
             
                            'original_request_timestamp': request_info.get('timestamp')
         | 
| 302 337 | 
             
                        }
         | 
| 303 338 |  | 
| 339 | 
            +
                        # Add structured response data to metadata if available
         | 
| 340 | 
            +
                        if structured_response:
         | 
| 341 | 
            +
                            metadata['structured_response'] = {
         | 
| 342 | 
            +
                                'task_completed': structured_response.get('task_completed', False),
         | 
| 343 | 
            +
                                'instructions': structured_response.get('instructions', ''),
         | 
| 344 | 
            +
                                'results': structured_response.get('results', ''),
         | 
| 345 | 
            +
                                'files_modified': structured_response.get('files_modified', []),
         | 
| 346 | 
            +
                                'tools_used': structured_response.get('tools_used', []),
         | 
| 347 | 
            +
                                'remember': structured_response.get('remember')
         | 
| 348 | 
            +
                            }
         | 
| 349 | 
            +
                            
         | 
| 350 | 
            +
                            # Check if task was completed for logging purposes
         | 
| 351 | 
            +
                            if structured_response.get('task_completed'):
         | 
| 352 | 
            +
                                metadata['task_completed'] = True
         | 
| 353 | 
            +
                            
         | 
| 354 | 
            +
                            # Log files modified for debugging
         | 
| 355 | 
            +
                            if DEBUG and structured_response.get('files_modified'):
         | 
| 356 | 
            +
                                files = [f['file'] for f in structured_response['files_modified']]
         | 
| 357 | 
            +
                                print(f"Agent {agent_type} modified files: {files}", file=sys.stderr)
         | 
| 358 | 
            +
                        
         | 
| 304 359 | 
             
                        # Track the response
         | 
| 305 360 | 
             
                        file_path = self.response_tracker.track_response(
         | 
| 306 361 | 
             
                            agent_name=agent_type,
         | 
| @@ -452,9 +507,20 @@ class ClaudeHookHandler: | |
| 452 507 | 
             
                            elif DEBUG:
         | 
| 453 508 | 
             
                                print(f"Hook handler: Connection attempt {attempt + 1} failed, retrying...", file=sys.stderr)
         | 
| 454 509 |  | 
| 455 | 
            -
                            # Exponential backoff
         | 
| 510 | 
            +
                            # Exponential backoff with async delay
         | 
| 456 511 | 
             
                            if attempt < max_retries - 1:
         | 
| 457 | 
            -
                                time.sleep | 
| 512 | 
            +
                                # Use asyncio.sleep if in async context, otherwise fall back to time.sleep
         | 
| 513 | 
            +
                                try:
         | 
| 514 | 
            +
                                    loop = asyncio.get_event_loop()
         | 
| 515 | 
            +
                                    if loop.is_running():
         | 
| 516 | 
            +
                                        # We're in an async context, use async sleep
         | 
| 517 | 
            +
                                        asyncio.create_task(asyncio.sleep(retry_delay))
         | 
| 518 | 
            +
                                    else:
         | 
| 519 | 
            +
                                        # Sync context, use regular sleep
         | 
| 520 | 
            +
                                        time.sleep(retry_delay)
         | 
| 521 | 
            +
                                except:
         | 
| 522 | 
            +
                                    # Fallback to sync sleep if asyncio not available
         | 
| 523 | 
            +
                                    time.sleep(retry_delay)
         | 
| 458 524 | 
             
                                retry_delay *= 2  # Double the delay for next attempt
         | 
| 459 525 |  | 
| 460 526 | 
             
                    # All attempts failed
         | 
| @@ -492,6 +558,8 @@ class ClaudeHookHandler: | |
| 492 558 | 
             
                            self._handle_stop_fast(event)
         | 
| 493 559 | 
             
                        elif hook_type == 'SubagentStop':
         | 
| 494 560 | 
             
                            self._handle_subagent_stop_fast(event)
         | 
| 561 | 
            +
                        elif hook_type == 'AssistantResponse':
         | 
| 562 | 
            +
                            self._handle_assistant_response(event)
         | 
| 495 563 |  | 
| 496 564 | 
             
                        # Socket.IO emit is non-blocking and will complete asynchronously
         | 
| 497 565 | 
             
                        # Removed sleep() to eliminate 100ms delay that was blocking Claude execution
         | 
| @@ -606,6 +674,18 @@ class ClaudeHookHandler: | |
| 606 674 | 
             
                        'urgency': 'high' if any(word in prompt.lower() for word in ['urgent', 'error', 'bug', 'fix', 'broken']) else 'normal'
         | 
| 607 675 | 
             
                    }
         | 
| 608 676 |  | 
| 677 | 
            +
                    # Store prompt for comprehensive response tracking if enabled
         | 
| 678 | 
            +
                    if self.response_tracking_enabled and self.track_all_interactions:
         | 
| 679 | 
            +
                        session_id = event.get('session_id', '')
         | 
| 680 | 
            +
                        if session_id:
         | 
| 681 | 
            +
                            self.pending_prompts[session_id] = {
         | 
| 682 | 
            +
                                'prompt': prompt,
         | 
| 683 | 
            +
                                'timestamp': datetime.now().isoformat(),
         | 
| 684 | 
            +
                                'working_directory': working_dir
         | 
| 685 | 
            +
                            }
         | 
| 686 | 
            +
                            if DEBUG:
         | 
| 687 | 
            +
                                print(f"Stored prompt for comprehensive tracking: session {session_id[:8]}...", file=sys.stderr)
         | 
| 688 | 
            +
                    
         | 
| 609 689 | 
             
                    # Emit to /hook namespace
         | 
| 610 690 | 
             
                    self._emit_socketio_event('/hook', 'user_prompt', prompt_data)
         | 
| 611 691 |  | 
| @@ -617,6 +697,13 @@ class ClaudeHookHandler: | |
| 617 697 | 
             
                    - Provides context about what Claude is about to do
         | 
| 618 698 | 
             
                    - Enables pattern analysis and security monitoring
         | 
| 619 699 | 
             
                    """
         | 
| 700 | 
            +
                    # Enhanced debug logging for session correlation
         | 
| 701 | 
            +
                    session_id = event.get('session_id', '')
         | 
| 702 | 
            +
                    if DEBUG:
         | 
| 703 | 
            +
                        print(f"\n[DEBUG] PreToolUse event received:", file=sys.stderr)
         | 
| 704 | 
            +
                        print(f"  - session_id: {session_id[:16] if session_id else 'None'}...", file=sys.stderr)
         | 
| 705 | 
            +
                        print(f"  - event keys: {list(event.keys())}", file=sys.stderr)
         | 
| 706 | 
            +
                    
         | 
| 620 707 | 
             
                    tool_name = event.get('tool_name', '')
         | 
| 621 708 | 
             
                    tool_input = event.get('tool_input', {})
         | 
| 622 709 |  | 
| @@ -669,7 +756,14 @@ class ClaudeHookHandler: | |
| 669 756 | 
             
                        }
         | 
| 670 757 |  | 
| 671 758 | 
             
                        # Track this delegation for SubagentStop correlation and response tracking
         | 
| 672 | 
            -
                        session_id  | 
| 759 | 
            +
                        # session_id already extracted at method start
         | 
| 760 | 
            +
                        if DEBUG:
         | 
| 761 | 
            +
                            print(f"[DEBUG] Task delegation tracking:", file=sys.stderr)
         | 
| 762 | 
            +
                            print(f"  - session_id: {session_id[:16] if session_id else 'None'}...", file=sys.stderr)
         | 
| 763 | 
            +
                            print(f"  - agent_type: {agent_type}", file=sys.stderr)
         | 
| 764 | 
            +
                            print(f"  - raw_agent_type: {raw_agent_type}", file=sys.stderr)
         | 
| 765 | 
            +
                            print(f"  - tool_name: {tool_name}", file=sys.stderr)
         | 
| 766 | 
            +
                        
         | 
| 673 767 | 
             
                        if session_id and agent_type != 'unknown':
         | 
| 674 768 | 
             
                            # Prepare request data for response tracking correlation
         | 
| 675 769 | 
             
                            request_data = {
         | 
| @@ -679,6 +773,17 @@ class ClaudeHookHandler: | |
| 679 773 | 
             
                            }
         | 
| 680 774 | 
             
                            self._track_delegation(session_id, agent_type, request_data)
         | 
| 681 775 |  | 
| 776 | 
            +
                            if DEBUG:
         | 
| 777 | 
            +
                                print(f"  - Delegation tracked successfully", file=sys.stderr)
         | 
| 778 | 
            +
                                print(f"  - Request data keys: {list(request_data.keys())}", file=sys.stderr)
         | 
| 779 | 
            +
                                print(f"  - delegation_requests size: {len(self.delegation_requests)}", file=sys.stderr)
         | 
| 780 | 
            +
                                # Show all session IDs for debugging
         | 
| 781 | 
            +
                                all_sessions = list(self.delegation_requests.keys())
         | 
| 782 | 
            +
                                if all_sessions:
         | 
| 783 | 
            +
                                    print(f"  - All stored sessions (first 16 chars):", file=sys.stderr)
         | 
| 784 | 
            +
                                    for sid in all_sessions[:10]:  # Show up to 10
         | 
| 785 | 
            +
                                        print(f"    - {sid[:16]}... (agent: {self.delegation_requests[sid].get('agent_type', 'unknown')})", file=sys.stderr)
         | 
| 786 | 
            +
                            
         | 
| 682 787 | 
             
                            # Log important delegations for debugging
         | 
| 683 788 | 
             
                            if DEBUG or agent_type in ['research', 'engineer', 'qa', 'documentation']:
         | 
| 684 789 | 
             
                                print(f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'", file=sys.stderr)
         | 
| @@ -972,15 +1077,75 @@ class ClaudeHookHandler: | |
| 972 1077 | 
             
                    """
         | 
| 973 1078 | 
             
                    reason = event.get('reason', 'unknown')
         | 
| 974 1079 | 
             
                    stop_type = event.get('stop_type', 'normal')
         | 
| 1080 | 
            +
                    session_id = event.get('session_id', '')
         | 
| 975 1081 |  | 
| 976 1082 | 
             
                    # Get working directory and git branch
         | 
| 977 1083 | 
             
                    working_dir = event.get('cwd', '')
         | 
| 978 1084 | 
             
                    git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
         | 
| 979 1085 |  | 
| 1086 | 
            +
                    # Track response for Stop events (main Claude responses, not delegations)
         | 
| 1087 | 
            +
                    if DEBUG:
         | 
| 1088 | 
            +
                        print(f"[DEBUG] Stop event processing:", file=sys.stderr)
         | 
| 1089 | 
            +
                        print(f"  - response_tracking_enabled: {self.response_tracking_enabled}", file=sys.stderr)
         | 
| 1090 | 
            +
                        print(f"  - response_tracker exists: {self.response_tracker is not None}", file=sys.stderr)
         | 
| 1091 | 
            +
                        print(f"  - session_id: {session_id[:8] if session_id else 'None'}...", file=sys.stderr)
         | 
| 1092 | 
            +
                        print(f"  - reason: {reason}", file=sys.stderr)
         | 
| 1093 | 
            +
                        print(f"  - stop_type: {stop_type}", file=sys.stderr)
         | 
| 1094 | 
            +
                    
         | 
| 1095 | 
            +
                    if self.response_tracking_enabled and self.response_tracker:
         | 
| 1096 | 
            +
                        try:
         | 
| 1097 | 
            +
                            # Extract output from event
         | 
| 1098 | 
            +
                            output = event.get('output', '') or event.get('final_output', '') or event.get('response', '')
         | 
| 1099 | 
            +
                            
         | 
| 1100 | 
            +
                            # Check if we have a pending prompt for this session
         | 
| 1101 | 
            +
                            prompt_data = self.pending_prompts.get(session_id)
         | 
| 1102 | 
            +
                            
         | 
| 1103 | 
            +
                            if DEBUG:
         | 
| 1104 | 
            +
                                print(f"  - output present: {bool(output)} (length: {len(str(output)) if output else 0})", file=sys.stderr)
         | 
| 1105 | 
            +
                                print(f"  - prompt_data present: {bool(prompt_data)}", file=sys.stderr)
         | 
| 1106 | 
            +
                                if prompt_data:
         | 
| 1107 | 
            +
                                    print(f"  - prompt preview: {str(prompt_data.get('prompt', ''))[:100]}...", file=sys.stderr)
         | 
| 1108 | 
            +
                            
         | 
| 1109 | 
            +
                            if output and prompt_data:
         | 
| 1110 | 
            +
                                # Track the main Claude response
         | 
| 1111 | 
            +
                                metadata = {
         | 
| 1112 | 
            +
                                    'timestamp': datetime.now().isoformat(),
         | 
| 1113 | 
            +
                                    'prompt_timestamp': prompt_data.get('timestamp'),
         | 
| 1114 | 
            +
                                    'working_directory': working_dir,
         | 
| 1115 | 
            +
                                    'git_branch': git_branch,
         | 
| 1116 | 
            +
                                    'event_type': 'stop',
         | 
| 1117 | 
            +
                                    'reason': reason,
         | 
| 1118 | 
            +
                                    'stop_type': stop_type
         | 
| 1119 | 
            +
                                }
         | 
| 1120 | 
            +
                                
         | 
| 1121 | 
            +
                                file_path = self.response_tracker.track_response(
         | 
| 1122 | 
            +
                                    agent_name='claude_main',
         | 
| 1123 | 
            +
                                    request=prompt_data['prompt'],
         | 
| 1124 | 
            +
                                    response=str(output),
         | 
| 1125 | 
            +
                                    session_id=session_id,
         | 
| 1126 | 
            +
                                    metadata=metadata
         | 
| 1127 | 
            +
                                )
         | 
| 1128 | 
            +
                                
         | 
| 1129 | 
            +
                                if file_path and DEBUG:
         | 
| 1130 | 
            +
                                    print(f"✅ Tracked main Claude response on Stop event for session {session_id[:8]}...: {file_path.name}", file=sys.stderr)
         | 
| 1131 | 
            +
                                
         | 
| 1132 | 
            +
                                # Clean up the stored prompt
         | 
| 1133 | 
            +
                                if session_id in self.pending_prompts:
         | 
| 1134 | 
            +
                                    del self.pending_prompts[session_id]
         | 
| 1135 | 
            +
                                    
         | 
| 1136 | 
            +
                            elif DEBUG and not prompt_data:
         | 
| 1137 | 
            +
                                print(f"No stored prompt for Stop event session {session_id[:8]}...", file=sys.stderr)
         | 
| 1138 | 
            +
                            elif DEBUG and not output:
         | 
| 1139 | 
            +
                                print(f"No output in Stop event for session {session_id[:8]}...", file=sys.stderr)
         | 
| 1140 | 
            +
                                
         | 
| 1141 | 
            +
                        except Exception as e:
         | 
| 1142 | 
            +
                            if DEBUG:
         | 
| 1143 | 
            +
                                print(f"❌ Failed to track response on Stop event: {e}", file=sys.stderr)
         | 
| 1144 | 
            +
                    
         | 
| 980 1145 | 
             
                    stop_data = {
         | 
| 981 1146 | 
             
                        'reason': reason,
         | 
| 982 1147 | 
             
                        'stop_type': stop_type,
         | 
| 983 | 
            -
                        'session_id':  | 
| 1148 | 
            +
                        'session_id': session_id,
         | 
| 984 1149 | 
             
                        'working_directory': working_dir,
         | 
| 985 1150 | 
             
                        'git_branch': git_branch,
         | 
| 986 1151 | 
             
                        'timestamp': datetime.now().isoformat(),
         | 
| @@ -1002,8 +1167,23 @@ class ClaudeHookHandler: | |
| 1002 1167 | 
             
                    - Enables tracking of delegation success/failure patterns
         | 
| 1003 1168 | 
             
                    - Useful for understanding subagent performance and reliability
         | 
| 1004 1169 | 
             
                    """
         | 
| 1005 | 
            -
                    #  | 
| 1170 | 
            +
                    # Enhanced debug logging for session correlation
         | 
| 1006 1171 | 
             
                    session_id = event.get('session_id', '')
         | 
| 1172 | 
            +
                    if DEBUG:
         | 
| 1173 | 
            +
                        print(f"\n[DEBUG] SubagentStop event received:", file=sys.stderr)
         | 
| 1174 | 
            +
                        print(f"  - session_id: {session_id[:16] if session_id else 'None'}...", file=sys.stderr)
         | 
| 1175 | 
            +
                        print(f"  - event keys: {list(event.keys())}", file=sys.stderr)
         | 
| 1176 | 
            +
                        print(f"  - delegation_requests size: {len(self.delegation_requests)}", file=sys.stderr)
         | 
| 1177 | 
            +
                        # Show all stored session IDs for comparison
         | 
| 1178 | 
            +
                        all_sessions = list(self.delegation_requests.keys())
         | 
| 1179 | 
            +
                        if all_sessions:
         | 
| 1180 | 
            +
                            print(f"  - Stored sessions (first 16 chars):", file=sys.stderr)
         | 
| 1181 | 
            +
                            for sid in all_sessions[:10]:  # Show up to 10
         | 
| 1182 | 
            +
                                print(f"    - {sid[:16]}... (agent: {self.delegation_requests[sid].get('agent_type', 'unknown')})", file=sys.stderr)
         | 
| 1183 | 
            +
                        else:
         | 
| 1184 | 
            +
                            print(f"  - No stored sessions in delegation_requests!", file=sys.stderr)
         | 
| 1185 | 
            +
                    
         | 
| 1186 | 
            +
                    # First try to get agent type from our tracking
         | 
| 1007 1187 | 
             
                    agent_type = self._get_delegation_agent_type(session_id) if session_id else 'unknown'
         | 
| 1008 1188 |  | 
| 1009 1189 | 
             
                    # Fall back to event data if tracking didn't have it
         | 
| @@ -1031,6 +1211,137 @@ class ClaudeHookHandler: | |
| 1031 1211 | 
             
                    working_dir = event.get('cwd', '')
         | 
| 1032 1212 | 
             
                    git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
         | 
| 1033 1213 |  | 
| 1214 | 
            +
                    # Try to extract structured response from output if available
         | 
| 1215 | 
            +
                    output = event.get('output', '')
         | 
| 1216 | 
            +
                    structured_response = None
         | 
| 1217 | 
            +
                    if output:
         | 
| 1218 | 
            +
                        try:
         | 
| 1219 | 
            +
                            import re
         | 
| 1220 | 
            +
                            json_match = re.search(r'```json\s*(\{.*?\})\s*```', str(output), re.DOTALL)
         | 
| 1221 | 
            +
                            if json_match:
         | 
| 1222 | 
            +
                                structured_response = json.loads(json_match.group(1))
         | 
| 1223 | 
            +
                                if DEBUG:
         | 
| 1224 | 
            +
                                    print(f"Extracted structured response from {agent_type} agent in SubagentStop", file=sys.stderr)
         | 
| 1225 | 
            +
                        except (json.JSONDecodeError, AttributeError):
         | 
| 1226 | 
            +
                            pass  # No structured response, that's okay
         | 
| 1227 | 
            +
                    
         | 
| 1228 | 
            +
                    # Track agent response even without structured JSON
         | 
| 1229 | 
            +
                    if DEBUG:
         | 
| 1230 | 
            +
                        print(f"[DEBUG] SubagentStop response tracking check:", file=sys.stderr)
         | 
| 1231 | 
            +
                        print(f"  - response_tracking_enabled: {self.response_tracking_enabled}", file=sys.stderr)
         | 
| 1232 | 
            +
                        print(f"  - response_tracker exists: {self.response_tracker is not None}", file=sys.stderr)
         | 
| 1233 | 
            +
                        print(f"  - session_id: {session_id[:16] if session_id else 'None'}...", file=sys.stderr)
         | 
| 1234 | 
            +
                        print(f"  - agent_type: {agent_type}", file=sys.stderr)
         | 
| 1235 | 
            +
                        print(f"  - reason: {reason}", file=sys.stderr)
         | 
| 1236 | 
            +
                        # Check if session exists in our storage
         | 
| 1237 | 
            +
                        if session_id in self.delegation_requests:
         | 
| 1238 | 
            +
                            print(f"  - ✅ Session found in delegation_requests", file=sys.stderr)
         | 
| 1239 | 
            +
                            print(f"  - Stored agent: {self.delegation_requests[session_id].get('agent_type')}", file=sys.stderr)
         | 
| 1240 | 
            +
                        else:
         | 
| 1241 | 
            +
                            print(f"  - ❌ Session NOT found in delegation_requests!", file=sys.stderr)
         | 
| 1242 | 
            +
                            print(f"  - Looking for partial match...", file=sys.stderr)
         | 
| 1243 | 
            +
                            # Try to find partial matches
         | 
| 1244 | 
            +
                            for stored_sid in list(self.delegation_requests.keys())[:10]:
         | 
| 1245 | 
            +
                                if stored_sid.startswith(session_id[:8]) or session_id.startswith(stored_sid[:8]):
         | 
| 1246 | 
            +
                                    print(f"    - Partial match found: {stored_sid[:16]}...", file=sys.stderr)
         | 
| 1247 | 
            +
                    
         | 
| 1248 | 
            +
                    if self.response_tracking_enabled and self.response_tracker:
         | 
| 1249 | 
            +
                        try:
         | 
| 1250 | 
            +
                            # Get the original request data (with fuzzy matching fallback)
         | 
| 1251 | 
            +
                            request_info = self.delegation_requests.get(session_id)
         | 
| 1252 | 
            +
                            
         | 
| 1253 | 
            +
                            # If exact match fails, try partial matching
         | 
| 1254 | 
            +
                            if not request_info and session_id:
         | 
| 1255 | 
            +
                                if DEBUG:
         | 
| 1256 | 
            +
                                    print(f"  - Trying fuzzy match for session {session_id[:16]}...", file=sys.stderr)
         | 
| 1257 | 
            +
                                # Try to find a session that matches the first 8-16 characters
         | 
| 1258 | 
            +
                                for stored_sid in list(self.delegation_requests.keys()):
         | 
| 1259 | 
            +
                                    if (stored_sid.startswith(session_id[:8]) or 
         | 
| 1260 | 
            +
                                        session_id.startswith(stored_sid[:8]) or
         | 
| 1261 | 
            +
                                        (len(session_id) >= 16 and len(stored_sid) >= 16 and 
         | 
| 1262 | 
            +
                                         stored_sid[:16] == session_id[:16])):
         | 
| 1263 | 
            +
                                        if DEBUG:
         | 
| 1264 | 
            +
                                            print(f"  - \u2705 Fuzzy match found: {stored_sid[:16]}...", file=sys.stderr)
         | 
| 1265 | 
            +
                                        request_info = self.delegation_requests.get(stored_sid)
         | 
| 1266 | 
            +
                                        # Update the key to use the current session_id for consistency
         | 
| 1267 | 
            +
                                        if request_info:
         | 
| 1268 | 
            +
                                            self.delegation_requests[session_id] = request_info
         | 
| 1269 | 
            +
                                            # Optionally remove the old key to avoid duplicates
         | 
| 1270 | 
            +
                                            if stored_sid != session_id:
         | 
| 1271 | 
            +
                                                del self.delegation_requests[stored_sid]
         | 
| 1272 | 
            +
                                        break
         | 
| 1273 | 
            +
                            
         | 
| 1274 | 
            +
                            if DEBUG:
         | 
| 1275 | 
            +
                                print(f"  - request_info present: {bool(request_info)}", file=sys.stderr)
         | 
| 1276 | 
            +
                                if request_info:
         | 
| 1277 | 
            +
                                    print(f"  - ✅ Found request data for response tracking", file=sys.stderr)
         | 
| 1278 | 
            +
                                    print(f"  - stored agent_type: {request_info.get('agent_type')}", file=sys.stderr)
         | 
| 1279 | 
            +
                                    print(f"  - request keys: {list(request_info.get('request', {}).keys())}", file=sys.stderr)
         | 
| 1280 | 
            +
                                else:
         | 
| 1281 | 
            +
                                    print(f"  - ❌ No request data found for session {session_id[:16]}...", file=sys.stderr)
         | 
| 1282 | 
            +
                            
         | 
| 1283 | 
            +
                            if request_info:
         | 
| 1284 | 
            +
                                # Use the output as the response
         | 
| 1285 | 
            +
                                response_text = str(output) if output else f"Agent {agent_type} completed with reason: {reason}"
         | 
| 1286 | 
            +
                                
         | 
| 1287 | 
            +
                                # Get the original request
         | 
| 1288 | 
            +
                                original_request = request_info.get('request', {})
         | 
| 1289 | 
            +
                                prompt = original_request.get('prompt', '')
         | 
| 1290 | 
            +
                                description = original_request.get('description', '')
         | 
| 1291 | 
            +
                                
         | 
| 1292 | 
            +
                                # Combine prompt and description
         | 
| 1293 | 
            +
                                full_request = prompt
         | 
| 1294 | 
            +
                                if description and description != prompt:
         | 
| 1295 | 
            +
                                    if full_request:
         | 
| 1296 | 
            +
                                        full_request += f"\n\nDescription: {description}"
         | 
| 1297 | 
            +
                                    else:
         | 
| 1298 | 
            +
                                        full_request = description
         | 
| 1299 | 
            +
                                
         | 
| 1300 | 
            +
                                if not full_request:
         | 
| 1301 | 
            +
                                    full_request = f"Task delegation to {agent_type} agent"
         | 
| 1302 | 
            +
                                
         | 
| 1303 | 
            +
                                # Prepare metadata
         | 
| 1304 | 
            +
                                metadata = {
         | 
| 1305 | 
            +
                                    'exit_code': event.get('exit_code', 0),
         | 
| 1306 | 
            +
                                    'success': reason in ['completed', 'finished', 'done'],
         | 
| 1307 | 
            +
                                    'has_error': reason in ['error', 'timeout', 'failed', 'blocked'],
         | 
| 1308 | 
            +
                                    'duration_ms': event.get('duration_ms'),
         | 
| 1309 | 
            +
                                    'working_directory': working_dir,
         | 
| 1310 | 
            +
                                    'git_branch': git_branch,
         | 
| 1311 | 
            +
                                    'timestamp': datetime.now().isoformat(),
         | 
| 1312 | 
            +
                                    'event_type': 'subagent_stop',
         | 
| 1313 | 
            +
                                    'reason': reason,
         | 
| 1314 | 
            +
                                    'original_request_timestamp': request_info.get('timestamp')
         | 
| 1315 | 
            +
                                }
         | 
| 1316 | 
            +
                                
         | 
| 1317 | 
            +
                                # Add structured response if available
         | 
| 1318 | 
            +
                                if structured_response:
         | 
| 1319 | 
            +
                                    metadata['structured_response'] = structured_response
         | 
| 1320 | 
            +
                                    metadata['task_completed'] = structured_response.get('task_completed', False)
         | 
| 1321 | 
            +
                                
         | 
| 1322 | 
            +
                                # Track the response
         | 
| 1323 | 
            +
                                file_path = self.response_tracker.track_response(
         | 
| 1324 | 
            +
                                    agent_name=agent_type,
         | 
| 1325 | 
            +
                                    request=full_request,
         | 
| 1326 | 
            +
                                    response=response_text,
         | 
| 1327 | 
            +
                                    session_id=session_id,
         | 
| 1328 | 
            +
                                    metadata=metadata
         | 
| 1329 | 
            +
                                )
         | 
| 1330 | 
            +
                                
         | 
| 1331 | 
            +
                                if file_path and DEBUG:
         | 
| 1332 | 
            +
                                    print(f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}", file=sys.stderr)
         | 
| 1333 | 
            +
                                
         | 
| 1334 | 
            +
                                # Clean up the request data
         | 
| 1335 | 
            +
                                if session_id in self.delegation_requests:
         | 
| 1336 | 
            +
                                    del self.delegation_requests[session_id]
         | 
| 1337 | 
            +
                                    
         | 
| 1338 | 
            +
                            elif DEBUG:
         | 
| 1339 | 
            +
                                print(f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}", file=sys.stderr)
         | 
| 1340 | 
            +
                                
         | 
| 1341 | 
            +
                        except Exception as e:
         | 
| 1342 | 
            +
                            if DEBUG:
         | 
| 1343 | 
            +
                                print(f"❌ Failed to track response on SubagentStop: {e}", file=sys.stderr)
         | 
| 1344 | 
            +
                    
         | 
| 1034 1345 | 
             
                    subagent_stop_data = {
         | 
| 1035 1346 | 
             
                        'agent_type': agent_type,
         | 
| 1036 1347 | 
             
                        'agent_id': agent_id,
         | 
| @@ -1047,6 +1358,17 @@ class ClaudeHookHandler: | |
| 1047 1358 | 
             
                        'hook_event_name': 'SubagentStop'  # Explicitly set for dashboard
         | 
| 1048 1359 | 
             
                    }
         | 
| 1049 1360 |  | 
| 1361 | 
            +
                    # Add structured response data if available
         | 
| 1362 | 
            +
                    if structured_response:
         | 
| 1363 | 
            +
                        subagent_stop_data['structured_response'] = {
         | 
| 1364 | 
            +
                            'task_completed': structured_response.get('task_completed', False),
         | 
| 1365 | 
            +
                            'instructions': structured_response.get('instructions', ''),
         | 
| 1366 | 
            +
                            'results': structured_response.get('results', ''),
         | 
| 1367 | 
            +
                            'files_modified': structured_response.get('files_modified', []),
         | 
| 1368 | 
            +
                            'tools_used': structured_response.get('tools_used', []),
         | 
| 1369 | 
            +
                            'remember': structured_response.get('remember')
         | 
| 1370 | 
            +
                        }
         | 
| 1371 | 
            +
                    
         | 
| 1050 1372 | 
             
                    # Debug log the processed data
         | 
| 1051 1373 | 
             
                    if DEBUG:
         | 
| 1052 1374 | 
             
                        print(f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'", file=sys.stderr)
         | 
| @@ -1054,6 +1376,66 @@ class ClaudeHookHandler: | |
| 1054 1376 | 
             
                    # Emit to /hook namespace with high priority
         | 
| 1055 1377 | 
             
                    self._emit_socketio_event('/hook', 'subagent_stop', subagent_stop_data)
         | 
| 1056 1378 |  | 
| 1379 | 
            +
                def _handle_assistant_response(self, event):
         | 
| 1380 | 
            +
                    """Handle assistant response events for comprehensive response tracking.
         | 
| 1381 | 
            +
                    
         | 
| 1382 | 
            +
                    WHY: This enables capture of all Claude responses, not just Task delegations.
         | 
| 1383 | 
            +
                    When track_all_interactions is enabled, we capture every Claude response
         | 
| 1384 | 
            +
                    paired with its original user prompt.
         | 
| 1385 | 
            +
                    
         | 
| 1386 | 
            +
                    DESIGN DECISION: We correlate responses with stored prompts using session_id.
         | 
| 1387 | 
            +
                    This provides complete conversation tracking for analysis and learning.
         | 
| 1388 | 
            +
                    """
         | 
| 1389 | 
            +
                    if not self.response_tracking_enabled or not self.track_all_interactions:
         | 
| 1390 | 
            +
                        return
         | 
| 1391 | 
            +
                    
         | 
| 1392 | 
            +
                    session_id = event.get('session_id', '')
         | 
| 1393 | 
            +
                    if not session_id:
         | 
| 1394 | 
            +
                        return
         | 
| 1395 | 
            +
                    
         | 
| 1396 | 
            +
                    # Get the stored prompt for this session
         | 
| 1397 | 
            +
                    prompt_data = self.pending_prompts.get(session_id)
         | 
| 1398 | 
            +
                    if not prompt_data:
         | 
| 1399 | 
            +
                        if DEBUG:
         | 
| 1400 | 
            +
                            print(f"No stored prompt for session {session_id[:8]}..., skipping response tracking", file=sys.stderr)
         | 
| 1401 | 
            +
                        return
         | 
| 1402 | 
            +
                    
         | 
| 1403 | 
            +
                    try:
         | 
| 1404 | 
            +
                        # Extract response content from event
         | 
| 1405 | 
            +
                        response_content = event.get('response', '') or event.get('content', '') or event.get('text', '')
         | 
| 1406 | 
            +
                        
         | 
| 1407 | 
            +
                        if not response_content:
         | 
| 1408 | 
            +
                            if DEBUG:
         | 
| 1409 | 
            +
                                print(f"No response content in event for session {session_id[:8]}...", file=sys.stderr)
         | 
| 1410 | 
            +
                            return
         | 
| 1411 | 
            +
                        
         | 
| 1412 | 
            +
                        # Track the response
         | 
| 1413 | 
            +
                        metadata = {
         | 
| 1414 | 
            +
                            'timestamp': datetime.now().isoformat(),
         | 
| 1415 | 
            +
                            'prompt_timestamp': prompt_data.get('timestamp'),
         | 
| 1416 | 
            +
                            'working_directory': prompt_data.get('working_directory', ''),
         | 
| 1417 | 
            +
                            'event_type': 'assistant_response',
         | 
| 1418 | 
            +
                            'session_type': 'interactive'
         | 
| 1419 | 
            +
                        }
         | 
| 1420 | 
            +
                        
         | 
| 1421 | 
            +
                        file_path = self.response_tracker.track_response(
         | 
| 1422 | 
            +
                            agent_name='claude',
         | 
| 1423 | 
            +
                            request=prompt_data['prompt'],
         | 
| 1424 | 
            +
                            response=response_content,
         | 
| 1425 | 
            +
                            session_id=session_id,
         | 
| 1426 | 
            +
                            metadata=metadata
         | 
| 1427 | 
            +
                        )
         | 
| 1428 | 
            +
                        
         | 
| 1429 | 
            +
                        if file_path and DEBUG:
         | 
| 1430 | 
            +
                            print(f"✅ Tracked Claude response for session {session_id[:8]}...: {file_path.name}", file=sys.stderr)
         | 
| 1431 | 
            +
                        
         | 
| 1432 | 
            +
                        # Clean up the stored prompt
         | 
| 1433 | 
            +
                        del self.pending_prompts[session_id]
         | 
| 1434 | 
            +
                        
         | 
| 1435 | 
            +
                    except Exception as e:
         | 
| 1436 | 
            +
                        if DEBUG:
         | 
| 1437 | 
            +
                            print(f"❌ Failed to track assistant response: {e}", file=sys.stderr)
         | 
| 1438 | 
            +
                
         | 
| 1057 1439 | 
             
                def _trigger_memory_pre_delegation_hook(self, agent_type: str, tool_input: dict, session_id: str):
         | 
| 1058 1440 | 
             
                    """Trigger memory pre-delegation hook for agent memory injection.
         | 
| 1059 1441 |  |