claude-mpm 3.1.3__py3-none-any.whl → 3.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. claude_mpm/__init__.py +3 -3
  2. claude_mpm/__main__.py +0 -17
  3. claude_mpm/agents/INSTRUCTIONS.md +149 -17
  4. claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
  5. claude_mpm/agents/base_agent.json +1 -1
  6. claude_mpm/agents/templates/pm.json +25 -0
  7. claude_mpm/agents/templates/research.json +2 -1
  8. claude_mpm/cli/__init__.py +19 -23
  9. claude_mpm/cli/commands/__init__.py +3 -1
  10. claude_mpm/cli/commands/agents.py +7 -18
  11. claude_mpm/cli/commands/info.py +5 -10
  12. claude_mpm/cli/commands/memory.py +232 -0
  13. claude_mpm/cli/commands/run.py +501 -28
  14. claude_mpm/cli/commands/tickets.py +10 -17
  15. claude_mpm/cli/commands/ui.py +15 -37
  16. claude_mpm/cli/parser.py +91 -1
  17. claude_mpm/cli/utils.py +9 -28
  18. claude_mpm/config/socketio_config.py +256 -0
  19. claude_mpm/constants.py +9 -0
  20. claude_mpm/core/__init__.py +2 -2
  21. claude_mpm/core/agent_registry.py +4 -4
  22. claude_mpm/core/claude_runner.py +919 -0
  23. claude_mpm/core/config.py +21 -1
  24. claude_mpm/core/factories.py +1 -1
  25. claude_mpm/core/hook_manager.py +196 -0
  26. claude_mpm/core/pm_hook_interceptor.py +205 -0
  27. claude_mpm/core/service_registry.py +1 -1
  28. claude_mpm/core/simple_runner.py +323 -33
  29. claude_mpm/core/socketio_pool.py +582 -0
  30. claude_mpm/core/websocket_handler.py +233 -0
  31. claude_mpm/deployment_paths.py +261 -0
  32. claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
  34. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
  35. claude_mpm/hooks/memory_integration_hook.py +312 -0
  36. claude_mpm/models/__init__.py +9 -91
  37. claude_mpm/orchestration/__init__.py +1 -1
  38. claude_mpm/scripts/claude-mpm-socketio +32 -0
  39. claude_mpm/scripts/claude_mpm_monitor.html +567 -0
  40. claude_mpm/scripts/install_socketio_server.py +407 -0
  41. claude_mpm/scripts/launch_monitor.py +132 -0
  42. claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
  43. claude_mpm/scripts/manage_version.py +479 -0
  44. claude_mpm/scripts/socketio_daemon.py +181 -0
  45. claude_mpm/scripts/socketio_server_manager.py +428 -0
  46. claude_mpm/services/__init__.py +5 -0
  47. claude_mpm/services/agent_lifecycle_manager.py +76 -25
  48. claude_mpm/services/agent_memory_manager.py +684 -0
  49. claude_mpm/services/agent_modification_tracker.py +98 -17
  50. claude_mpm/services/agent_persistence_service.py +33 -13
  51. claude_mpm/services/agent_registry.py +82 -43
  52. claude_mpm/services/hook_service.py +362 -0
  53. claude_mpm/services/socketio_client_manager.py +474 -0
  54. claude_mpm/services/socketio_server.py +922 -0
  55. claude_mpm/services/standalone_socketio_server.py +631 -0
  56. claude_mpm/services/ticket_manager.py +4 -5
  57. claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
  58. claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
  59. claude_mpm/services/version_control/semantic_versioning.py +9 -10
  60. claude_mpm/services/websocket_server.py +376 -0
  61. claude_mpm/utils/dependency_manager.py +211 -0
  62. claude_mpm/utils/import_migration_example.py +80 -0
  63. claude_mpm/utils/path_operations.py +0 -20
  64. claude_mpm/web/open_dashboard.py +34 -0
  65. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/METADATA +20 -9
  66. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/RECORD +71 -50
  67. claude_mpm-3.3.0.dist-info/entry_points.txt +7 -0
  68. claude_mpm/cli_old.py +0 -728
  69. claude_mpm/models/common.py +0 -41
  70. claude_mpm/models/lifecycle.py +0 -97
  71. claude_mpm/models/modification.py +0 -126
  72. claude_mpm/models/persistence.py +0 -57
  73. claude_mpm/models/registry.py +0 -91
  74. claude_mpm/security/__init__.py +0 -8
  75. claude_mpm/security/bash_validator.py +0 -393
  76. claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
  77. /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
  78. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/WHEEL +0 -0
  79. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/licenses/LICENSE +0 -0
  80. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,362 @@
1
+ """Hook service for managing pre and post delegation hooks.
2
+
3
+ WHY: The agent system needs a centralized way to manage hooks that can modify
4
+ behavior before and after agent delegation. This service provides a clean API
5
+ for registering, prioritizing, and executing hooks while handling errors gracefully.
6
+
7
+ DESIGN DECISION: We chose to have separate lists for pre/post delegation hooks
8
+ rather than a single list with type checking because:
9
+ - It's more performant (no filtering needed during execution)
10
+ - It's clearer in the API (register_pre_delegation_hook vs checking type)
11
+ - It prevents accidentally mixing hook types
12
+ """
13
+
14
+ from typing import List, Optional, Dict, Any
15
+ import time
16
+ from datetime import datetime
17
+
18
+ from claude_mpm.core.config import Config
19
+ from claude_mpm.core.logger import get_logger
20
+ from claude_mpm.hooks.base_hook import (
21
+ BaseHook,
22
+ PreDelegationHook,
23
+ PostDelegationHook,
24
+ HookContext,
25
+ HookResult,
26
+ HookType
27
+ )
28
+
29
+
30
+ class HookService:
31
+ """Service for managing and executing hooks in the delegation workflow.
32
+
33
+ WHY: Provides a centralized place to register and execute hooks, ensuring
34
+ they run in the correct order and that failures in individual hooks don't
35
+ break the entire delegation flow.
36
+
37
+ DESIGN DECISION: We execute hooks even if previous ones fail because:
38
+ - Hooks should be independent and not rely on each other
39
+ - A logging hook failure shouldn't prevent memory hooks from running
40
+ - We log all failures for debugging but keep the system resilient
41
+ """
42
+
43
+ def __init__(self, config: Optional[Config] = None):
44
+ """Initialize the hook service.
45
+
46
+ Args:
47
+ config: Optional configuration object for controlling hook behavior
48
+ """
49
+ self.config = config or Config()
50
+ self.logger = get_logger("hook_service")
51
+
52
+ # Separate lists for different hook types for performance
53
+ self.pre_delegation_hooks: List[PreDelegationHook] = []
54
+ self.post_delegation_hooks: List[PostDelegationHook] = []
55
+
56
+ # Track execution statistics for monitoring
57
+ self.stats = {
58
+ "pre_delegation_executed": 0,
59
+ "post_delegation_executed": 0,
60
+ "errors": 0
61
+ }
62
+
63
+ def register_hook(self, hook: BaseHook) -> bool:
64
+ """Register a hook with the service.
65
+
66
+ WHY: We need a way to add hooks dynamically, and we validate the hook
67
+ type to ensure it goes into the correct execution list.
68
+
69
+ Args:
70
+ hook: The hook to register
71
+
72
+ Returns:
73
+ True if successfully registered, False otherwise
74
+ """
75
+ try:
76
+ if isinstance(hook, PreDelegationHook):
77
+ self.pre_delegation_hooks.append(hook)
78
+ # Sort by priority (lower number = higher priority = executes first)
79
+ self.pre_delegation_hooks.sort(key=lambda h: h.priority)
80
+ self.logger.info(f"Registered pre-delegation hook: {hook.name} (priority: {hook.priority})")
81
+ return True
82
+
83
+ elif isinstance(hook, PostDelegationHook):
84
+ self.post_delegation_hooks.append(hook)
85
+ # Sort by priority
86
+ self.post_delegation_hooks.sort(key=lambda h: h.priority)
87
+ self.logger.info(f"Registered post-delegation hook: {hook.name} (priority: {hook.priority})")
88
+ return True
89
+
90
+ else:
91
+ self.logger.warning(f"Attempted to register unsupported hook type: {type(hook).__name__}")
92
+ return False
93
+
94
+ except Exception as e:
95
+ self.logger.error(f"Failed to register hook {hook.name}: {e}")
96
+ return False
97
+
98
+ def execute_pre_delegation_hooks(self, context: HookContext) -> HookResult:
99
+ """Execute all pre-delegation hooks in priority order.
100
+
101
+ WHY: Pre-delegation hooks need to modify the context before it's sent
102
+ to the agent. Each hook can add or modify context data.
103
+
104
+ DESIGN DECISION: We pass the context through all hooks sequentially,
105
+ allowing each to modify it. This enables powerful composition patterns.
106
+
107
+ Args:
108
+ context: The HookContext to be processed by hooks
109
+
110
+ Returns:
111
+ HookResult containing the final processed data
112
+ """
113
+ # Check if hooks are enabled via config
114
+ if not self._are_hooks_enabled("pre_delegation"):
115
+ return HookResult(
116
+ success=True,
117
+ data=context.data,
118
+ modified=False
119
+ )
120
+
121
+ # Create a working copy of the data to preserve original
122
+ working_data = context.data.copy()
123
+
124
+ executed_count = 0
125
+ has_modifications = False
126
+
127
+ for hook in self.pre_delegation_hooks:
128
+ if not hook.enabled:
129
+ self.logger.debug(f"Skipping disabled hook: {hook.name}")
130
+ continue
131
+
132
+ try:
133
+ # Validate if hook should run
134
+ if not hook.validate(context):
135
+ self.logger.debug(f"Hook {hook.name} validation failed, skipping")
136
+ continue
137
+
138
+ # Create a hook context with the current working data
139
+ hook_context = HookContext(
140
+ hook_type=context.hook_type,
141
+ data=working_data,
142
+ metadata=context.metadata,
143
+ timestamp=context.timestamp,
144
+ session_id=context.session_id,
145
+ user_id=context.user_id
146
+ )
147
+
148
+ # Execute with timing
149
+ start_time = time.time()
150
+ result = hook.execute(hook_context)
151
+ execution_time = (time.time() - start_time) * 1000 # ms
152
+
153
+ if result.success:
154
+ if result.modified and result.data:
155
+ # Update working data with modified data
156
+ working_data.update(result.data)
157
+ has_modifications = True
158
+ self.logger.debug(f"Hook {hook.name} modified context")
159
+
160
+ self.logger.info(
161
+ f"Executed pre-delegation hook {hook.name} "
162
+ f"(took {execution_time:.2f}ms)"
163
+ )
164
+ else:
165
+ self.logger.warning(f"Hook {hook.name} failed: {result.error}")
166
+ self.stats["errors"] += 1
167
+
168
+ executed_count += 1
169
+
170
+ except Exception as e:
171
+ # Log error but continue with other hooks
172
+ self.logger.error(f"Exception in hook {hook.name}: {e}", exc_info=True)
173
+ self.stats["errors"] += 1
174
+
175
+ self.stats["pre_delegation_executed"] += executed_count
176
+ self.logger.info(f"Executed {executed_count} pre-delegation hooks")
177
+
178
+ return HookResult(
179
+ success=True,
180
+ data=working_data,
181
+ modified=has_modifications
182
+ )
183
+
184
+ def execute_post_delegation_hooks(self, context: HookContext) -> HookResult:
185
+ """Execute all post-delegation hooks in priority order.
186
+
187
+ WHY: Post-delegation hooks need to process agent results, extract learnings,
188
+ log outcomes, or perform other post-processing tasks.
189
+
190
+ Args:
191
+ context: The HookContext containing delegation results
192
+
193
+ Returns:
194
+ HookResult containing the final processed data
195
+ """
196
+ # Check if hooks are enabled via config
197
+ if not self._are_hooks_enabled("post_delegation"):
198
+ return HookResult(
199
+ success=True,
200
+ data=context.data,
201
+ modified=False
202
+ )
203
+
204
+ # Create a working copy of the data to preserve original
205
+ working_data = context.data.copy()
206
+
207
+ executed_count = 0
208
+ has_modifications = False
209
+
210
+ for hook in self.post_delegation_hooks:
211
+ if not hook.enabled:
212
+ self.logger.debug(f"Skipping disabled hook: {hook.name}")
213
+ continue
214
+
215
+ try:
216
+ # Validate if hook should run
217
+ if not hook.validate(context):
218
+ self.logger.debug(f"Hook {hook.name} validation failed, skipping")
219
+ continue
220
+
221
+ # Create a hook context with the current working data
222
+ hook_context = HookContext(
223
+ hook_type=context.hook_type,
224
+ data=working_data,
225
+ metadata=context.metadata,
226
+ timestamp=context.timestamp,
227
+ session_id=context.session_id,
228
+ user_id=context.user_id
229
+ )
230
+
231
+ # Execute with timing
232
+ start_time = time.time()
233
+ result = hook.execute(hook_context)
234
+ execution_time = (time.time() - start_time) * 1000 # ms
235
+
236
+ if result.success:
237
+ if result.modified and result.data:
238
+ # Update working data with modified data
239
+ working_data.update(result.data)
240
+ has_modifications = True
241
+ self.logger.debug(f"Hook {hook.name} modified context")
242
+
243
+ self.logger.info(
244
+ f"Executed post-delegation hook {hook.name} "
245
+ f"(took {execution_time:.2f}ms)"
246
+ )
247
+ else:
248
+ self.logger.warning(f"Hook {hook.name} failed: {result.error}")
249
+ self.stats["errors"] += 1
250
+
251
+ executed_count += 1
252
+
253
+ except Exception as e:
254
+ # Log error but continue with other hooks
255
+ self.logger.error(f"Exception in hook {hook.name}: {e}", exc_info=True)
256
+ self.stats["errors"] += 1
257
+
258
+ self.stats["post_delegation_executed"] += executed_count
259
+ self.logger.info(f"Executed {executed_count} post-delegation hooks")
260
+
261
+ return HookResult(
262
+ success=True,
263
+ data=working_data,
264
+ modified=has_modifications
265
+ )
266
+
267
+ def _are_hooks_enabled(self, hook_type: str) -> bool:
268
+ """Check if hooks are enabled in configuration.
269
+
270
+ WHY: We need fine-grained control over hook execution. This allows
271
+ disabling hooks for debugging or in specific environments.
272
+
273
+ Args:
274
+ hook_type: Type of hooks to check (pre_delegation, post_delegation)
275
+
276
+ Returns:
277
+ True if hooks are enabled
278
+ """
279
+ # Check global hook enable flag
280
+ if not self.config.get("hooks.enabled", True):
281
+ self.logger.debug("All hooks disabled via configuration")
282
+ return False
283
+
284
+ # Check specific hook type enable flag
285
+ if not self.config.get(f"hooks.{hook_type}.enabled", True):
286
+ self.logger.debug(f"{hook_type} hooks disabled via configuration")
287
+ return False
288
+
289
+ # Special check for memory hooks if they depend on memory system
290
+ if hook_type in ["pre_delegation", "post_delegation"]:
291
+ # If any registered hooks are memory-related, check memory.enabled
292
+ for hook in getattr(self, f"{hook_type}_hooks", []):
293
+ if "memory" in hook.name.lower():
294
+ if not self.config.get("memory.enabled", True):
295
+ self.logger.debug("Memory hooks disabled via memory.enabled config")
296
+ return False
297
+
298
+ return True
299
+
300
+ def get_stats(self) -> Dict[str, int]:
301
+ """Get execution statistics.
302
+
303
+ WHY: Monitoring hook execution helps identify performance issues
304
+ and debug problems in production.
305
+
306
+ Returns:
307
+ Dictionary of execution statistics
308
+ """
309
+ return self.stats.copy()
310
+
311
+ def reset_stats(self):
312
+ """Reset execution statistics.
313
+
314
+ WHY: Allows periodic cleanup of stats for long-running services.
315
+ """
316
+ self.stats = {
317
+ "pre_delegation_executed": 0,
318
+ "post_delegation_executed": 0,
319
+ "errors": 0
320
+ }
321
+
322
+ def list_hooks(self) -> Dict[str, List[str]]:
323
+ """List all registered hooks by type.
324
+
325
+ WHY: Useful for debugging and understanding what hooks are active.
326
+
327
+ Returns:
328
+ Dictionary mapping hook type to list of hook names
329
+ """
330
+ return {
331
+ "pre_delegation": [h.name for h in self.pre_delegation_hooks],
332
+ "post_delegation": [h.name for h in self.post_delegation_hooks]
333
+ }
334
+
335
+ def remove_hook(self, hook_name: str) -> bool:
336
+ """Remove a hook by name.
337
+
338
+ WHY: Allows dynamic hook management, useful for testing or
339
+ conditional hook usage.
340
+
341
+ Args:
342
+ hook_name: Name of the hook to remove
343
+
344
+ Returns:
345
+ True if hook was found and removed
346
+ """
347
+ # Check pre-delegation hooks
348
+ for i, hook in enumerate(self.pre_delegation_hooks):
349
+ if hook.name == hook_name:
350
+ self.pre_delegation_hooks.pop(i)
351
+ self.logger.info(f"Removed pre-delegation hook: {hook_name}")
352
+ return True
353
+
354
+ # Check post-delegation hooks
355
+ for i, hook in enumerate(self.post_delegation_hooks):
356
+ if hook.name == hook_name:
357
+ self.post_delegation_hooks.pop(i)
358
+ self.logger.info(f"Removed post-delegation hook: {hook_name}")
359
+ return True
360
+
361
+ self.logger.warning(f"Hook not found: {hook_name}")
362
+ return False