claude-mpm 5.6.1__py3-none-any.whl → 5.6.76__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
- 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 +174 -4
- 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/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- 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/commander_parser.py +43 -10
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +300 -33
- claude_mpm/cli/startup_display.py +4 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/commander/__init__.py +6 -0
- claude_mpm/commander/adapters/__init__.py +32 -3
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +98 -1
- claude_mpm/commander/adapters/claude_code.py +32 -1
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/app.py +32 -16
- claude_mpm/commander/api/errors.py +21 -0
- claude_mpm/commander/api/routes/messages.py +11 -11
- claude_mpm/commander/api/routes/projects.py +20 -20
- claude_mpm/commander/api/routes/sessions.py +37 -26
- claude_mpm/commander/api/routes/work.py +86 -50
- claude_mpm/commander/api/schemas.py +4 -0
- claude_mpm/commander/chat/cli.py +47 -5
- claude_mpm/commander/chat/commands.py +44 -16
- claude_mpm/commander/chat/repl.py +1729 -82
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +215 -10
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +91 -1
- 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 +546 -15
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -2
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/commander/work/executor.py +38 -20
- claude_mpm/commander/workflow/event_handler.py +25 -3
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +30 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +16 -2
- claude_mpm/core/logging_utils.py +40 -16
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +37 -7
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
- claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +222 -54
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
- 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 +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- 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 +10 -9
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- 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/services/pm_skills_deployer.py +5 -3
- claude_mpm/services/skills/git_skill_source_manager.py +79 -8
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +17 -1
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -3,16 +3,30 @@
|
|
|
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
|
-
import sys
|
|
12
17
|
import uuid
|
|
13
18
|
from datetime import datetime, timezone
|
|
14
19
|
from pathlib import Path
|
|
15
|
-
from typing import Optional
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
# Import _log helper to avoid stderr writes (which cause hook errors)
|
|
23
|
+
try:
|
|
24
|
+
from .hook_handler import _log
|
|
25
|
+
except ImportError:
|
|
26
|
+
# Fallback for direct execution
|
|
27
|
+
def _log(message: str) -> None:
|
|
28
|
+
"""Fallback logger when hook_handler not available."""
|
|
29
|
+
|
|
16
30
|
|
|
17
31
|
# Import tool analysis with fallback for direct execution
|
|
18
32
|
try:
|
|
@@ -34,8 +48,15 @@ except ImportError:
|
|
|
34
48
|
extract_tool_results,
|
|
35
49
|
)
|
|
36
50
|
|
|
37
|
-
#
|
|
38
|
-
|
|
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
|
+
|
|
58
|
+
# Debug mode - MUST match hook_handler.py default (false) to prevent stderr writes
|
|
59
|
+
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "false").lower() == "true"
|
|
39
60
|
|
|
40
61
|
# Import constants for configuration
|
|
41
62
|
try:
|
|
@@ -46,13 +67,152 @@ except ImportError:
|
|
|
46
67
|
QUICK_TIMEOUT = 2.0
|
|
47
68
|
|
|
48
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
|
+
|
|
49
150
|
class EventHandlers:
|
|
50
|
-
"""Collection of event handlers for different Claude Code hook events.
|
|
151
|
+
"""Collection of event handlers for different Claude Code hook events.
|
|
152
|
+
|
|
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
|
+
"""
|
|
51
161
|
|
|
52
|
-
def __init__(
|
|
53
|
-
|
|
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
|
+
"""
|
|
54
180
|
self.hook_handler = hook_handler
|
|
55
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
|
+
|
|
56
216
|
def handle_user_prompt_fast(self, event):
|
|
57
217
|
"""Handle user prompt with comprehensive data capture.
|
|
58
218
|
|
|
@@ -111,14 +271,22 @@ class EventHandlers:
|
|
|
111
271
|
"working_directory": working_dir,
|
|
112
272
|
}
|
|
113
273
|
if DEBUG:
|
|
114
|
-
|
|
115
|
-
f"Stored prompt for comprehensive tracking: session {session_id[:8]}..."
|
|
116
|
-
file=sys.stderr,
|
|
274
|
+
_log(
|
|
275
|
+
f"Stored prompt for comprehensive tracking: session {session_id[:8]}..."
|
|
117
276
|
)
|
|
118
277
|
except Exception: # nosec B110
|
|
119
278
|
# Response tracking is optional - silently continue if it fails
|
|
120
279
|
pass
|
|
121
280
|
|
|
281
|
+
# Record user message for auto-pause if active
|
|
282
|
+
auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
|
|
283
|
+
if auto_pause and auto_pause.is_pause_active():
|
|
284
|
+
try:
|
|
285
|
+
auto_pause.on_user_message(prompt)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
if DEBUG:
|
|
288
|
+
_log(f"Auto-pause user message recording error: {e}")
|
|
289
|
+
|
|
122
290
|
# Emit normalized event (namespace no longer needed with normalized events)
|
|
123
291
|
self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
|
|
124
292
|
|
|
@@ -133,11 +301,8 @@ class EventHandlers:
|
|
|
133
301
|
# Enhanced debug logging for session correlation
|
|
134
302
|
session_id = event.get("session_id", "")
|
|
135
303
|
if DEBUG:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
file=sys.stderr,
|
|
139
|
-
)
|
|
140
|
-
print(f" - event keys: {list(event.keys())}", file=sys.stderr)
|
|
304
|
+
_log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
|
|
305
|
+
_log(f" - event keys: {list(event.keys())}")
|
|
141
306
|
|
|
142
307
|
tool_name = event.get("tool_name", "")
|
|
143
308
|
tool_input = event.get("tool_input", {})
|
|
@@ -176,13 +341,10 @@ class EventHandlers:
|
|
|
176
341
|
|
|
177
342
|
# Store tool_call_id using CorrelationManager for cross-process retrieval
|
|
178
343
|
if session_id:
|
|
179
|
-
from .correlation_manager import CorrelationManager
|
|
180
|
-
|
|
181
344
|
CorrelationManager.store(session_id, tool_call_id, tool_name)
|
|
182
345
|
if DEBUG:
|
|
183
|
-
|
|
184
|
-
f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
|
|
185
|
-
file=sys.stderr,
|
|
346
|
+
_log(
|
|
347
|
+
f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
|
|
186
348
|
)
|
|
187
349
|
|
|
188
350
|
# Add delegation-specific data if this is a Task tool
|
|
@@ -196,7 +358,7 @@ class EventHandlers:
|
|
|
196
358
|
auto_pause.on_tool_call(tool_name, tool_input)
|
|
197
359
|
except Exception as e:
|
|
198
360
|
if DEBUG:
|
|
199
|
-
|
|
361
|
+
_log(f"Auto-pause tool recording error: {e}")
|
|
200
362
|
|
|
201
363
|
self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
|
|
202
364
|
|
|
@@ -212,9 +374,8 @@ class EventHandlers:
|
|
|
212
374
|
}
|
|
213
375
|
self.hook_handler._emit_socketio_event("", "todo_updated", todo_data)
|
|
214
376
|
if DEBUG:
|
|
215
|
-
|
|
216
|
-
f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}..."
|
|
217
|
-
file=sys.stderr,
|
|
377
|
+
_log(
|
|
378
|
+
f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}..."
|
|
218
379
|
)
|
|
219
380
|
|
|
220
381
|
def _handle_task_delegation(
|
|
@@ -255,12 +416,9 @@ class EventHandlers:
|
|
|
255
416
|
|
|
256
417
|
# Track this delegation for SubagentStop correlation and response tracking
|
|
257
418
|
if DEBUG:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
)
|
|
262
|
-
print(f" - agent_type: {agent_type}", file=sys.stderr)
|
|
263
|
-
print(f" - raw_agent_type: {raw_agent_type}", file=sys.stderr)
|
|
419
|
+
_log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
|
|
420
|
+
_log(f" - agent_type: {agent_type}")
|
|
421
|
+
_log(f" - raw_agent_type: {raw_agent_type}")
|
|
264
422
|
|
|
265
423
|
if session_id and agent_type != "unknown":
|
|
266
424
|
# Prepare request data for response tracking correlation
|
|
@@ -272,24 +430,17 @@ class EventHandlers:
|
|
|
272
430
|
self.hook_handler._track_delegation(session_id, agent_type, request_data)
|
|
273
431
|
|
|
274
432
|
if DEBUG:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
f" - Request data keys: {list(request_data.keys())}",
|
|
278
|
-
file=sys.stderr,
|
|
279
|
-
)
|
|
433
|
+
_log(" - Delegation tracked successfully")
|
|
434
|
+
_log(f" - Request data keys: {list(request_data.keys())}")
|
|
280
435
|
delegation_requests = getattr(
|
|
281
436
|
self.hook_handler, "delegation_requests", {}
|
|
282
437
|
)
|
|
283
|
-
|
|
284
|
-
f" - delegation_requests size: {len(delegation_requests)}",
|
|
285
|
-
file=sys.stderr,
|
|
286
|
-
)
|
|
438
|
+
_log(f" - delegation_requests size: {len(delegation_requests)}")
|
|
287
439
|
|
|
288
440
|
# Log important delegations for debugging
|
|
289
441
|
if DEBUG or agent_type in ["research", "engineer", "qa", "documentation"]:
|
|
290
|
-
|
|
291
|
-
f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'"
|
|
292
|
-
file=sys.stderr,
|
|
442
|
+
_log(
|
|
443
|
+
f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'"
|
|
293
444
|
)
|
|
294
445
|
|
|
295
446
|
# Trigger memory pre-delegation hook
|
|
@@ -316,53 +467,50 @@ class EventHandlers:
|
|
|
316
467
|
)
|
|
317
468
|
|
|
318
469
|
# Log agent prompt if LogManager is available
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
340
|
-
},
|
|
341
|
-
}
|
|
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
|
+
}
|
|
342
490
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
)
|
|
358
507
|
)
|
|
359
|
-
)
|
|
360
508
|
|
|
509
|
+
if DEBUG:
|
|
510
|
+
_log(f" - Agent prompt logged for {agent_type}")
|
|
511
|
+
except Exception as e:
|
|
361
512
|
if DEBUG:
|
|
362
|
-
|
|
363
|
-
except Exception as e:
|
|
364
|
-
if DEBUG:
|
|
365
|
-
print(f" - Could not log agent prompt: {e}", file=sys.stderr)
|
|
513
|
+
_log(f" - Could not log agent prompt: {e}")
|
|
366
514
|
|
|
367
515
|
def _get_git_branch(self, working_dir: Optional[str] = None) -> str:
|
|
368
516
|
"""Get git branch for the given directory with caching."""
|
|
@@ -446,13 +594,10 @@ class EventHandlers:
|
|
|
446
594
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
447
595
|
|
|
448
596
|
# Retrieve tool_call_id using CorrelationManager for cross-process correlation
|
|
449
|
-
from .correlation_manager import CorrelationManager
|
|
450
|
-
|
|
451
597
|
tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
|
|
452
598
|
if DEBUG and tool_call_id:
|
|
453
|
-
|
|
454
|
-
f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
|
|
455
|
-
file=sys.stderr,
|
|
599
|
+
_log(
|
|
600
|
+
f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}..."
|
|
456
601
|
)
|
|
457
602
|
|
|
458
603
|
post_tool_data = {
|
|
@@ -598,19 +743,30 @@ class EventHandlers:
|
|
|
598
743
|
threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
|
|
599
744
|
if threshold_crossed:
|
|
600
745
|
warning = auto_pause.emit_threshold_warning(threshold_crossed)
|
|
601
|
-
|
|
746
|
+
# CRITICAL: Never write to stderr unconditionally - causes hook errors
|
|
747
|
+
# Use _log() instead which only writes to file if DEBUG=true
|
|
748
|
+
_log(f"⚠️ Auto-pause threshold crossed: {warning}")
|
|
602
749
|
|
|
603
750
|
if DEBUG:
|
|
604
|
-
|
|
605
|
-
f" - Auto-pause threshold crossed: {threshold_crossed}"
|
|
606
|
-
file=sys.stderr,
|
|
751
|
+
_log(
|
|
752
|
+
f" - Auto-pause threshold crossed: {threshold_crossed}"
|
|
607
753
|
)
|
|
608
754
|
except Exception as e:
|
|
609
755
|
if DEBUG:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
756
|
+
_log(f"Auto-pause error in handle_stop_fast: {e}")
|
|
757
|
+
|
|
758
|
+
# Finalize pause session if active
|
|
759
|
+
try:
|
|
760
|
+
if auto_pause.is_pause_active():
|
|
761
|
+
session_file = auto_pause.on_session_end()
|
|
762
|
+
if session_file:
|
|
763
|
+
if DEBUG:
|
|
764
|
+
_log(
|
|
765
|
+
f"✅ Auto-pause session finalized: {session_file.name}"
|
|
766
|
+
)
|
|
767
|
+
except Exception as e:
|
|
768
|
+
if DEBUG:
|
|
769
|
+
_log(f"❌ Failed to finalize auto-pause session: {e}")
|
|
614
770
|
|
|
615
771
|
# Track response if enabled
|
|
616
772
|
try:
|
|
@@ -652,24 +808,15 @@ class EventHandlers:
|
|
|
652
808
|
getattr(rtm, "response_tracker", None) is not None if rtm else False
|
|
653
809
|
)
|
|
654
810
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
file=sys.stderr,
|
|
658
|
-
)
|
|
659
|
-
print(
|
|
660
|
-
f" - response_tracker exists: {tracker_exists}",
|
|
661
|
-
file=sys.stderr,
|
|
662
|
-
)
|
|
811
|
+
_log(f" - response_tracking_enabled: {tracking_enabled}")
|
|
812
|
+
_log(f" - response_tracker exists: {tracker_exists}")
|
|
663
813
|
except Exception: # nosec B110
|
|
664
814
|
# If debug logging fails, just skip it
|
|
665
815
|
pass
|
|
666
816
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
)
|
|
671
|
-
print(f" - reason: {metadata['reason']}", file=sys.stderr)
|
|
672
|
-
print(f" - stop_type: {metadata['stop_type']}", file=sys.stderr)
|
|
817
|
+
_log(f" - session_id: {session_id[:8] if session_id else 'None'}...")
|
|
818
|
+
_log(f" - reason: {metadata['reason']}")
|
|
819
|
+
_log(f" - stop_type: {metadata['stop_type']}")
|
|
673
820
|
|
|
674
821
|
def _emit_stop_event(self, event: dict, session_id: str, metadata: dict) -> None:
|
|
675
822
|
"""Emit stop event data to Socket.IO."""
|
|
@@ -731,10 +878,7 @@ class EventHandlers:
|
|
|
731
878
|
# If exact match fails, try partial matching
|
|
732
879
|
if not request_info and session_id:
|
|
733
880
|
if DEBUG:
|
|
734
|
-
|
|
735
|
-
f" - Trying fuzzy match for session {session_id[:16]}...",
|
|
736
|
-
file=sys.stderr,
|
|
737
|
-
)
|
|
881
|
+
_log(f" - Trying fuzzy match for session {session_id[:16]}...")
|
|
738
882
|
# Try to find a session that matches the first 8-16 characters
|
|
739
883
|
for stored_sid in list(delegation_requests.keys()):
|
|
740
884
|
if (
|
|
@@ -747,10 +891,7 @@ class EventHandlers:
|
|
|
747
891
|
)
|
|
748
892
|
):
|
|
749
893
|
if DEBUG:
|
|
750
|
-
|
|
751
|
-
f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
|
|
752
|
-
file=sys.stderr,
|
|
753
|
-
)
|
|
894
|
+
_log(f" - ✅ Fuzzy match found: {stored_sid[:16]}...")
|
|
754
895
|
request_info = delegation_requests.get(stored_sid) # nosec B113
|
|
755
896
|
# Update the key to use the current session_id for consistency
|
|
756
897
|
if request_info:
|
|
@@ -819,9 +960,8 @@ class EventHandlers:
|
|
|
819
960
|
)
|
|
820
961
|
|
|
821
962
|
if file_path and DEBUG:
|
|
822
|
-
|
|
823
|
-
f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}"
|
|
824
|
-
file=sys.stderr,
|
|
963
|
+
_log(
|
|
964
|
+
f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}"
|
|
825
965
|
)
|
|
826
966
|
|
|
827
967
|
# Clean up the request data
|
|
@@ -832,16 +972,13 @@ class EventHandlers:
|
|
|
832
972
|
del delegation_requests[session_id]
|
|
833
973
|
|
|
834
974
|
elif DEBUG:
|
|
835
|
-
|
|
836
|
-
f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}"
|
|
837
|
-
file=sys.stderr,
|
|
975
|
+
_log(
|
|
976
|
+
f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}"
|
|
838
977
|
)
|
|
839
978
|
|
|
840
979
|
except Exception as e:
|
|
841
980
|
if DEBUG:
|
|
842
|
-
|
|
843
|
-
f"❌ Failed to track response on SubagentStop: {e}", file=sys.stderr
|
|
844
|
-
)
|
|
981
|
+
_log(f"❌ Failed to track response on SubagentStop: {e}")
|
|
845
982
|
|
|
846
983
|
def handle_assistant_response(self, event):
|
|
847
984
|
"""Handle assistant response events for comprehensive response tracking.
|
|
@@ -868,7 +1005,7 @@ class EventHandlers:
|
|
|
868
1005
|
self._scan_for_delegation_patterns(event)
|
|
869
1006
|
except Exception as e: # nosec B110
|
|
870
1007
|
if DEBUG:
|
|
871
|
-
|
|
1008
|
+
_log(f"Delegation scanning error: {e}")
|
|
872
1009
|
|
|
873
1010
|
# Get working directory and git branch
|
|
874
1011
|
working_dir = event.get("cwd", "")
|
|
@@ -917,9 +1054,8 @@ class EventHandlers:
|
|
|
917
1054
|
|
|
918
1055
|
# Debug logging
|
|
919
1056
|
if DEBUG:
|
|
920
|
-
|
|
921
|
-
f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}"
|
|
922
|
-
file=sys.stderr,
|
|
1057
|
+
_log(
|
|
1058
|
+
f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}"
|
|
923
1059
|
)
|
|
924
1060
|
|
|
925
1061
|
# Record assistant response for auto-pause if active
|
|
@@ -935,7 +1071,7 @@ class EventHandlers:
|
|
|
935
1071
|
auto_pause.on_assistant_response(summary)
|
|
936
1072
|
except Exception as e:
|
|
937
1073
|
if DEBUG:
|
|
938
|
-
|
|
1074
|
+
_log(f"Auto-pause response recording error: {e}")
|
|
939
1075
|
|
|
940
1076
|
# Emit normalized event
|
|
941
1077
|
self.hook_handler._emit_socketio_event(
|
|
@@ -949,7 +1085,9 @@ class EventHandlers:
|
|
|
949
1085
|
- Provides visibility into new conversation sessions
|
|
950
1086
|
- Enables tracking of session lifecycle and duration
|
|
951
1087
|
- Useful for monitoring concurrent sessions and resource usage
|
|
952
|
-
|
|
1088
|
+
|
|
1089
|
+
NOTE: This handler is intentionally lightweight - only event monitoring.
|
|
1090
|
+
All initialization/deployment logic runs in MPM CLI startup, not here.
|
|
953
1091
|
"""
|
|
954
1092
|
session_id = event.get("session_id", "")
|
|
955
1093
|
working_dir = event.get("cwd", "")
|
|
@@ -963,45 +1101,8 @@ class EventHandlers:
|
|
|
963
1101
|
"hook_event_name": "SessionStart",
|
|
964
1102
|
}
|
|
965
1103
|
|
|
966
|
-
# Auto-inject pending autotodos if enabled
|
|
967
|
-
try:
|
|
968
|
-
from pathlib import Path
|
|
969
|
-
|
|
970
|
-
from claude_mpm.cli.commands.autotodos import get_pending_todos
|
|
971
|
-
from claude_mpm.core.config import Config
|
|
972
|
-
|
|
973
|
-
config = Config()
|
|
974
|
-
auto_inject_enabled = config.get("autotodos.auto_inject_on_startup", True)
|
|
975
|
-
max_todos = config.get("autotodos.max_todos_per_session", 10)
|
|
976
|
-
|
|
977
|
-
if auto_inject_enabled:
|
|
978
|
-
# Pass working directory from event to avoid Path.cwd() issues
|
|
979
|
-
working_dir_param = None
|
|
980
|
-
if working_dir:
|
|
981
|
-
working_dir_param = Path(working_dir)
|
|
982
|
-
|
|
983
|
-
pending_todos = get_pending_todos(
|
|
984
|
-
max_todos=max_todos, working_dir=working_dir_param
|
|
985
|
-
)
|
|
986
|
-
if pending_todos:
|
|
987
|
-
session_start_data["pending_autotodos"] = pending_todos
|
|
988
|
-
session_start_data["autotodos_count"] = len(pending_todos)
|
|
989
|
-
if DEBUG:
|
|
990
|
-
print(
|
|
991
|
-
f" - Auto-injected {len(pending_todos)} pending autotodos",
|
|
992
|
-
file=sys.stderr,
|
|
993
|
-
)
|
|
994
|
-
except Exception as e: # nosec B110
|
|
995
|
-
# Auto-injection is optional - continue if it fails
|
|
996
|
-
if DEBUG:
|
|
997
|
-
print(f" - Failed to auto-inject autotodos: {e}", file=sys.stderr)
|
|
998
|
-
|
|
999
1104
|
# Debug logging
|
|
1000
|
-
|
|
1001
|
-
print(
|
|
1002
|
-
f"Hook handler: Processing SessionStart - session: '{session_id}'",
|
|
1003
|
-
file=sys.stderr,
|
|
1004
|
-
)
|
|
1105
|
+
_log(f"Hook handler: Processing SessionStart - session: '{session_id}'")
|
|
1005
1106
|
|
|
1006
1107
|
# Emit normalized event
|
|
1007
1108
|
self.hook_handler._emit_socketio_event("", "session_start", session_start_data)
|
|
@@ -1044,10 +1145,8 @@ class EventHandlers:
|
|
|
1044
1145
|
|
|
1045
1146
|
# Debug logging
|
|
1046
1147
|
if DEBUG:
|
|
1047
|
-
|
|
1048
|
-
f"Hook handler: SubagentStart - agent_type='{agent_type}', "
|
|
1049
|
-
f"agent_id='{agent_id}', session_id='{session_id[:16]}...'",
|
|
1050
|
-
file=sys.stderr,
|
|
1148
|
+
_log(
|
|
1149
|
+
f"Hook handler: SubagentStart - agent_type='{agent_type}', agent_id='{agent_id}', session_id='{session_id[:16]}...'"
|
|
1051
1150
|
)
|
|
1052
1151
|
|
|
1053
1152
|
# Emit to /hook namespace as subagent_start (NOT session_start!)
|
|
@@ -1076,35 +1175,30 @@ class EventHandlers:
|
|
|
1076
1175
|
- Delegation patterns = PM doing something WRONG → pm.violation (error)
|
|
1077
1176
|
- Script failures = Something BROKEN → autotodo.error (todo)
|
|
1078
1177
|
"""
|
|
1079
|
-
# Only scan if delegation detector
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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:
|
|
1084
1184
|
if DEBUG:
|
|
1085
|
-
|
|
1185
|
+
_log("Delegation detector or event log not available")
|
|
1086
1186
|
return
|
|
1087
1187
|
|
|
1088
1188
|
response_text = event.get("response", "")
|
|
1089
1189
|
if not response_text:
|
|
1090
1190
|
return
|
|
1091
1191
|
|
|
1092
|
-
# Get the delegation detector
|
|
1093
|
-
detector = get_delegation_detector()
|
|
1094
|
-
|
|
1095
1192
|
# Detect delegation patterns
|
|
1096
1193
|
detections = detector.detect_user_delegation(response_text)
|
|
1097
1194
|
|
|
1098
1195
|
if not detections:
|
|
1099
1196
|
return # No patterns detected
|
|
1100
1197
|
|
|
1101
|
-
# Get event log for violation recording
|
|
1102
|
-
event_log = get_event_log()
|
|
1103
|
-
|
|
1104
1198
|
# Create PM violation events (NOT autotodos)
|
|
1105
1199
|
for detection in detections:
|
|
1106
1200
|
# Create event log entry as pm.violation
|
|
1107
|
-
|
|
1201
|
+
event_log_service.append_event(
|
|
1108
1202
|
event_type="pm.violation",
|
|
1109
1203
|
payload={
|
|
1110
1204
|
"violation_type": "delegation_anti_pattern",
|
|
@@ -1121,7 +1215,4 @@ class EventHandlers:
|
|
|
1121
1215
|
)
|
|
1122
1216
|
|
|
1123
1217
|
if DEBUG:
|
|
1124
|
-
|
|
1125
|
-
f"⚠️ PM violation detected: {detection['original_text'][:60]}...",
|
|
1126
|
-
file=sys.stderr,
|
|
1127
|
-
)
|
|
1218
|
+
_log(f"⚠️ PM violation detected: {detection['original_text'][:60]}...")
|