claude-mpm 5.6.17__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/commander.py +7 -7
- claude_mpm/cli/parsers/commander_parser.py +2 -2
- claude_mpm/cli/startup.py +36 -19
- claude_mpm/commander/chat/cli.py +38 -3
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/daemon.py +9 -0
- claude_mpm/commander/frameworks/base.py +4 -1
- claude_mpm/commander/instance_manager.py +124 -11
- claude_mpm/core/claude_runner.py +22 -13
- claude_mpm/core/config.py +3 -3
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +5 -2
- claude_mpm/core/socketio_pool.py +13 -5
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
- claude_mpm/hooks/claude_hooks/event_handlers.py +262 -89
- claude_mpm/hooks/claude_hooks/hook_handler.py +81 -32
- claude_mpm/hooks/claude_hooks/installer.py +90 -28
- claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
- claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
- claude_mpm/hooks/claude_hooks/services/container.py +310 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
- claude_mpm/scripts/claude-hook-handler.sh +3 -3
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/pm_skills_deployer.py +3 -2
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/METADATA +1 -1
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/RECORD +56 -78
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/WHEEL +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.17.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 '{"
|
|
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 '{"
|
|
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
|
-
|
|
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
|
|
209
|
-
|
|
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"]
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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"]
|
|
618
|
-
|
|
619
|
-
|
|
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"]
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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"]
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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", "
|
|
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", "
|
|
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
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -29,8 +29,8 @@ except ImportError:
|
|
|
29
29
|
pass # Silent fallback
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
# Debug mode
|
|
33
|
-
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "
|
|
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
|
|
32
|
-
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "
|
|
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()
|