claude-mpm 5.6.23__py3-none-any.whl → 5.6.72__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/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/commander.py +6 -6
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/startup.py +150 -33
- claude_mpm/cli/startup_display.py +3 -2
- claude_mpm/commander/chat/cli.py +5 -2
- claude_mpm/commander/chat/commands.py +42 -16
- claude_mpm/commander/chat/repl.py +1581 -70
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +87 -0
- claude_mpm/commander/frameworks/mpm.py +9 -14
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/instance_manager.py +428 -13
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/constants.py +5 -0
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +5 -2
- claude_mpm/core/socketio_pool.py +34 -10
- 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 +206 -94
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +175 -51
- 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 +326 -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/init.py +21 -14
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +3 -3
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/server.py +6 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/METADATA +24 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/RECORD +80 -60
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/entry_points.txt +2 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.72.dist-info}/top_level.txt +0 -0
|
@@ -358,8 +358,11 @@ class OutputStyleManager:
|
|
|
358
358
|
|
|
359
359
|
# Only set activeOutputStyle if:
|
|
360
360
|
# 1. No active style is set (first deployment), OR
|
|
361
|
-
# 2.
|
|
362
|
-
|
|
361
|
+
# 2. Current style is "default" (not a real user preference), OR
|
|
362
|
+
# 3. This is a fresh install (file didn't exist before deployment)
|
|
363
|
+
should_activate = (
|
|
364
|
+
current_style is None or current_style == "default" or is_fresh_install
|
|
365
|
+
)
|
|
363
366
|
|
|
364
367
|
if should_activate and current_style != style_name:
|
|
365
368
|
settings["activeOutputStyle"] = style_name
|
claude_mpm/core/socketio_pool.py
CHANGED
|
@@ -192,9 +192,14 @@ class SocketIOConnectionPool:
|
|
|
192
192
|
self.health_running = False
|
|
193
193
|
self.last_health_check = datetime.now(timezone.utc)
|
|
194
194
|
|
|
195
|
-
# Server configuration
|
|
196
|
-
self.
|
|
197
|
-
|
|
195
|
+
# Server configuration - use default immediately, update async
|
|
196
|
+
self.server_port = int(
|
|
197
|
+
os.environ.get(
|
|
198
|
+
"CLAUDE_MPM_SOCKETIO_PORT", str(NetworkConfig.DEFAULT_SOCKETIO_PORT)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
self.server_url = f"http://localhost:{self.server_port}"
|
|
202
|
+
self._port_detection_complete = False
|
|
198
203
|
|
|
199
204
|
# Pool lifecycle
|
|
200
205
|
self._running = False
|
|
@@ -208,7 +213,10 @@ class SocketIOConnectionPool:
|
|
|
208
213
|
return
|
|
209
214
|
|
|
210
215
|
self._running = True
|
|
211
|
-
|
|
216
|
+
|
|
217
|
+
# Start async port detection in background (non-blocking)
|
|
218
|
+
# Default port is already set in __init__, this just updates if a better one is found
|
|
219
|
+
self._detect_server_async()
|
|
212
220
|
|
|
213
221
|
# Start batch processing thread
|
|
214
222
|
self.batch_running = True
|
|
@@ -274,14 +282,29 @@ class SocketIOConnectionPool:
|
|
|
274
282
|
|
|
275
283
|
self.logger.info("Socket.IO connection pool stopped")
|
|
276
284
|
|
|
285
|
+
def _detect_server_async(self):
|
|
286
|
+
"""Start server detection in background thread.
|
|
287
|
+
|
|
288
|
+
This runs port scanning asynchronously to avoid blocking the main thread.
|
|
289
|
+
The default port is already set in __init__, so this just updates if a better one is found.
|
|
290
|
+
"""
|
|
291
|
+
threading.Thread(
|
|
292
|
+
target=self._detect_server, daemon=True, name="port-detect"
|
|
293
|
+
).start()
|
|
294
|
+
|
|
277
295
|
def _detect_server(self):
|
|
278
|
-
"""Detect Socket.IO server configuration.
|
|
279
|
-
|
|
296
|
+
"""Detect Socket.IO server configuration.
|
|
297
|
+
|
|
298
|
+
This method scans ports to find a running Socket.IO server.
|
|
299
|
+
It's designed to be run in a background thread to avoid blocking.
|
|
300
|
+
"""
|
|
301
|
+
# Check environment variable first - if set, use it and skip detection
|
|
280
302
|
env_port = os.environ.get("CLAUDE_MPM_SOCKETIO_PORT")
|
|
281
303
|
if env_port:
|
|
282
304
|
try:
|
|
283
305
|
self.server_port = int(env_port)
|
|
284
306
|
self.server_url = f"http://localhost:{self.server_port}"
|
|
307
|
+
self._port_detection_complete = True
|
|
285
308
|
self.logger.debug(
|
|
286
309
|
f"Using Socket.IO server from environment: {self.server_url}"
|
|
287
310
|
)
|
|
@@ -310,19 +333,20 @@ class SocketIOConnectionPool:
|
|
|
310
333
|
for port in common_ports:
|
|
311
334
|
try:
|
|
312
335
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
313
|
-
|
|
336
|
+
# Use 10ms timeout (reduced from 50ms) for faster scanning
|
|
337
|
+
s.settimeout(0.01)
|
|
314
338
|
result = s.connect_ex(("localhost", port))
|
|
315
339
|
if result == 0:
|
|
316
340
|
self.server_port = port
|
|
317
341
|
self.server_url = f"http://localhost:{port}"
|
|
342
|
+
self._port_detection_complete = True
|
|
318
343
|
self.logger.debug(f"Detected Socket.IO server on port {port}")
|
|
319
344
|
return
|
|
320
345
|
except Exception: # nosec B112 - intentional: skip ports that fail
|
|
321
346
|
continue
|
|
322
347
|
|
|
323
|
-
#
|
|
324
|
-
self.
|
|
325
|
-
self.server_url = f"http://localhost:{self.server_port}"
|
|
348
|
+
# Keep default port set in __init__, mark detection complete
|
|
349
|
+
self._port_detection_complete = True
|
|
326
350
|
self.logger.debug(f"Using default Socket.IO server: {self.server_url}")
|
|
327
351
|
|
|
328
352
|
def _create_client(self) -> Optional[socketio.AsyncClient]:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -56,7 +56,7 @@ except ImportError:
|
|
|
56
56
|
logger = get_logger(__name__)
|
|
57
57
|
|
|
58
58
|
# Debug mode
|
|
59
|
-
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "
|
|
59
|
+
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
|
|
60
60
|
|
|
61
61
|
# Warning messages for threshold crossings
|
|
62
62
|
THRESHOLD_WARNINGS = {
|
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
This module provides individual event handlers for different types of
|
|
5
5
|
Claude Code hook events.
|
|
6
|
+
|
|
7
|
+
Supports Dependency Injection:
|
|
8
|
+
- Optional services can be passed via constructor
|
|
9
|
+
- Lazy loading fallback for services not provided
|
|
10
|
+
- Eliminates runtime imports inside methods
|
|
6
11
|
"""
|
|
7
12
|
|
|
13
|
+
import asyncio
|
|
8
14
|
import os
|
|
9
15
|
import re
|
|
10
16
|
import subprocess # nosec B404 - subprocess used for safe claude CLI version checking only
|
|
11
17
|
import uuid
|
|
12
18
|
from datetime import datetime, timezone
|
|
13
19
|
from pathlib import Path
|
|
14
|
-
from typing import Optional
|
|
20
|
+
from typing import Any, Optional
|
|
15
21
|
|
|
16
22
|
# Import _log helper to avoid stderr writes (which cause hook errors)
|
|
17
23
|
try:
|
|
@@ -42,6 +48,13 @@ except ImportError:
|
|
|
42
48
|
extract_tool_results,
|
|
43
49
|
)
|
|
44
50
|
|
|
51
|
+
# Import correlation manager with fallback for direct execution
|
|
52
|
+
# WHY at top level: Runtime relative imports fail with "no known parent package" error
|
|
53
|
+
try:
|
|
54
|
+
from .correlation_manager import CorrelationManager
|
|
55
|
+
except ImportError:
|
|
56
|
+
from correlation_manager import CorrelationManager
|
|
57
|
+
|
|
45
58
|
# Debug mode - MUST match hook_handler.py default (false) to prevent stderr writes
|
|
46
59
|
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
|
|
47
60
|
|
|
@@ -54,13 +67,152 @@ except ImportError:
|
|
|
54
67
|
QUICK_TIMEOUT = 2.0
|
|
55
68
|
|
|
56
69
|
|
|
70
|
+
# ============================================================================
|
|
71
|
+
# Optional Dependencies - loaded once at module level for DI
|
|
72
|
+
# ============================================================================
|
|
73
|
+
|
|
74
|
+
# Log manager (for agent prompt logging)
|
|
75
|
+
_log_manager: Optional[Any] = None
|
|
76
|
+
_log_manager_loaded = False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_log_manager() -> Optional[Any]:
|
|
80
|
+
"""Get log manager with lazy loading."""
|
|
81
|
+
global _log_manager, _log_manager_loaded
|
|
82
|
+
if not _log_manager_loaded:
|
|
83
|
+
try:
|
|
84
|
+
from claude_mpm.core.log_manager import get_log_manager
|
|
85
|
+
|
|
86
|
+
_log_manager = get_log_manager()
|
|
87
|
+
except ImportError:
|
|
88
|
+
_log_manager = None
|
|
89
|
+
_log_manager_loaded = True
|
|
90
|
+
return _log_manager
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Config service (for autotodos configuration)
|
|
94
|
+
_config: Optional[Any] = None
|
|
95
|
+
_config_loaded = False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_config() -> Optional[Any]:
|
|
99
|
+
"""Get Config with lazy loading."""
|
|
100
|
+
global _config, _config_loaded
|
|
101
|
+
if not _config_loaded:
|
|
102
|
+
try:
|
|
103
|
+
from claude_mpm.core.config import Config
|
|
104
|
+
|
|
105
|
+
_config = Config()
|
|
106
|
+
except ImportError:
|
|
107
|
+
_config = None
|
|
108
|
+
_config_loaded = True
|
|
109
|
+
return _config
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Delegation detector (for anti-pattern detection)
|
|
113
|
+
_delegation_detector: Optional[Any] = None
|
|
114
|
+
_delegation_detector_loaded = False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_delegation_detector_service() -> Optional[Any]:
|
|
118
|
+
"""Get delegation detector with lazy loading."""
|
|
119
|
+
global _delegation_detector, _delegation_detector_loaded
|
|
120
|
+
if not _delegation_detector_loaded:
|
|
121
|
+
try:
|
|
122
|
+
from claude_mpm.services.delegation_detector import get_delegation_detector
|
|
123
|
+
|
|
124
|
+
_delegation_detector = get_delegation_detector()
|
|
125
|
+
except ImportError:
|
|
126
|
+
_delegation_detector = None
|
|
127
|
+
_delegation_detector_loaded = True
|
|
128
|
+
return _delegation_detector
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Event log (for PM violation logging)
|
|
132
|
+
_event_log: Optional[Any] = None
|
|
133
|
+
_event_log_loaded = False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _get_event_log_service() -> Optional[Any]:
|
|
137
|
+
"""Get event log with lazy loading."""
|
|
138
|
+
global _event_log, _event_log_loaded
|
|
139
|
+
if not _event_log_loaded:
|
|
140
|
+
try:
|
|
141
|
+
from claude_mpm.services.event_log import get_event_log
|
|
142
|
+
|
|
143
|
+
_event_log = get_event_log()
|
|
144
|
+
except ImportError:
|
|
145
|
+
_event_log = None
|
|
146
|
+
_event_log_loaded = True
|
|
147
|
+
return _event_log
|
|
148
|
+
|
|
149
|
+
|
|
57
150
|
class EventHandlers:
|
|
58
|
-
"""Collection of event handlers for different Claude Code hook events.
|
|
151
|
+
"""Collection of event handlers for different Claude Code hook events.
|
|
59
152
|
|
|
60
|
-
|
|
61
|
-
|
|
153
|
+
Supports dependency injection for optional services:
|
|
154
|
+
- log_manager: For agent prompt logging
|
|
155
|
+
- config: For autotodos configuration
|
|
156
|
+
- delegation_detector: For anti-pattern detection
|
|
157
|
+
- event_log: For PM violation logging
|
|
158
|
+
|
|
159
|
+
If services are not provided, they are loaded lazily on first use.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
hook_handler,
|
|
165
|
+
*,
|
|
166
|
+
log_manager: Optional[Any] = None,
|
|
167
|
+
config: Optional[Any] = None,
|
|
168
|
+
delegation_detector: Optional[Any] = None,
|
|
169
|
+
event_log: Optional[Any] = None,
|
|
170
|
+
):
|
|
171
|
+
"""Initialize with reference to the main hook handler and optional services.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
hook_handler: The main ClaudeHookHandler instance
|
|
175
|
+
log_manager: Optional LogManager for agent prompt logging
|
|
176
|
+
config: Optional Config for autotodos configuration
|
|
177
|
+
delegation_detector: Optional DelegationDetector for anti-pattern detection
|
|
178
|
+
event_log: Optional EventLog for PM violation logging
|
|
179
|
+
"""
|
|
62
180
|
self.hook_handler = hook_handler
|
|
63
181
|
|
|
182
|
+
# Store injected services (None means use lazy loading)
|
|
183
|
+
self._log_manager = log_manager
|
|
184
|
+
self._config = config
|
|
185
|
+
self._delegation_detector = delegation_detector
|
|
186
|
+
self._event_log = event_log
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def log_manager(self) -> Optional[Any]:
|
|
190
|
+
"""Get log manager (injected or lazy loaded)."""
|
|
191
|
+
if self._log_manager is not None:
|
|
192
|
+
return self._log_manager
|
|
193
|
+
return _get_log_manager()
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def config(self) -> Optional[Any]:
|
|
197
|
+
"""Get config (injected or lazy loaded)."""
|
|
198
|
+
if self._config is not None:
|
|
199
|
+
return self._config
|
|
200
|
+
return _get_config()
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def delegation_detector(self) -> Optional[Any]:
|
|
204
|
+
"""Get delegation detector (injected or lazy loaded)."""
|
|
205
|
+
if self._delegation_detector is not None:
|
|
206
|
+
return self._delegation_detector
|
|
207
|
+
return _get_delegation_detector_service()
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def event_log(self) -> Optional[Any]:
|
|
211
|
+
"""Get event log (injected or lazy loaded)."""
|
|
212
|
+
if self._event_log is not None:
|
|
213
|
+
return self._event_log
|
|
214
|
+
return _get_event_log_service()
|
|
215
|
+
|
|
64
216
|
def handle_user_prompt_fast(self, event):
|
|
65
217
|
"""Handle user prompt with comprehensive data capture.
|
|
66
218
|
|
|
@@ -189,8 +341,6 @@ class EventHandlers:
|
|
|
189
341
|
|
|
190
342
|
# Store tool_call_id using CorrelationManager for cross-process retrieval
|
|
191
343
|
if session_id:
|
|
192
|
-
from .correlation_manager import CorrelationManager
|
|
193
|
-
|
|
194
344
|
CorrelationManager.store(session_id, tool_call_id, tool_name)
|
|
195
345
|
if DEBUG:
|
|
196
346
|
_log(
|
|
@@ -317,53 +467,50 @@ class EventHandlers:
|
|
|
317
467
|
)
|
|
318
468
|
|
|
319
469
|
# Log agent prompt if LogManager is available
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
341
|
-
},
|
|
342
|
-
}
|
|
470
|
+
# Uses injected log_manager or lazy-loaded module-level instance
|
|
471
|
+
log_manager = self.log_manager
|
|
472
|
+
if log_manager is not None:
|
|
473
|
+
try:
|
|
474
|
+
# Prepare prompt content
|
|
475
|
+
prompt_content = tool_input.get("prompt", "")
|
|
476
|
+
if not prompt_content:
|
|
477
|
+
prompt_content = tool_input.get("description", "")
|
|
478
|
+
|
|
479
|
+
if prompt_content:
|
|
480
|
+
# Prepare metadata
|
|
481
|
+
metadata = {
|
|
482
|
+
"agent_type": agent_type,
|
|
483
|
+
"agent_id": f"{agent_type}_{session_id}",
|
|
484
|
+
"session_id": session_id,
|
|
485
|
+
"delegation_context": {
|
|
486
|
+
"description": tool_input.get("description", ""),
|
|
487
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
488
|
+
},
|
|
489
|
+
}
|
|
343
490
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
491
|
+
# Log the agent prompt asynchronously
|
|
492
|
+
try:
|
|
493
|
+
loop = asyncio.get_running_loop()
|
|
494
|
+
_task = asyncio.create_task(
|
|
495
|
+
log_manager.log_prompt(
|
|
496
|
+
f"agent_{agent_type}", prompt_content, metadata
|
|
497
|
+
)
|
|
498
|
+
) # Fire-and-forget logging (ephemeral hook process)
|
|
499
|
+
except RuntimeError:
|
|
500
|
+
# No running loop, create one
|
|
501
|
+
loop = asyncio.new_event_loop()
|
|
502
|
+
asyncio.set_event_loop(loop)
|
|
503
|
+
loop.run_until_complete(
|
|
504
|
+
log_manager.log_prompt(
|
|
505
|
+
f"agent_{agent_type}", prompt_content, metadata
|
|
506
|
+
)
|
|
359
507
|
)
|
|
360
|
-
)
|
|
361
508
|
|
|
509
|
+
if DEBUG:
|
|
510
|
+
_log(f" - Agent prompt logged for {agent_type}")
|
|
511
|
+
except Exception as e:
|
|
362
512
|
if DEBUG:
|
|
363
|
-
_log(f" -
|
|
364
|
-
except Exception as e:
|
|
365
|
-
if DEBUG:
|
|
366
|
-
_log(f" - Could not log agent prompt: {e}")
|
|
513
|
+
_log(f" - Could not log agent prompt: {e}")
|
|
367
514
|
|
|
368
515
|
def _get_git_branch(self, working_dir: Optional[str] = None) -> str:
|
|
369
516
|
"""Get git branch for the given directory with caching."""
|
|
@@ -447,8 +594,6 @@ class EventHandlers:
|
|
|
447
594
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
448
595
|
|
|
449
596
|
# Retrieve tool_call_id using CorrelationManager for cross-process correlation
|
|
450
|
-
from .correlation_manager import CorrelationManager
|
|
451
|
-
|
|
452
597
|
tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
|
|
453
598
|
if DEBUG and tool_call_id:
|
|
454
599
|
_log(
|
|
@@ -600,8 +745,6 @@ class EventHandlers:
|
|
|
600
745
|
warning = auto_pause.emit_threshold_warning(threshold_crossed)
|
|
601
746
|
# CRITICAL: Never write to stderr unconditionally - causes hook errors
|
|
602
747
|
# Use _log() instead which only writes to file if DEBUG=true
|
|
603
|
-
from . import _log
|
|
604
|
-
|
|
605
748
|
_log(f"⚠️ Auto-pause threshold crossed: {warning}")
|
|
606
749
|
|
|
607
750
|
if DEBUG:
|
|
@@ -942,7 +1085,9 @@ class EventHandlers:
|
|
|
942
1085
|
- Provides visibility into new conversation sessions
|
|
943
1086
|
- Enables tracking of session lifecycle and duration
|
|
944
1087
|
- Useful for monitoring concurrent sessions and resource usage
|
|
945
|
-
|
|
1088
|
+
|
|
1089
|
+
NOTE: This handler is intentionally lightweight - only event monitoring.
|
|
1090
|
+
All initialization/deployment logic runs in MPM CLI startup, not here.
|
|
946
1091
|
"""
|
|
947
1092
|
session_id = event.get("session_id", "")
|
|
948
1093
|
working_dir = event.get("cwd", "")
|
|
@@ -956,34 +1101,6 @@ class EventHandlers:
|
|
|
956
1101
|
"hook_event_name": "SessionStart",
|
|
957
1102
|
}
|
|
958
1103
|
|
|
959
|
-
# Auto-inject pending autotodos if enabled
|
|
960
|
-
try:
|
|
961
|
-
from pathlib import Path
|
|
962
|
-
|
|
963
|
-
from claude_mpm.cli.commands.autotodos import get_pending_todos
|
|
964
|
-
from claude_mpm.core.config import Config
|
|
965
|
-
|
|
966
|
-
config = Config()
|
|
967
|
-
auto_inject_enabled = config.get("autotodos.auto_inject_on_startup", True)
|
|
968
|
-
max_todos = config.get("autotodos.max_todos_per_session", 10)
|
|
969
|
-
|
|
970
|
-
if auto_inject_enabled:
|
|
971
|
-
# Pass working directory from event to avoid Path.cwd() issues
|
|
972
|
-
working_dir_param = None
|
|
973
|
-
if working_dir:
|
|
974
|
-
working_dir_param = Path(working_dir)
|
|
975
|
-
|
|
976
|
-
pending_todos = get_pending_todos(
|
|
977
|
-
max_todos=max_todos, working_dir=working_dir_param
|
|
978
|
-
)
|
|
979
|
-
if pending_todos:
|
|
980
|
-
session_start_data["pending_autotodos"] = pending_todos
|
|
981
|
-
session_start_data["autotodos_count"] = len(pending_todos)
|
|
982
|
-
_log(f" - Auto-injected {len(pending_todos)} pending autotodos")
|
|
983
|
-
except Exception as e: # nosec B110
|
|
984
|
-
# Auto-injection is optional - continue if it fails
|
|
985
|
-
_log(f" - Failed to auto-inject autotodos: {e}")
|
|
986
|
-
|
|
987
1104
|
# Debug logging
|
|
988
1105
|
_log(f"Hook handler: Processing SessionStart - session: '{session_id}'")
|
|
989
1106
|
|
|
@@ -1058,11 +1175,12 @@ class EventHandlers:
|
|
|
1058
1175
|
- Delegation patterns = PM doing something WRONG → pm.violation (error)
|
|
1059
1176
|
- Script failures = Something BROKEN → autotodo.error (todo)
|
|
1060
1177
|
"""
|
|
1061
|
-
# Only scan if delegation detector
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1178
|
+
# Only scan if delegation detector and event log are available
|
|
1179
|
+
# Uses injected services or lazy-loaded module-level instances
|
|
1180
|
+
detector = self.delegation_detector
|
|
1181
|
+
event_log_service = self.event_log
|
|
1182
|
+
|
|
1183
|
+
if detector is None or event_log_service is None:
|
|
1066
1184
|
if DEBUG:
|
|
1067
1185
|
_log("Delegation detector or event log not available")
|
|
1068
1186
|
return
|
|
@@ -1071,22 +1189,16 @@ class EventHandlers:
|
|
|
1071
1189
|
if not response_text:
|
|
1072
1190
|
return
|
|
1073
1191
|
|
|
1074
|
-
# Get the delegation detector
|
|
1075
|
-
detector = get_delegation_detector()
|
|
1076
|
-
|
|
1077
1192
|
# Detect delegation patterns
|
|
1078
1193
|
detections = detector.detect_user_delegation(response_text)
|
|
1079
1194
|
|
|
1080
1195
|
if not detections:
|
|
1081
1196
|
return # No patterns detected
|
|
1082
1197
|
|
|
1083
|
-
# Get event log for violation recording
|
|
1084
|
-
event_log = get_event_log()
|
|
1085
|
-
|
|
1086
1198
|
# Create PM violation events (NOT autotodos)
|
|
1087
1199
|
for detection in detections:
|
|
1088
1200
|
# Create event log entry as pm.violation
|
|
1089
|
-
|
|
1201
|
+
event_log_service.append_event(
|
|
1090
1202
|
event_type="pm.violation",
|
|
1091
1203
|
payload={
|
|
1092
1204
|
"violation_type": "delegation_anti_pattern",
|