claude-mpm 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
1
+ """Core mixins for Claude MPM.
2
+
3
+ This module provides reusable mixins that add common functionality
4
+ to classes throughout the framework.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+
11
+ class LoggerMixin:
12
+ """Mixin that provides automatic logger initialization.
13
+
14
+ This mixin eliminates duplicate logger initialization code by automatically
15
+ creating a logger instance based on the class name or a custom name.
16
+
17
+ Usage:
18
+ class MyService(LoggerMixin):
19
+ def __init__(self):
20
+ # Logger is automatically available as self.logger
21
+ self.logger.info("Service initialized")
22
+
23
+ class CustomLogger(LoggerMixin):
24
+ def __init__(self):
25
+ # Use custom logger name
26
+ self._logger_name = "my.custom.logger"
27
+ self.logger.info("Using custom logger")
28
+
29
+ The logger is lazily initialized on first access, using either:
30
+ 1. A custom name set via self._logger_name
31
+ 2. The module and class name (e.g., 'claude_mpm.services.MyService')
32
+ 3. The module name if class name is not available
33
+
34
+ Attributes:
35
+ logger: The logging.Logger instance for this class
36
+ _logger_name: Optional custom logger name to use instead of class name
37
+ _logger_instance: Cached logger instance (internal use)
38
+ """
39
+
40
+ _logger_instance: Optional[logging.Logger] = None
41
+ _logger_name: Optional[str] = None
42
+
43
+ @property
44
+ def logger(self) -> logging.Logger:
45
+ """Get or create the logger instance.
46
+
47
+ Returns:
48
+ logging.Logger: The logger instance for this class
49
+
50
+ Examples:
51
+ >>> class MyService(LoggerMixin):
52
+ ... def process(self):
53
+ ... self.logger.info("Processing...")
54
+ >>>
55
+ >>> service = MyService()
56
+ >>> service.process() # Logs with 'claude_mpm.module.MyService'
57
+
58
+ >>> class CustomService(LoggerMixin):
59
+ ... def __init__(self):
60
+ ... self._logger_name = "custom.service"
61
+ ... self.logger.debug("Initialized")
62
+ >>>
63
+ >>> custom = CustomService() # Logs with 'custom.service'
64
+ """
65
+ if self._logger_instance is None:
66
+ if self._logger_name:
67
+ # Use custom logger name if provided
68
+ logger_name = self._logger_name
69
+ else:
70
+ # Build logger name from module and class
71
+ module = self.__class__.__module__
72
+ class_name = self.__class__.__name__
73
+
74
+ if module and module != "__main__":
75
+ logger_name = f"{module}.{class_name}"
76
+ else:
77
+ # Fallback to just class name if no proper module
78
+ logger_name = class_name
79
+
80
+ self._logger_instance = logging.getLogger(logger_name)
81
+
82
+ return self._logger_instance
83
+
84
+ def set_logger(self, logger: Optional[logging.Logger] = None,
85
+ name: Optional[str] = None) -> None:
86
+ """Set a custom logger instance or name.
87
+
88
+ This method allows overriding the automatic logger creation with either
89
+ a pre-configured logger instance or a custom logger name.
90
+
91
+ Args:
92
+ logger: A pre-configured Logger instance to use
93
+ name: A custom name for creating a new logger
94
+
95
+ Raises:
96
+ ValueError: If both logger and name are provided
97
+
98
+ Examples:
99
+ >>> service = MyService()
100
+ >>> service.set_logger(name="my.custom.logger")
101
+ >>> service.logger.info("Using custom logger")
102
+
103
+ >>> import logging
104
+ >>> custom_logger = logging.getLogger("preconfigured")
105
+ >>> service.set_logger(logger=custom_logger)
106
+ """
107
+ if logger and name:
108
+ raise ValueError("Cannot specify both logger instance and name")
109
+
110
+ if logger:
111
+ self._logger_instance = logger
112
+ self._logger_name = logger.name
113
+ elif name:
114
+ self._logger_name = name
115
+ self._logger_instance = None # Reset to force recreation
116
+ else:
117
+ # Reset to defaults
118
+ self._logger_name = None
119
+ self._logger_instance = None
120
+
121
+
122
+ # Example usage patterns for reference
123
+ if __name__ == "__main__":
124
+ # Example 1: Basic usage with automatic logger naming
125
+ class ExampleService(LoggerMixin):
126
+ def do_work(self):
127
+ self.logger.info("Doing work...")
128
+ self.logger.debug("Debug details")
129
+
130
+ # Example 2: Custom logger name
131
+ class CustomService(LoggerMixin):
132
+ def __init__(self):
133
+ self._logger_name = "custom.service"
134
+ self.logger.info("Service initialized with custom logger")
135
+
136
+ # Example 3: Setting logger after initialization
137
+ class ConfigurableService(LoggerMixin):
138
+ def configure(self, logger_name: str):
139
+ self.set_logger(name=logger_name)
140
+ self.logger.info(f"Reconfigured with logger: {logger_name}")
141
+
142
+ # Example 4: Multiple inheritance friendly
143
+ class BaseService:
144
+ def __init__(self, config=None):
145
+ self.config = config
146
+
147
+ class AdvancedService(BaseService, LoggerMixin):
148
+ def __init__(self, config=None):
149
+ super().__init__(config)
150
+ self.logger.info("Advanced service ready")
@@ -0,0 +1,299 @@
1
+ """
2
+ Service Registry for Claude MPM.
3
+
4
+ Provides centralized service registration and discovery, working with
5
+ the DI container to manage application services.
6
+ """
7
+
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional, Type, Union
11
+
12
+ from .container import DIContainer, ServiceLifetime, get_container
13
+ from .base_service import BaseService
14
+ from .logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class ServiceRegistry:
20
+ """
21
+ Central registry for all application services.
22
+
23
+ Manages service registration, configuration, and lifecycle.
24
+ """
25
+
26
+ def __init__(self, container: Optional[DIContainer] = None):
27
+ """
28
+ Initialize service registry.
29
+
30
+ Args:
31
+ container: DI container to use (uses global if not provided)
32
+ """
33
+ self.container = container or get_container()
34
+ self._services: Dict[str, Type[BaseService]] = {}
35
+ self._initialized = False
36
+
37
+ def register_core_services(self) -> None:
38
+ """Register all core framework services."""
39
+ from ..services.shared_prompt_cache import SharedPromptCache
40
+ from ..services.json_rpc_hook_manager import JSONRPCHookManager
41
+ from ..services.ticket_manager import TicketManager
42
+ from ..services.agent_deployment import AgentDeploymentService
43
+ from .session_manager import SessionManager
44
+ from .agent_session_manager import AgentSessionManager
45
+ from .config import Config
46
+
47
+ # Register configuration as singleton
48
+ config = Config()
49
+ self.container.register_singleton(Config, instance=config)
50
+
51
+ # Register core services
52
+ self.register_service(
53
+ "session_manager",
54
+ SessionManager,
55
+ lifetime=ServiceLifetime.SINGLETON
56
+ )
57
+
58
+ self.register_service(
59
+ "agent_session_manager",
60
+ AgentSessionManager,
61
+ lifetime=ServiceLifetime.SINGLETON,
62
+ dependencies={
63
+ 'session_dir': lambda c: c.resolve(Config).get('session_dir')
64
+ }
65
+ )
66
+
67
+ # Register shared cache as singleton (it's already a singleton internally)
68
+ self.register_service(
69
+ "prompt_cache",
70
+ SharedPromptCache,
71
+ lifetime=ServiceLifetime.SINGLETON,
72
+ factory=lambda c: SharedPromptCache.get_instance()
73
+ )
74
+
75
+ # Register hook manager
76
+ self.register_service(
77
+ "hook_manager",
78
+ JSONRPCHookManager,
79
+ lifetime=ServiceLifetime.SINGLETON
80
+ )
81
+
82
+ # Register ticket manager
83
+ self.register_service(
84
+ "ticket_manager",
85
+ TicketManager,
86
+ lifetime=ServiceLifetime.TRANSIENT
87
+ )
88
+
89
+ # Register agent deployment service
90
+ self.register_service(
91
+ "agent_deployment",
92
+ AgentDeploymentService,
93
+ lifetime=ServiceLifetime.TRANSIENT
94
+ )
95
+
96
+ logger.info("Core services registered")
97
+
98
+ def register_service(
99
+ self,
100
+ name: str,
101
+ service_class: Type[BaseService],
102
+ lifetime: ServiceLifetime = ServiceLifetime.SINGLETON,
103
+ factory: Optional[Any] = None,
104
+ dependencies: Optional[Dict[str, Any]] = None,
105
+ config: Optional[Dict[str, Any]] = None
106
+ ) -> None:
107
+ """
108
+ Register a service with the registry.
109
+
110
+ Args:
111
+ name: Service name for lookup
112
+ service_class: Service class (must inherit from BaseService)
113
+ lifetime: Service lifetime management
114
+ factory: Optional factory function
115
+ dependencies: Optional dependency mapping
116
+ config: Optional service-specific configuration
117
+ """
118
+ # Store service metadata
119
+ self._services[name] = service_class
120
+
121
+ # Create factory wrapper if config provided
122
+ if config and not factory:
123
+ factory = lambda c: service_class(name=name, config=config, container=c)
124
+ elif not factory:
125
+ # Default factory with container injection
126
+ factory = lambda c: service_class(name=name, container=c)
127
+
128
+ # Register with DI container
129
+ self.container.register(
130
+ service_class,
131
+ factory=factory,
132
+ lifetime=lifetime,
133
+ dependencies=dependencies
134
+ )
135
+
136
+ logger.debug(f"Registered service: {name} ({service_class.__name__})")
137
+
138
+ def get_service(self, service_type: Union[str, Type[BaseService]]) -> BaseService:
139
+ """
140
+ Get a service instance.
141
+
142
+ Args:
143
+ service_type: Service name or class
144
+
145
+ Returns:
146
+ Service instance
147
+ """
148
+ if isinstance(service_type, str):
149
+ # Look up by name
150
+ if service_type not in self._services:
151
+ raise KeyError(f"Service '{service_type}' not registered")
152
+ service_class = self._services[service_type]
153
+ else:
154
+ service_class = service_type
155
+
156
+ return self.container.resolve(service_class)
157
+
158
+ def get_service_optional(
159
+ self,
160
+ service_type: Union[str, Type[BaseService]],
161
+ default: Optional[BaseService] = None
162
+ ) -> Optional[BaseService]:
163
+ """Get a service if available, otherwise return default."""
164
+ try:
165
+ return self.get_service(service_type)
166
+ except (KeyError, Exception):
167
+ return default
168
+
169
+ def start_all_services(self) -> None:
170
+ """Start all registered singleton services."""
171
+ import asyncio
172
+
173
+ async def _start_all():
174
+ for name, service_class in self._services.items():
175
+ try:
176
+ # Only start singleton services
177
+ registration = self.container.get_all_registrations().get(service_class)
178
+ if registration and registration.lifetime == ServiceLifetime.SINGLETON:
179
+ service = self.get_service(service_class)
180
+ if hasattr(service, 'start'):
181
+ await service.start()
182
+ logger.info(f"Started service: {name}")
183
+ except Exception as e:
184
+ logger.error(f"Failed to start service {name}: {e}")
185
+
186
+ asyncio.run(_start_all())
187
+ self._initialized = True
188
+
189
+ def stop_all_services(self) -> None:
190
+ """Stop all running singleton services."""
191
+ import asyncio
192
+
193
+ async def _stop_all():
194
+ for name, service_class in reversed(list(self._services.items())):
195
+ try:
196
+ # Only stop singleton services
197
+ registration = self.container.get_all_registrations().get(service_class)
198
+ if registration and registration.lifetime == ServiceLifetime.SINGLETON:
199
+ if service_class in self.container._singletons:
200
+ service = self.container._singletons[service_class]
201
+ if hasattr(service, 'stop') and service.running:
202
+ await service.stop()
203
+ logger.info(f"Stopped service: {name}")
204
+ except Exception as e:
205
+ logger.error(f"Failed to stop service {name}: {e}")
206
+
207
+ asyncio.run(_stop_all())
208
+
209
+ def get_service_health(self) -> Dict[str, Dict[str, Any]]:
210
+ """Get health status of all services."""
211
+ import asyncio
212
+
213
+ async def _get_health():
214
+ health_status = {}
215
+
216
+ for name, service_class in self._services.items():
217
+ try:
218
+ if service_class in self.container._singletons:
219
+ service = self.container._singletons[service_class]
220
+ if hasattr(service, 'health_check'):
221
+ health = await service.health_check()
222
+ health_status[name] = {
223
+ 'status': health.status,
224
+ 'message': health.message,
225
+ 'metrics': health.metrics
226
+ }
227
+ except Exception as e:
228
+ health_status[name] = {
229
+ 'status': 'error',
230
+ 'message': str(e)
231
+ }
232
+
233
+ return health_status
234
+
235
+ return asyncio.run(_get_health())
236
+
237
+ def list_services(self) -> List[Dict[str, Any]]:
238
+ """List all registered services with their metadata."""
239
+ services = []
240
+
241
+ for name, service_class in self._services.items():
242
+ registration = self.container.get_all_registrations().get(service_class)
243
+
244
+ service_info = {
245
+ 'name': name,
246
+ 'class': service_class.__name__,
247
+ 'module': service_class.__module__,
248
+ 'lifetime': registration.lifetime.value if registration else 'unknown',
249
+ 'is_singleton': registration.lifetime == ServiceLifetime.SINGLETON if registration else False,
250
+ 'is_running': False
251
+ }
252
+
253
+ # Check if singleton is running
254
+ if service_class in self.container._singletons:
255
+ service = self.container._singletons[service_class]
256
+ if hasattr(service, 'running'):
257
+ service_info['is_running'] = service.running
258
+
259
+ services.append(service_info)
260
+
261
+ return services
262
+
263
+
264
+ # Global registry instance
265
+ _global_registry: Optional[ServiceRegistry] = None
266
+
267
+
268
+ def get_service_registry() -> ServiceRegistry:
269
+ """Get the global service registry instance."""
270
+ global _global_registry
271
+ if _global_registry is None:
272
+ _global_registry = ServiceRegistry()
273
+ _global_registry.register_core_services()
274
+ return _global_registry
275
+
276
+
277
+ def initialize_services(config: Optional[Dict[str, Any]] = None) -> ServiceRegistry:
278
+ """
279
+ Initialize all application services.
280
+
281
+ Args:
282
+ config: Optional configuration overrides
283
+
284
+ Returns:
285
+ Initialized service registry
286
+ """
287
+ registry = get_service_registry()
288
+
289
+ if config:
290
+ # Apply configuration overrides
291
+ config_service = registry.container.resolve(Config)
292
+ for key, value in config.items():
293
+ config_service.set(key, value)
294
+
295
+ # Start all services
296
+ if not registry._initialized:
297
+ registry.start_all_services()
298
+
299
+ return registry
@@ -0,0 +1,190 @@
1
+ """Session ID management for Claude subprocess optimization."""
2
+
3
+ import uuid
4
+ from typing import Optional, Dict, Any
5
+ from datetime import datetime, timedelta
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from ..core.logger import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class SessionManager:
15
+ """Manages session IDs for Claude subprocess reuse."""
16
+
17
+ def __init__(self, session_dir: Optional[Path] = None):
18
+ """Initialize session manager.
19
+
20
+ Args:
21
+ session_dir: Directory to store session metadata
22
+ """
23
+ self.session_dir = session_dir or Path.home() / ".claude-mpm" / "sessions"
24
+ self.session_dir.mkdir(parents=True, exist_ok=True)
25
+ self.active_sessions: Dict[str, Dict[str, Any]] = {}
26
+ self._load_sessions()
27
+
28
+ def create_session(self, context: str = "default") -> str:
29
+ """Create a new session ID.
30
+
31
+ Args:
32
+ context: Context identifier (e.g., "pm_orchestration", "agent_delegation")
33
+
34
+ Returns:
35
+ UUID session ID
36
+ """
37
+ session_id = str(uuid.uuid4())
38
+
39
+ self.active_sessions[session_id] = {
40
+ "id": session_id,
41
+ "context": context,
42
+ "created_at": datetime.now().isoformat(),
43
+ "last_used": datetime.now().isoformat(),
44
+ "use_count": 0,
45
+ "agents_run": []
46
+ }
47
+
48
+ self._save_sessions()
49
+ logger.info(f"Created session {session_id} for context: {context}")
50
+
51
+ return session_id
52
+
53
+ def get_or_create_session(self, context: str = "default", max_age_minutes: int = 30) -> str:
54
+ """Get existing session or create new one.
55
+
56
+ Args:
57
+ context: Context identifier
58
+ max_age_minutes: Maximum age of session to reuse
59
+
60
+ Returns:
61
+ Session ID
62
+ """
63
+ # Look for existing session in context
64
+ now = datetime.now()
65
+ max_age = timedelta(minutes=max_age_minutes)
66
+
67
+ for session_id, session_data in self.active_sessions.items():
68
+ if session_data["context"] == context:
69
+ last_used = datetime.fromisoformat(session_data["last_used"])
70
+ if now - last_used < max_age:
71
+ # Reuse this session
72
+ session_data["last_used"] = now.isoformat()
73
+ session_data["use_count"] += 1
74
+ self._save_sessions()
75
+ logger.info(f"Reusing session {session_id} for context: {context}")
76
+ return session_id
77
+
78
+ # No valid session found, create new one
79
+ return self.create_session(context)
80
+
81
+ def record_agent_use(self, session_id: str, agent: str, task: str):
82
+ """Record that an agent used this session.
83
+
84
+ Args:
85
+ session_id: Session ID
86
+ agent: Agent name
87
+ task: Task description
88
+ """
89
+ if session_id in self.active_sessions:
90
+ self.active_sessions[session_id]["agents_run"].append({
91
+ "agent": agent,
92
+ "task": task[:100], # Truncate long tasks
93
+ "timestamp": datetime.now().isoformat()
94
+ })
95
+ self.active_sessions[session_id]["last_used"] = datetime.now().isoformat()
96
+ self._save_sessions()
97
+
98
+ def cleanup_old_sessions(self, max_age_hours: int = 24):
99
+ """Remove sessions older than max_age_hours.
100
+
101
+ Args:
102
+ max_age_hours: Maximum age in hours
103
+ """
104
+ now = datetime.now()
105
+ max_age = timedelta(hours=max_age_hours)
106
+
107
+ expired = []
108
+ for session_id, session_data in self.active_sessions.items():
109
+ created = datetime.fromisoformat(session_data["created_at"])
110
+ if now - created > max_age:
111
+ expired.append(session_id)
112
+
113
+ for session_id in expired:
114
+ del self.active_sessions[session_id]
115
+ logger.info(f"Cleaned up expired session: {session_id}")
116
+
117
+ if expired:
118
+ self._save_sessions()
119
+
120
+ def _save_sessions(self):
121
+ """Save sessions to disk."""
122
+ session_file = self.session_dir / "active_sessions.json"
123
+ try:
124
+ with open(session_file, 'w') as f:
125
+ json.dump(self.active_sessions, f, indent=2)
126
+ except Exception as e:
127
+ logger.error(f"Failed to save sessions: {e}")
128
+
129
+ def _load_sessions(self):
130
+ """Load sessions from disk."""
131
+ session_file = self.session_dir / "active_sessions.json"
132
+ if session_file.exists():
133
+ try:
134
+ with open(session_file, 'r') as f:
135
+ self.active_sessions = json.load(f)
136
+
137
+ # Clean up old sessions on load
138
+ self.cleanup_old_sessions()
139
+ except Exception as e:
140
+ logger.error(f"Failed to load sessions: {e}")
141
+ self.active_sessions = {}
142
+
143
+
144
+ class OrchestrationSession:
145
+ """Context manager for orchestration sessions."""
146
+
147
+ def __init__(self, session_manager: SessionManager, context: str = "orchestration"):
148
+ """Initialize orchestration session.
149
+
150
+ Args:
151
+ session_manager: SessionManager instance
152
+ context: Session context
153
+ """
154
+ self.session_manager = session_manager
155
+ self.context = context
156
+ self.session_id: Optional[str] = None
157
+
158
+ def __enter__(self) -> str:
159
+ """Enter session context, return session ID."""
160
+ self.session_id = self.session_manager.get_or_create_session(self.context)
161
+ return self.session_id
162
+
163
+ def __exit__(self, exc_type, exc_val, exc_tb):
164
+ """Exit session context."""
165
+ # Could add cleanup here if needed
166
+ pass
167
+
168
+
169
+ # Example usage in subprocess orchestrator:
170
+ """
171
+ session_manager = SessionManager()
172
+
173
+ # In run_non_interactive:
174
+ with OrchestrationSession(session_manager, "pm_delegation") as session_id:
175
+ # Run PM with session
176
+ stdout, stderr, returncode = self.launcher.launch_oneshot(
177
+ message=pm_prompt,
178
+ session_id=session_id, # First call creates context
179
+ timeout=30
180
+ )
181
+
182
+ # Run agents with same session (reuses context)
183
+ for agent, task in delegations:
184
+ stdout, stderr, returncode = self.launcher.launch_oneshot(
185
+ message=agent_prompt,
186
+ session_id=session_id, # Reuses PM context!
187
+ timeout=60
188
+ )
189
+ session_manager.record_agent_use(session_id, agent, task)
190
+ """