claude-mpm 3.4.27__py3-none-any.whl → 3.5.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.
Files changed (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +5 -7
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,151 @@
1
+ """
2
+ Centralized configuration paths for claude-mpm.
3
+
4
+ This module provides a single source of truth for all configuration-related paths
5
+ throughout the codebase. All modules should import and use these constants
6
+ instead of hardcoding directory names.
7
+ """
8
+
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+
14
+ class ConfigDirName(Enum):
15
+ """Enum for configuration directory names to ensure type safety."""
16
+ CLAUDE_MPM = ".claude-mpm"
17
+
18
+
19
+ # Core configuration directory name - MUST be consistent everywhere
20
+ CONFIG_DIR_NAME = ConfigDirName.CLAUDE_MPM.value
21
+
22
+ # Legacy name that should NOT be used (kept for reference during migration)
23
+ _LEGACY_CONFIG_DIR_NAME = ".claude-pm" # DO NOT USE THIS
24
+
25
+
26
+ class ConfigPaths:
27
+ """Centralized configuration path constants."""
28
+
29
+ # Base directory names
30
+ CONFIG_DIR = ConfigDirName.CLAUDE_MPM.value
31
+ AGENTS_DIR = "agents"
32
+ BACKUPS_DIR = "backups"
33
+ TRACKING_DIR = "agent_tracking"
34
+ MEMORIES_DIR = "memories"
35
+ RESPONSES_DIR = "responses"
36
+
37
+ # Agent subdirectories
38
+ AGENT_PROJECT_SPECIFIC = "project-specific"
39
+ AGENT_USER_AGENTS = "user-agents"
40
+ AGENT_USER_DEFINED = "user-defined"
41
+ AGENT_SYSTEM = "templates"
42
+
43
+ # Configuration files
44
+ CONFIG_FILE = "config.json"
45
+ CONFIGURATION_YAML = "configuration.yaml"
46
+ LIFECYCLE_RECORDS = "lifecycle_records.json"
47
+ VERSION_FILE = "VERSION"
48
+ INSTRUCTIONS_FILE = "INSTRUCTIONS.md"
49
+
50
+ @classmethod
51
+ def get_user_config_dir(cls) -> Path:
52
+ """Get the user-level configuration directory."""
53
+ return Path.home() / cls.CONFIG_DIR
54
+
55
+ @classmethod
56
+ def get_project_config_dir(cls, project_root: Optional[Path] = None) -> Path:
57
+ """Get the project-level configuration directory."""
58
+ root = project_root or Path.cwd()
59
+ return root / cls.CONFIG_DIR
60
+
61
+ @classmethod
62
+ def get_framework_config_dir(cls, framework_root: Path) -> Path:
63
+ """Get the framework-level configuration directory."""
64
+ return framework_root / cls.CONFIG_DIR
65
+
66
+ @classmethod
67
+ def get_user_agents_dir(cls) -> Path:
68
+ """Get the user-level agents directory."""
69
+ return cls.get_user_config_dir() / cls.AGENTS_DIR
70
+
71
+ @classmethod
72
+ def get_project_agents_dir(cls, project_root: Optional[Path] = None) -> Path:
73
+ """Get the project-level agents directory."""
74
+ return cls.get_project_config_dir(project_root) / cls.AGENTS_DIR
75
+
76
+ @classmethod
77
+ def get_tracking_dir(cls) -> Path:
78
+ """Get the agent tracking directory."""
79
+ return cls.get_user_config_dir() / cls.TRACKING_DIR
80
+
81
+ @classmethod
82
+ def get_backups_dir(cls, base_dir: Optional[Path] = None) -> Path:
83
+ """Get the backups directory."""
84
+ if base_dir:
85
+ return base_dir / cls.CONFIG_DIR / cls.BACKUPS_DIR
86
+ return cls.get_user_config_dir() / cls.BACKUPS_DIR
87
+
88
+ @classmethod
89
+ def get_memories_dir(cls, base_dir: Optional[Path] = None) -> Path:
90
+ """Get the memories directory."""
91
+ if base_dir:
92
+ return base_dir / cls.CONFIG_DIR / cls.MEMORIES_DIR
93
+ return cls.get_project_config_dir() / cls.MEMORIES_DIR
94
+
95
+ @classmethod
96
+ def get_responses_dir(cls, base_dir: Optional[Path] = None) -> Path:
97
+ """Get the responses directory."""
98
+ if base_dir:
99
+ return base_dir / cls.CONFIG_DIR / cls.RESPONSES_DIR
100
+ return cls.get_project_config_dir() / cls.RESPONSES_DIR
101
+
102
+ @classmethod
103
+ def find_config_dir(cls, start_path: Optional[Path] = None) -> Optional[Path]:
104
+ """
105
+ Find the nearest configuration directory by walking up the directory tree.
106
+
107
+ Args:
108
+ start_path: Starting directory (defaults to current working directory)
109
+
110
+ Returns:
111
+ Path to the configuration directory if found, None otherwise
112
+ """
113
+ current = start_path or Path.cwd()
114
+
115
+ # Check current directory and parents
116
+ for path in [current] + list(current.parents):
117
+ config_dir = path / cls.CONFIG_DIR
118
+ if config_dir.exists():
119
+ return config_dir
120
+
121
+ return None
122
+
123
+ @classmethod
124
+ def validate_not_legacy(cls, path: Path) -> bool:
125
+ """
126
+ Check if a path contains the legacy configuration directory name.
127
+
128
+ Args:
129
+ path: Path to check
130
+
131
+ Returns:
132
+ True if path is valid (not legacy), False if it contains legacy name
133
+ """
134
+ path_str = str(path)
135
+ return _LEGACY_CONFIG_DIR_NAME not in path_str
136
+
137
+
138
+ # Export commonly used paths as module-level constants for convenience
139
+ USER_CONFIG_DIR = ConfigPaths.get_user_config_dir()
140
+ USER_AGENTS_DIR = ConfigPaths.get_user_agents_dir()
141
+ USER_TRACKING_DIR = ConfigPaths.get_tracking_dir()
142
+
143
+ # Export all public symbols
144
+ __all__ = [
145
+ "ConfigDirName",
146
+ "ConfigPaths",
147
+ "CONFIG_DIR_NAME",
148
+ "USER_CONFIG_DIR",
149
+ "USER_AGENTS_DIR",
150
+ "USER_TRACKING_DIR",
151
+ ]
@@ -12,9 +12,8 @@ from typing import Any, Dict, Optional, Type, TypeVar
12
12
  from .container import DIContainer
13
13
  from .logger import get_logger
14
14
  from .config import Config
15
- from ..services.agent_deployment import AgentDeploymentService
16
- from ..orchestration.factory import OrchestratorFactory
17
- from ..orchestration.base import BaseOrchestrator
15
+ from ..services import AgentDeploymentService
16
+ # Note: Orchestration functionality has been replaced by claude_runner
18
17
 
19
18
  logger = get_logger(__name__)
20
19
 
@@ -32,52 +31,7 @@ class ServiceFactory(ABC):
32
31
 
33
32
 
34
33
 
35
- class OrchestratorFactoryWrapper(ServiceFactory):
36
- """Factory wrapper for creating orchestrator instances."""
37
-
38
- def __init__(self):
39
- """Initialize the factory wrapper."""
40
- self._factory = OrchestratorFactory()
41
-
42
- def create(
43
- self,
44
- container: DIContainer,
45
- orchestrator_type: Optional[str] = None,
46
- **kwargs
47
- ) -> BaseOrchestrator:
48
- """
49
- Create an orchestrator instance.
50
-
51
- Args:
52
- container: DI container
53
- orchestrator_type: Type of orchestrator to create
54
- **kwargs: Additional arguments for orchestrator
55
-
56
- Returns:
57
- Orchestrator instance
58
- """
59
- config = container.resolve(Config)
60
-
61
- # Get orchestrator type from config if not provided
62
- if orchestrator_type is None:
63
- orchestrator_type = config.get('orchestrator.type', 'subprocess')
64
-
65
- # Get orchestrator config
66
- orch_config = config.get(f'orchestrator.{orchestrator_type}', {})
67
-
68
- # Merge with provided kwargs
69
- orch_config.update(kwargs)
70
-
71
- # No hook manager injection needed - Claude Code hooks are external
72
-
73
- # Create orchestrator
74
- orchestrator = self._factory.create_orchestrator(
75
- orchestrator_type,
76
- **orch_config
77
- )
78
-
79
- logger.info(f"Created {orchestrator_type} orchestrator")
80
- return orchestrator
34
+ # OrchestratorFactoryWrapper has been removed - orchestration replaced by claude_runner
81
35
 
82
36
 
83
37
  class AgentServiceFactory(ServiceFactory):
@@ -204,7 +158,7 @@ class FactoryRegistry:
204
158
  def __init__(self):
205
159
  """Initialize factory registry."""
206
160
  self._factories: Dict[str, ServiceFactory] = {
207
- 'orchestrator': OrchestratorFactoryWrapper(),
161
+ # 'orchestrator' removed - replaced by claude_runner
208
162
  'agent_service': AgentServiceFactory(),
209
163
  'session_manager': SessionManagerFactory(),
210
164
  'configuration': ConfigurationFactory(),
claude_mpm/core/logger.py CHANGED
@@ -18,13 +18,8 @@ from enum import Enum
18
18
  from collections import defaultdict
19
19
  import functools
20
20
 
21
- # Optional Rich support
22
- try:
23
- from rich.logging import RichHandler
24
- from rich.console import Console
25
- HAS_RICH = True
26
- except ImportError:
27
- HAS_RICH = False
21
+ # Rich support has been removed
22
+ HAS_RICH = False
28
23
 
29
24
 
30
25
  class LogLevel(Enum):
@@ -150,7 +145,7 @@ def setup_logging(
150
145
  log_file: Specific log file path (overrides log_dir)
151
146
  console_output: Enable console output
152
147
  file_output: Enable file output
153
- use_rich: Use Rich handler for colored console output
148
+ use_rich: (Deprecated) Rich support has been removed
154
149
  json_format: Use JSON format for structured logging
155
150
  use_streaming: Use streaming handler for single-line INFO messages
156
151
 
@@ -187,10 +182,10 @@ def setup_logging(
187
182
  # Use streaming handler for single-line INFO messages
188
183
  console_handler = StreamingHandler(sys.stdout)
189
184
  console_handler.setFormatter(simple_formatter)
190
- elif use_rich and HAS_RICH and not json_format:
191
- console = Console(stderr=True)
192
- console_handler = RichHandler(console=console, show_time=True, show_path=True, markup=True)
193
- console_handler.setFormatter(logging.Formatter(fmt="%(message)s", datefmt="[%X]"))
185
+ elif use_rich and not json_format:
186
+ # Rich support has been removed, use standard handler
187
+ console_handler = logging.StreamHandler(sys.stdout)
188
+ console_handler.setFormatter(simple_formatter)
194
189
  else:
195
190
  console_handler = logging.StreamHandler(sys.stdout)
196
191
  console_handler.setFormatter(formatter if json_format else simple_formatter)
@@ -212,7 +207,10 @@ def setup_logging(
212
207
  else:
213
208
  # Use default log directory
214
209
  if log_dir is None:
215
- log_dir = Path.home() / ".claude-mpm" / "logs"
210
+ # Use deployment root for logs to keep everything centralized
211
+ from claude_mpm.deployment_paths import get_project_root
212
+ deployment_root = get_project_root()
213
+ log_dir = deployment_root / ".claude-mpm" / "logs"
216
214
 
217
215
  log_dir.mkdir(parents=True, exist_ok=True)
218
216
 
@@ -36,9 +36,9 @@ class ServiceRegistry:
36
36
 
37
37
  def register_core_services(self) -> None:
38
38
  """Register all core framework services."""
39
- from ..services.shared_prompt_cache import SharedPromptCache
39
+ from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
40
40
  from ..services.ticket_manager import TicketManager
41
- from ..services.agent_deployment import AgentDeploymentService
41
+ from ..services import AgentDeploymentService
42
42
  from .session_manager import SessionManager
43
43
  from .agent_session_manager import AgentSessionManager
44
44
  from .config import Config
@@ -77,6 +77,14 @@ class AgentInference {
77
77
  // Direct event detection (highest confidence) - from design doc
78
78
  if (eventType === 'SubagentStop' || subtype === 'subagent_stop') {
79
79
  const agentName = this.extractAgentNameFromEvent(event);
80
+ // Log SubagentStop events for debugging
81
+ console.log('SubagentStop event detected:', {
82
+ agentName: agentName,
83
+ sessionId: sessionId,
84
+ eventType: eventType,
85
+ subtype: subtype,
86
+ rawAgentType: event.agent_type || data.agent_type
87
+ });
80
88
  return {
81
89
  type: 'subagent',
82
90
  confidence: 'definitive',
@@ -98,6 +106,12 @@ class AgentInference {
98
106
  if (toolName === 'Task') {
99
107
  const agentName = this.extractSubagentTypeFromTask(event);
100
108
  if (agentName) {
109
+ // Log Task delegations for debugging
110
+ console.log('Task delegation detected:', {
111
+ agentName: agentName,
112
+ sessionId: sessionId,
113
+ eventType: eventType
114
+ });
101
115
  return {
102
116
  type: 'subagent',
103
117
  confidence: 'high',
@@ -141,7 +155,7 @@ class AgentInference {
141
155
  return {
142
156
  type: 'subagent',
143
157
  confidence: 'high',
144
- agentName: subagentType,
158
+ agentName: this.normalizeAgentName(subagentType),
145
159
  reason: 'subagent_type field'
146
160
  };
147
161
  }
@@ -150,7 +164,7 @@ class AgentInference {
150
164
  return {
151
165
  type: 'subagent',
152
166
  confidence: 'medium',
153
- agentName: agentType,
167
+ agentName: this.normalizeAgentName(agentType),
154
168
  reason: 'agent_type field'
155
169
  };
156
170
  }
@@ -160,7 +174,7 @@ class AgentInference {
160
174
  return {
161
175
  type: 'subagent',
162
176
  confidence: 'high',
163
- agentName: data.delegation_details.agent_type,
177
+ agentName: this.normalizeAgentName(data.delegation_details.agent_type),
164
178
  reason: 'delegation_details'
165
179
  };
166
180
  }
@@ -169,12 +183,30 @@ class AgentInference {
169
183
  if (event.type && event.type.startsWith('hook.')) {
170
184
  // Extract the hook type
171
185
  const hookType = event.type.replace('hook.', '');
186
+
187
+ // Handle SubagentStart events
188
+ if (hookType === 'subagent_start' || (data.hook_event_name === 'SubagentStart')) {
189
+ const rawAgentName = data.agent_type || data.agent_id || 'Subagent';
190
+ console.log('SubagentStart event from Socket.IO:', {
191
+ agentName: rawAgentName,
192
+ sessionId: sessionId,
193
+ hookType: hookType
194
+ });
195
+ return {
196
+ type: 'subagent',
197
+ confidence: 'definitive',
198
+ agentName: this.normalizeAgentName(rawAgentName),
199
+ reason: 'Socket.IO hook SubagentStart'
200
+ };
201
+ }
202
+
203
+ // Handle SubagentStop events
172
204
  if (hookType === 'subagent_stop' || (data.hook_event_name === 'SubagentStop')) {
173
- const agentName = data.agent_type || data.agent_id || 'Subagent';
205
+ const rawAgentName = data.agent_type || data.agent_id || 'Subagent';
174
206
  return {
175
207
  type: 'subagent',
176
208
  confidence: 'high',
177
- agentName: agentName,
209
+ agentName: this.normalizeAgentName(rawAgentName),
178
210
  reason: 'Socket.IO hook SubagentStop'
179
211
  };
180
212
  }
@@ -189,33 +221,77 @@ class AgentInference {
189
221
  };
190
222
  }
191
223
 
224
+ /**
225
+ * Normalize agent name from lowercase/underscore format to display format
226
+ * @param {string} agentName - Raw agent name (e.g., 'engineer', 'test_integration')
227
+ * @returns {string} - Normalized display name (e.g., 'Engineer Agent', 'Test Integration Agent')
228
+ */
229
+ normalizeAgentName(agentName) {
230
+ if (!agentName) return 'Unknown';
231
+
232
+ // Agent name mapping from raw format to display format
233
+ const agentNameMap = {
234
+ 'engineer': 'Engineer Agent',
235
+ 'research': 'Research Agent',
236
+ 'qa': 'QA Agent',
237
+ 'documentation': 'Documentation Agent',
238
+ 'security': 'Security Agent',
239
+ 'ops': 'Ops Agent',
240
+ 'version_control': 'Version Control Agent',
241
+ 'data_engineer': 'Data Engineer Agent',
242
+ 'test_integration': 'Test Integration Agent',
243
+ 'pm': 'PM Agent'
244
+ };
245
+
246
+ // Check if we have a direct mapping
247
+ const normalized = agentNameMap[agentName.toLowerCase()];
248
+ if (normalized) {
249
+ return normalized;
250
+ }
251
+
252
+ // If no direct mapping, apply basic formatting:
253
+ // Convert underscore to space, capitalize words, and add "Agent" if not present
254
+ let formatted = agentName
255
+ .replace(/_/g, ' ')
256
+ .split(' ')
257
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
258
+ .join(' ');
259
+
260
+ // Add "Agent" suffix if not already present
261
+ if (!formatted.toLowerCase().includes('agent')) {
262
+ formatted += ' Agent';
263
+ }
264
+
265
+ return formatted;
266
+ }
267
+
192
268
  /**
193
269
  * Extract subagent type from Task tool parameters
194
270
  * @param {Object} event - Event with Task tool
195
271
  * @returns {string|null} - Subagent type or null
196
272
  */
197
273
  extractSubagentTypeFromTask(event) {
274
+ let rawAgentName = null;
275
+
198
276
  // Check tool_parameters directly
199
277
  if (event.tool_parameters?.subagent_type) {
200
- return event.tool_parameters.subagent_type;
278
+ rawAgentName = event.tool_parameters.subagent_type;
201
279
  }
202
-
203
280
  // Check nested in data.tool_parameters (hook events)
204
- if (event.data?.tool_parameters?.subagent_type) {
205
- return event.data.tool_parameters.subagent_type;
281
+ else if (event.data?.tool_parameters?.subagent_type) {
282
+ rawAgentName = event.data.tool_parameters.subagent_type;
206
283
  }
207
-
208
284
  // Check delegation_details (new structure)
209
- if (event.data?.delegation_details?.agent_type) {
210
- return event.data.delegation_details.agent_type;
285
+ else if (event.data?.delegation_details?.agent_type) {
286
+ rawAgentName = event.data.delegation_details.agent_type;
211
287
  }
212
-
213
288
  // Check tool_input fallback
214
- if (event.tool_input?.subagent_type) {
215
- return event.tool_input.subagent_type;
289
+ else if (event.tool_input?.subagent_type) {
290
+ rawAgentName = event.tool_input.subagent_type;
216
291
  }
217
292
 
218
- return null;
293
+ // Normalize the agent name before returning
294
+ return rawAgentName ? this.normalizeAgentName(rawAgentName) : null;
219
295
  }
220
296
 
221
297
  /**
@@ -235,40 +311,40 @@ class AgentInference {
235
311
 
236
312
  // 2. Direct subagent_type field
237
313
  if (event.subagent_type && event.subagent_type !== 'unknown') {
238
- return event.subagent_type;
314
+ return this.normalizeAgentName(event.subagent_type);
239
315
  }
240
316
  if (data.subagent_type && data.subagent_type !== 'unknown') {
241
- return data.subagent_type;
317
+ return this.normalizeAgentName(data.subagent_type);
242
318
  }
243
319
 
244
320
  // 2.5. Check delegation_details
245
321
  if (data.delegation_details?.agent_type && data.delegation_details.agent_type !== 'unknown') {
246
- return data.delegation_details.agent_type;
322
+ return this.normalizeAgentName(data.delegation_details.agent_type);
247
323
  }
248
324
 
249
325
  // 3. Agent type fields (but not 'main' or 'unknown')
250
326
  if (event.agent_type && !['main', 'unknown'].includes(event.agent_type)) {
251
- return event.agent_type;
327
+ return this.normalizeAgentName(event.agent_type);
252
328
  }
253
329
  if (data.agent_type && !['main', 'unknown'].includes(data.agent_type)) {
254
- return data.agent_type;
330
+ return this.normalizeAgentName(data.agent_type);
255
331
  }
256
332
 
257
333
  // 4. Agent ID field as fallback
258
334
  if (event.agent_id && !['main', 'unknown'].includes(event.agent_id)) {
259
- return event.agent_id;
335
+ return this.normalizeAgentName(event.agent_id);
260
336
  }
261
337
  if (data.agent_id && !['main', 'unknown'].includes(data.agent_id)) {
262
- return data.agent_id;
338
+ return this.normalizeAgentName(data.agent_id);
263
339
  }
264
340
 
265
341
  // 5. Other fallbacks
266
342
  if (event.agent && event.agent !== 'unknown') {
267
- return event.agent;
343
+ return this.normalizeAgentName(event.agent);
268
344
  }
269
345
 
270
346
  if (event.name && event.name !== 'unknown') {
271
- return event.name;
347
+ return this.normalizeAgentName(event.name);
272
348
  }
273
349
 
274
350
  // Default fallback
@@ -324,9 +324,10 @@ class EventProcessor {
324
324
 
325
325
  return filteredInstances.map((instance, index) => {
326
326
  const agentName = instance.agentName;
327
- const timestamp = this.formatTimestamp(instance.timestamp);
327
+ const timestamp = this.formatTimestamp(instance.firstTimestamp || instance.timestamp);
328
328
  const delegationType = instance.isImplied ? 'implied' : 'explicit';
329
- const eventCount = instance.eventCount || 0;
329
+ // Fix: Use totalEventCount which is the actual property name from getUniqueAgentInstances()
330
+ const eventCount = instance.totalEventCount || instance.eventCount || 0;
330
331
 
331
332
  const onclickString = `dashboard.selectCard('agents', ${index}, 'agent_instance', '${instance.id}'); dashboard.showAgentInstanceDetails('${instance.id}');`;
332
333