claude-mpm 5.6.10__py3-none-any.whl → 5.6.33__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 (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/commander.py +174 -4
  3. claude_mpm/cli/parsers/commander_parser.py +43 -10
  4. claude_mpm/cli/startup.py +140 -20
  5. claude_mpm/cli/startup_display.py +2 -1
  6. claude_mpm/commander/__init__.py +6 -0
  7. claude_mpm/commander/adapters/__init__.py +32 -3
  8. claude_mpm/commander/adapters/auggie.py +260 -0
  9. claude_mpm/commander/adapters/base.py +98 -1
  10. claude_mpm/commander/adapters/claude_code.py +32 -1
  11. claude_mpm/commander/adapters/codex.py +237 -0
  12. claude_mpm/commander/adapters/example_usage.py +310 -0
  13. claude_mpm/commander/adapters/mpm.py +389 -0
  14. claude_mpm/commander/adapters/registry.py +204 -0
  15. claude_mpm/commander/api/app.py +32 -16
  16. claude_mpm/commander/api/routes/messages.py +11 -11
  17. claude_mpm/commander/api/routes/projects.py +20 -20
  18. claude_mpm/commander/api/routes/sessions.py +19 -21
  19. claude_mpm/commander/api/routes/work.py +86 -50
  20. claude_mpm/commander/api/schemas.py +4 -0
  21. claude_mpm/commander/chat/cli.py +42 -3
  22. claude_mpm/commander/config.py +5 -3
  23. claude_mpm/commander/core/__init__.py +10 -0
  24. claude_mpm/commander/core/block_manager.py +325 -0
  25. claude_mpm/commander/core/response_manager.py +323 -0
  26. claude_mpm/commander/daemon.py +215 -10
  27. claude_mpm/commander/env_loader.py +59 -0
  28. claude_mpm/commander/frameworks/base.py +4 -1
  29. claude_mpm/commander/instance_manager.py +124 -11
  30. claude_mpm/commander/memory/__init__.py +45 -0
  31. claude_mpm/commander/memory/compression.py +347 -0
  32. claude_mpm/commander/memory/embeddings.py +230 -0
  33. claude_mpm/commander/memory/entities.py +310 -0
  34. claude_mpm/commander/memory/example_usage.py +290 -0
  35. claude_mpm/commander/memory/integration.py +325 -0
  36. claude_mpm/commander/memory/search.py +381 -0
  37. claude_mpm/commander/memory/store.py +657 -0
  38. claude_mpm/commander/registry.py +10 -4
  39. claude_mpm/commander/runtime/monitor.py +32 -2
  40. claude_mpm/commander/work/executor.py +38 -20
  41. claude_mpm/commander/workflow/event_handler.py +25 -3
  42. claude_mpm/core/claude_runner.py +152 -0
  43. claude_mpm/core/config.py +3 -3
  44. claude_mpm/core/config_constants.py +74 -9
  45. claude_mpm/core/constants.py +56 -12
  46. claude_mpm/core/interactive_session.py +5 -4
  47. claude_mpm/core/logging_utils.py +4 -2
  48. claude_mpm/core/network_config.py +148 -0
  49. claude_mpm/core/oneshot_session.py +7 -6
  50. claude_mpm/core/output_style_manager.py +37 -7
  51. claude_mpm/core/socketio_pool.py +13 -5
  52. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  53. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  54. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
  58. claude_mpm/hooks/claude_hooks/event_handlers.py +284 -89
  59. claude_mpm/hooks/claude_hooks/hook_handler.py +81 -32
  60. claude_mpm/hooks/claude_hooks/installer.py +90 -28
  61. claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
  62. claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
  63. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  64. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  65. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
  71. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
  72. claude_mpm/hooks/claude_hooks/services/container.py +310 -0
  73. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  74. claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
  75. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
  76. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  77. claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
  78. claude_mpm/scripts/claude-hook-handler.sh +3 -3
  79. claude_mpm/services/command_deployment_service.py +44 -26
  80. claude_mpm/services/hook_installer_service.py +77 -8
  81. claude_mpm/services/pm_skills_deployer.py +3 -2
  82. claude_mpm/skills/__init__.py +2 -1
  83. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  84. claude_mpm/skills/registry.py +295 -90
  85. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/METADATA +5 -3
  86. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/RECORD +91 -94
  87. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  88. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  89. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  90. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  91. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  92. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  97. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  98. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  99. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  100. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  103. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  104. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  105. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  106. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  107. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  108. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  109. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  110. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  111. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  112. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  113. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/WHEEL +0 -0
  114. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/entry_points.txt +0 -0
  115. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE +0 -0
  116. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  117. {claude_mpm-5.6.10.dist-info → claude_mpm-5.6.33.dist-info}/top_level.txt +0 -0
@@ -149,7 +149,7 @@ main() {
149
149
  if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
150
150
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Claude MPM not found, continuing..." >> /tmp/claude-mpm-hook.log
151
151
  fi
152
- echo '{"action": "continue"}'
152
+ echo '{"continue": true}'
153
153
  exit 0
154
154
  fi
155
155
 
@@ -176,7 +176,7 @@ main() {
176
176
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
177
177
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
178
178
  fi
179
- echo '{"action": "continue"}'
179
+ echo '{"continue": true}'
180
180
  exit 0
181
181
  fi
182
182
 
@@ -203,10 +203,14 @@ main "$@"
203
203
  import logging
204
204
 
205
205
  self.logger = logging.getLogger(__name__)
206
- self.claude_dir = Path.home() / ".claude"
206
+ # Use project-level paths, NEVER global ~/.claude/settings.json
207
+ # This ensures hooks are scoped to the current project only
208
+ self.project_root = Path.cwd()
209
+ self.claude_dir = self.project_root / ".claude"
207
210
  self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
208
- # Use settings.json for hooks (Claude Code reads from this file)
209
- self.settings_file = self.claude_dir / "settings.json"
211
+ # Use settings.local.json for project-level hook settings
212
+ # Claude Code reads project-level settings from .claude/settings.local.json
213
+ self.settings_file = self.claude_dir / "settings.local.json"
210
214
  # There is no legacy settings file - this was a bug where both pointed to same file
211
215
  # Setting to None to disable cleanup that was deleting freshly installed hooks
212
216
  self.old_settings_file = None
@@ -601,40 +605,98 @@ main "$@"
601
605
  # Hook configuration for each event type
602
606
  hook_command = {"type": "command", "command": str(hook_script_path.absolute())}
603
607
 
608
+ def is_our_hook(cmd: dict) -> bool:
609
+ """Check if a hook command belongs to claude-mpm."""
610
+ if cmd.get("type") != "command":
611
+ return False
612
+ command = cmd.get("command", "")
613
+ return "claude-hook-handler.sh" in command or command.endswith(
614
+ "claude-mpm-hook.sh"
615
+ )
616
+
617
+ def merge_hooks_for_event(
618
+ existing_hooks: list, new_hook_command: dict, use_matcher: bool = True
619
+ ) -> list:
620
+ """Merge new hook command into existing hooks without duplication.
621
+
622
+ Args:
623
+ existing_hooks: Current hooks configuration for an event type
624
+ new_hook_command: The claude-mpm hook command to add
625
+ use_matcher: Whether to include matcher: "*" in the config
626
+
627
+ Returns:
628
+ Updated hooks list with our hook merged in
629
+ """
630
+ # Check if our hook already exists in any existing hook config
631
+ our_hook_exists = False
632
+
633
+ for hook_config in existing_hooks:
634
+ if "hooks" in hook_config and isinstance(hook_config["hooks"], list):
635
+ for hook in hook_config["hooks"]:
636
+ if is_our_hook(hook):
637
+ # Update existing hook command path (in case it changed)
638
+ hook["command"] = new_hook_command["command"]
639
+ our_hook_exists = True
640
+ break
641
+ if our_hook_exists:
642
+ break
643
+
644
+ if our_hook_exists:
645
+ # Our hook already exists, just return the updated list
646
+ return existing_hooks
647
+
648
+ # Our hook doesn't exist - need to add it
649
+ # Strategy: Add our hook to the first "*" matcher config, or create new config
650
+ added = False
651
+
652
+ for hook_config in existing_hooks:
653
+ # Check if this config has matcher: "*" (or no matcher for simple events)
654
+ matcher = hook_config.get("matcher")
655
+ if matcher == "*" or (not use_matcher and matcher is None):
656
+ # Add our hook to this config's hooks array
657
+ if "hooks" not in hook_config:
658
+ hook_config["hooks"] = []
659
+ hook_config["hooks"].append(new_hook_command)
660
+ added = True
661
+ break
662
+
663
+ if not added:
664
+ # No suitable config found, create a new one
665
+ if use_matcher:
666
+ new_config = {"matcher": "*", "hooks": [new_hook_command]}
667
+ else:
668
+ new_config = {"hooks": [new_hook_command]}
669
+ existing_hooks.append(new_config)
670
+
671
+ return existing_hooks
672
+
604
673
  # Tool-related events need a matcher string
605
674
  tool_events = ["PreToolUse", "PostToolUse"]
606
675
  for event_type in tool_events:
607
- settings["hooks"][event_type] = [
608
- {
609
- "matcher": "*", # String value to match all tools
610
- "hooks": [hook_command],
611
- }
612
- ]
676
+ existing = settings["hooks"].get(event_type, [])
677
+ settings["hooks"][event_type] = merge_hooks_for_event(
678
+ existing, hook_command, use_matcher=True
679
+ )
613
680
 
614
681
  # Simple events (no subtypes, no matcher needed)
615
682
  simple_events = ["Stop", "SubagentStop", "SubagentStart"]
616
683
  for event_type in simple_events:
617
- settings["hooks"][event_type] = [
618
- {
619
- "hooks": [hook_command],
620
- }
621
- ]
684
+ existing = settings["hooks"].get(event_type, [])
685
+ settings["hooks"][event_type] = merge_hooks_for_event(
686
+ existing, hook_command, use_matcher=False
687
+ )
622
688
 
623
689
  # SessionStart needs matcher for subtypes (startup, resume)
624
- settings["hooks"]["SessionStart"] = [
625
- {
626
- "matcher": "*", # Match all SessionStart subtypes
627
- "hooks": [hook_command],
628
- }
629
- ]
690
+ existing = settings["hooks"].get("SessionStart", [])
691
+ settings["hooks"]["SessionStart"] = merge_hooks_for_event(
692
+ existing, hook_command, use_matcher=True
693
+ )
630
694
 
631
695
  # UserPromptSubmit needs matcher for potential subtypes
632
- settings["hooks"]["UserPromptSubmit"] = [
633
- {
634
- "matcher": "*",
635
- "hooks": [hook_command],
636
- }
637
- ]
696
+ existing = settings["hooks"].get("UserPromptSubmit", [])
697
+ settings["hooks"]["UserPromptSubmit"] = merge_hooks_for_event(
698
+ existing, hook_command, use_matcher=True
699
+ )
638
700
 
639
701
  # Fix statusLine command to handle both output style schemas
640
702
  self._fix_status_line(settings)
@@ -67,7 +67,7 @@ from datetime import datetime, timezone
67
67
  from typing import Optional
68
68
 
69
69
  # Debug mode
70
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
70
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
71
71
 
72
72
  # Memory hooks integration
73
73
  MEMORY_HOOKS_AVAILABLE = False
@@ -22,7 +22,7 @@ except ImportError:
22
22
 
23
23
 
24
24
  # Debug mode
25
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
25
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
26
26
 
27
27
  # Response tracking integration
28
28
  # NOTE: ResponseTracker import moved to _initialize_response_tracking() for lazy loading
@@ -3,13 +3,34 @@
3
3
  # Use HTTP-based connection manager for stable dashboard communication
4
4
  # from .connection_manager import ConnectionManagerService # Old SocketIO-based
5
5
  from .connection_manager_http import ConnectionManagerService # New HTTP-based
6
+ from .container import HookServiceContainer, get_container
6
7
  from .duplicate_detector import DuplicateEventDetector
8
+ from .protocols import (
9
+ IAutoPauseHandler,
10
+ IConnectionManager,
11
+ IDuplicateDetector,
12
+ IEventHandlers,
13
+ IMemoryHookManager,
14
+ IResponseTrackingManager,
15
+ IStateManager,
16
+ ISubagentProcessor,
17
+ )
7
18
  from .state_manager import StateManagerService
8
19
  from .subagent_processor import SubagentResponseProcessor
9
20
 
10
21
  __all__ = [
11
22
  "ConnectionManagerService",
12
23
  "DuplicateEventDetector",
24
+ "HookServiceContainer",
25
+ "IAutoPauseHandler",
26
+ "IConnectionManager",
27
+ "IDuplicateDetector",
28
+ "IEventHandlers",
29
+ "IMemoryHookManager",
30
+ "IResponseTrackingManager",
31
+ "IStateManager",
32
+ "ISubagentProcessor",
13
33
  "StateManagerService",
14
34
  "SubagentResponseProcessor",
35
+ "get_container",
15
36
  ]
@@ -29,8 +29,8 @@ except ImportError:
29
29
  pass # Silent fallback
30
30
 
31
31
 
32
- # Debug mode is enabled by default for better visibility into hook processing
33
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
32
+ # Debug mode - disabled by default to prevent logging overhead in production
33
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
34
34
 
35
35
  # Import extracted modules with fallback for direct execution
36
36
  try:
@@ -28,8 +28,8 @@ except ImportError:
28
28
  pass # Silent fallback
29
29
 
30
30
 
31
- # Debug mode is enabled by default for better visibility into hook processing
32
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
31
+ # Debug mode - disabled by default to prevent logging overhead in production
32
+ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
33
33
 
34
34
  # Import requests for HTTP POST communication
35
35
  try:
@@ -0,0 +1,310 @@
1
+ """Dependency Injection container for hook handler services.
2
+
3
+ This module provides a DI container that manages service instantiation,
4
+ lazy initialization, and service overriding for testing.
5
+
6
+ WHY DI Container:
7
+ - Centralized service management
8
+ - Lazy initialization reduces startup overhead
9
+ - Easy testing through service overriding
10
+ - Clear dependency graph
11
+ - Thread-safe singleton pattern
12
+ """
13
+
14
+ import threading
15
+ from typing import Any, Optional, TypeVar
16
+
17
+ # Import service protocols for type hints
18
+ from .protocols import (
19
+ IAutoPauseHandler,
20
+ IConnectionManager,
21
+ IDuplicateDetector,
22
+ IEventHandlers,
23
+ IMemoryHookManager,
24
+ IResponseTrackingManager,
25
+ IStateManager,
26
+ ISubagentProcessor,
27
+ )
28
+
29
+ T = TypeVar("T")
30
+
31
+
32
+ class HookServiceContainer:
33
+ """Dependency injection container for hook handler services.
34
+
35
+ Features:
36
+ - Lazy initialization of services
37
+ - Thread-safe singleton pattern
38
+ - Service overriding for testing
39
+ - Automatic dependency resolution
40
+
41
+ Usage:
42
+ # Normal usage (services created lazily)
43
+ container = HookServiceContainer()
44
+ state_manager = container.get_state_manager()
45
+
46
+ # Testing usage (override services)
47
+ container = HookServiceContainer()
48
+ container.override_state_manager(mock_state_manager)
49
+ state_manager = container.get_state_manager() # Returns mock
50
+
51
+ # Reset overrides
52
+ container.reset_overrides()
53
+ """
54
+
55
+ # Thread-safe singleton
56
+ _instance: Optional["HookServiceContainer"] = None
57
+ _lock = threading.Lock()
58
+
59
+ def __new__(cls) -> "HookServiceContainer":
60
+ """Thread-safe singleton pattern."""
61
+ if cls._instance is None:
62
+ with cls._lock:
63
+ # Double-check locking pattern
64
+ if cls._instance is None:
65
+ cls._instance = super().__new__(cls)
66
+ cls._instance._initialized = False
67
+ return cls._instance
68
+
69
+ def __init__(self) -> None:
70
+ """Initialize container if not already initialized."""
71
+ if getattr(self, "_initialized", False):
72
+ return
73
+
74
+ # Service instances (lazily initialized)
75
+ self._state_manager: Optional[IStateManager] = None
76
+ self._connection_manager: Optional[IConnectionManager] = None
77
+ self._duplicate_detector: Optional[IDuplicateDetector] = None
78
+ self._response_tracking_manager: Optional[IResponseTrackingManager] = None
79
+ self._memory_hook_manager: Optional[IMemoryHookManager] = None
80
+ self._subagent_processor: Optional[ISubagentProcessor] = None
81
+ self._auto_pause_handler: Optional[IAutoPauseHandler] = None
82
+ self._event_handlers: Optional[IEventHandlers] = None
83
+
84
+ # Override factories for testing
85
+ self._overrides: dict[str, Any] = {}
86
+
87
+ # Lock for lazy initialization
88
+ self._init_lock = threading.Lock()
89
+
90
+ self._initialized = True
91
+
92
+ # =========================================================================
93
+ # Lazy Service Getters
94
+ # =========================================================================
95
+
96
+ def get_state_manager(self) -> IStateManager:
97
+ """Get or create StateManagerService instance."""
98
+ if "state_manager" in self._overrides:
99
+ return self._overrides["state_manager"]
100
+
101
+ if self._state_manager is None:
102
+ with self._init_lock:
103
+ if self._state_manager is None:
104
+ from .state_manager import StateManagerService
105
+
106
+ self._state_manager = StateManagerService()
107
+ return self._state_manager
108
+
109
+ def get_connection_manager(self) -> IConnectionManager:
110
+ """Get or create ConnectionManagerService instance."""
111
+ if "connection_manager" in self._overrides:
112
+ return self._overrides["connection_manager"]
113
+
114
+ if self._connection_manager is None:
115
+ with self._init_lock:
116
+ if self._connection_manager is None:
117
+ from .connection_manager_http import ConnectionManagerService
118
+
119
+ self._connection_manager = ConnectionManagerService()
120
+ return self._connection_manager
121
+
122
+ def get_duplicate_detector(self) -> IDuplicateDetector:
123
+ """Get or create DuplicateEventDetector instance."""
124
+ if "duplicate_detector" in self._overrides:
125
+ return self._overrides["duplicate_detector"]
126
+
127
+ if self._duplicate_detector is None:
128
+ with self._init_lock:
129
+ if self._duplicate_detector is None:
130
+ from .duplicate_detector import DuplicateEventDetector
131
+
132
+ self._duplicate_detector = DuplicateEventDetector()
133
+ return self._duplicate_detector
134
+
135
+ def get_response_tracking_manager(self) -> IResponseTrackingManager:
136
+ """Get or create ResponseTrackingManager instance."""
137
+ if "response_tracking_manager" in self._overrides:
138
+ return self._overrides["response_tracking_manager"]
139
+
140
+ if self._response_tracking_manager is None:
141
+ with self._init_lock:
142
+ if self._response_tracking_manager is None:
143
+ from ..response_tracking import ResponseTrackingManager
144
+
145
+ self._response_tracking_manager = ResponseTrackingManager()
146
+ return self._response_tracking_manager
147
+
148
+ def get_memory_hook_manager(self) -> IMemoryHookManager:
149
+ """Get or create MemoryHookManager instance."""
150
+ if "memory_hook_manager" in self._overrides:
151
+ return self._overrides["memory_hook_manager"]
152
+
153
+ if self._memory_hook_manager is None:
154
+ with self._init_lock:
155
+ if self._memory_hook_manager is None:
156
+ from ..memory_integration import MemoryHookManager
157
+
158
+ self._memory_hook_manager = MemoryHookManager()
159
+ return self._memory_hook_manager
160
+
161
+ def get_auto_pause_handler(self) -> Optional[IAutoPauseHandler]:
162
+ """Get or create AutoPauseHandler instance.
163
+
164
+ Returns None if initialization fails (auto-pause is optional).
165
+ """
166
+ if "auto_pause_handler" in self._overrides:
167
+ return self._overrides["auto_pause_handler"]
168
+
169
+ if self._auto_pause_handler is None:
170
+ with self._init_lock:
171
+ if self._auto_pause_handler is None:
172
+ try:
173
+ from ..auto_pause_handler import AutoPauseHandler
174
+
175
+ self._auto_pause_handler = AutoPauseHandler()
176
+ except Exception:
177
+ # Auto-pause is optional
178
+ self._auto_pause_handler = None
179
+ return self._auto_pause_handler
180
+
181
+ def get_subagent_processor(
182
+ self,
183
+ state_manager: Optional[IStateManager] = None,
184
+ response_tracking_manager: Optional[IResponseTrackingManager] = None,
185
+ connection_manager: Optional[IConnectionManager] = None,
186
+ ) -> ISubagentProcessor:
187
+ """Get or create SubagentResponseProcessor instance.
188
+
189
+ Args:
190
+ state_manager: Optional override for state manager
191
+ response_tracking_manager: Optional override for response tracking
192
+ connection_manager: Optional override for connection manager
193
+ """
194
+ if "subagent_processor" in self._overrides:
195
+ return self._overrides["subagent_processor"]
196
+
197
+ if self._subagent_processor is None:
198
+ with self._init_lock:
199
+ if self._subagent_processor is None:
200
+ from .subagent_processor import SubagentResponseProcessor
201
+
202
+ # Use provided dependencies or get from container
203
+ sm = state_manager or self.get_state_manager()
204
+ rtm = (
205
+ response_tracking_manager
206
+ or self.get_response_tracking_manager()
207
+ )
208
+ cm = connection_manager or self.get_connection_manager()
209
+
210
+ self._subagent_processor = SubagentResponseProcessor(sm, rtm, cm)
211
+ return self._subagent_processor
212
+
213
+ def get_event_handlers(self, hook_handler: Any = None) -> IEventHandlers:
214
+ """Get or create EventHandlers instance.
215
+
216
+ Args:
217
+ hook_handler: The ClaudeHookHandler instance for backward compatibility.
218
+ In the future, this will be replaced with proper DI.
219
+ """
220
+ if "event_handlers" in self._overrides:
221
+ return self._overrides["event_handlers"]
222
+
223
+ if self._event_handlers is None:
224
+ with self._init_lock:
225
+ if self._event_handlers is None:
226
+ from ..event_handlers import EventHandlers
227
+
228
+ if hook_handler is None:
229
+ raise ValueError(
230
+ "hook_handler is required to create EventHandlers"
231
+ )
232
+ self._event_handlers = EventHandlers(hook_handler)
233
+ return self._event_handlers
234
+
235
+ # =========================================================================
236
+ # Service Override Methods (for testing)
237
+ # =========================================================================
238
+
239
+ def override_state_manager(self, service: IStateManager) -> None:
240
+ """Override state manager with a mock or custom implementation."""
241
+ self._overrides["state_manager"] = service
242
+
243
+ def override_connection_manager(self, service: IConnectionManager) -> None:
244
+ """Override connection manager with a mock or custom implementation."""
245
+ self._overrides["connection_manager"] = service
246
+
247
+ def override_duplicate_detector(self, service: IDuplicateDetector) -> None:
248
+ """Override duplicate detector with a mock or custom implementation."""
249
+ self._overrides["duplicate_detector"] = service
250
+
251
+ def override_response_tracking_manager(
252
+ self, service: IResponseTrackingManager
253
+ ) -> None:
254
+ """Override response tracking manager with a mock or custom implementation."""
255
+ self._overrides["response_tracking_manager"] = service
256
+
257
+ def override_memory_hook_manager(self, service: IMemoryHookManager) -> None:
258
+ """Override memory hook manager with a mock or custom implementation."""
259
+ self._overrides["memory_hook_manager"] = service
260
+
261
+ def override_subagent_processor(self, service: ISubagentProcessor) -> None:
262
+ """Override subagent processor with a mock or custom implementation."""
263
+ self._overrides["subagent_processor"] = service
264
+
265
+ def override_auto_pause_handler(self, service: Optional[IAutoPauseHandler]) -> None:
266
+ """Override auto-pause handler with a mock or custom implementation."""
267
+ self._overrides["auto_pause_handler"] = service
268
+
269
+ def override_event_handlers(self, service: IEventHandlers) -> None:
270
+ """Override event handlers with a mock or custom implementation."""
271
+ self._overrides["event_handlers"] = service
272
+
273
+ def reset_overrides(self) -> None:
274
+ """Reset all service overrides."""
275
+ self._overrides.clear()
276
+
277
+ def reset_all(self) -> None:
278
+ """Reset all services and overrides.
279
+
280
+ Useful for testing to ensure clean state between tests.
281
+ """
282
+ self._state_manager = None
283
+ self._connection_manager = None
284
+ self._duplicate_detector = None
285
+ self._response_tracking_manager = None
286
+ self._memory_hook_manager = None
287
+ self._subagent_processor = None
288
+ self._auto_pause_handler = None
289
+ self._event_handlers = None
290
+ self._overrides.clear()
291
+
292
+ @classmethod
293
+ def reset_singleton(cls) -> None:
294
+ """Reset the singleton instance.
295
+
296
+ USE WITH CAUTION: This is primarily for testing.
297
+ """
298
+ with cls._lock:
299
+ if cls._instance is not None:
300
+ cls._instance.reset_all()
301
+ cls._instance = None
302
+
303
+
304
+ def get_container() -> HookServiceContainer:
305
+ """Get the global HookServiceContainer singleton.
306
+
307
+ Returns:
308
+ The singleton HookServiceContainer instance.
309
+ """
310
+ return HookServiceContainer()