claude-mpm 5.1.9__py3-none-any.whl → 5.4.3__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/agents/PM_INSTRUCTIONS.md +46 -0
- claude_mpm/agents/agent_loader.py +10 -17
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/configure.py +1046 -149
- claude_mpm/cli/commands/configure_agent_display.py +13 -6
- claude_mpm/cli/commands/mpm_init/core.py +158 -1
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +8 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +60 -53
- claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +279 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +322 -53
- claude_mpm/services/agents/git_source_manager.py +20 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +6 -5
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +0 -2
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +1 -77
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +59 -114
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
claude_mpm/cli/executor.py
CHANGED
|
@@ -141,6 +141,14 @@ def execute_command(command: str, args) -> int:
|
|
|
141
141
|
result = agent_source_command(args)
|
|
142
142
|
return result if result is not None else 0
|
|
143
143
|
|
|
144
|
+
# Handle summarize command with lazy import
|
|
145
|
+
if command == "summarize":
|
|
146
|
+
# Lazy import to avoid loading unless needed
|
|
147
|
+
from .commands.summarize import summarize_command
|
|
148
|
+
|
|
149
|
+
result = summarize_command(args)
|
|
150
|
+
return result if result is not None else 0
|
|
151
|
+
|
|
144
152
|
# Handle auto-configure command with lazy import
|
|
145
153
|
if command == "auto-configure":
|
|
146
154
|
# Lazy import to avoid loading unless needed
|
|
@@ -594,6 +594,11 @@ def create_parser(
|
|
|
594
594
|
action="store_true",
|
|
595
595
|
help="Skip confirmation prompts",
|
|
596
596
|
)
|
|
597
|
+
|
|
598
|
+
# Add summarize command
|
|
599
|
+
from ..commands.summarize import add_summarize_parser
|
|
600
|
+
|
|
601
|
+
add_summarize_parser(subparsers)
|
|
597
602
|
except ImportError:
|
|
598
603
|
# Commands module may not be available during testing or refactoring
|
|
599
604
|
pass
|
claude_mpm/cli/startup.py
CHANGED
|
@@ -221,75 +221,76 @@ def discover_and_link_runtime_skills():
|
|
|
221
221
|
|
|
222
222
|
def deploy_output_style_on_startup():
|
|
223
223
|
"""
|
|
224
|
-
Deploy claude-mpm output
|
|
224
|
+
Deploy claude-mpm output styles to PROJECT-LEVEL directory on CLI startup.
|
|
225
225
|
|
|
226
|
-
WHY: Automatically deploy
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
WHY: Automatically deploy output styles to ensure consistent, professional
|
|
227
|
+
communication without emojis and exclamation points. Styles are project-specific
|
|
228
|
+
to allow different projects to have different communication styles.
|
|
229
229
|
|
|
230
|
-
DESIGN DECISION: This is non-blocking and idempotent.
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
|
|
231
|
+
directory (.claude/settings/output-styles/) instead of user-level to maintain
|
|
232
|
+
project isolation.
|
|
233
|
+
|
|
234
|
+
Deploys two styles:
|
|
235
|
+
- claude-mpm-style.md (professional mode)
|
|
236
|
+
- claude-mpm-teacher.md (teaching mode)
|
|
233
237
|
"""
|
|
234
238
|
try:
|
|
239
|
+
import shutil
|
|
235
240
|
from pathlib import Path
|
|
236
241
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# File is empty, need to redeploy with content
|
|
259
|
-
pass # Fall through to deployment below
|
|
260
|
-
else:
|
|
261
|
-
# File has content, check if already active
|
|
262
|
-
settings = json.loads(settings_file.read_text())
|
|
263
|
-
if settings.get("activeOutputStyle") == "claude-mpm":
|
|
264
|
-
# Already deployed and active with content
|
|
265
|
-
already_configured = True
|
|
266
|
-
except Exception:
|
|
267
|
-
pass # Continue with deployment if we can't read settings
|
|
242
|
+
# Source files (in framework package)
|
|
243
|
+
package_dir = Path(__file__).parent.parent / "agents"
|
|
244
|
+
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
245
|
+
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
246
|
+
|
|
247
|
+
# Target directory (PROJECT-LEVEL, not user-level)
|
|
248
|
+
project_dir = Path.cwd()
|
|
249
|
+
output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
|
|
250
|
+
professional_target = output_styles_dir / "claude-mpm-style.md"
|
|
251
|
+
teacher_target = output_styles_dir / "claude-mpm-teacher.md"
|
|
252
|
+
|
|
253
|
+
# Create directory if it doesn't exist
|
|
254
|
+
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
|
|
256
|
+
# Check if already deployed (both files exist and have content)
|
|
257
|
+
already_deployed = (
|
|
258
|
+
professional_target.exists()
|
|
259
|
+
and teacher_target.exists()
|
|
260
|
+
and professional_target.stat().st_size > 0
|
|
261
|
+
and teacher_target.stat().st_size > 0
|
|
262
|
+
)
|
|
268
263
|
|
|
269
|
-
if
|
|
270
|
-
# Show feedback that output
|
|
271
|
-
print("✓ Output
|
|
264
|
+
if already_deployed:
|
|
265
|
+
# Show feedback that output styles are ready
|
|
266
|
+
print("✓ Output styles ready", flush=True)
|
|
272
267
|
return
|
|
273
268
|
|
|
274
|
-
#
|
|
275
|
-
|
|
269
|
+
# Deploy both styles
|
|
270
|
+
deployed_count = 0
|
|
271
|
+
if professional_source.exists():
|
|
272
|
+
shutil.copy2(professional_source, professional_target)
|
|
273
|
+
deployed_count += 1
|
|
276
274
|
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
if teacher_source.exists():
|
|
276
|
+
shutil.copy2(teacher_source, teacher_target)
|
|
277
|
+
deployed_count += 1
|
|
280
278
|
|
|
281
|
-
|
|
279
|
+
if deployed_count > 0:
|
|
280
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
281
|
+
else:
|
|
282
|
+
# Source files missing - log but don't fail
|
|
283
|
+
from ..core.logger import get_logger
|
|
282
284
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
print("✓ Output style configured", flush=True)
|
|
285
|
+
logger = get_logger("cli")
|
|
286
|
+
logger.debug("Output style source files not found")
|
|
286
287
|
|
|
287
288
|
except Exception as e:
|
|
288
289
|
# Non-critical - log but don't fail startup
|
|
289
290
|
from ..core.logger import get_logger
|
|
290
291
|
|
|
291
292
|
logger = get_logger("cli")
|
|
292
|
-
logger.debug(f"Failed to deploy output
|
|
293
|
+
logger.debug(f"Failed to deploy output styles: {e}")
|
|
293
294
|
# Continue execution - output style deployment shouldn't block startup
|
|
294
295
|
|
|
295
296
|
|
|
@@ -386,6 +387,7 @@ def sync_remote_agents_on_startup():
|
|
|
386
387
|
# 1. Must have "/agents/" in path (from git repos)
|
|
387
388
|
# 2. Must not be in PM templates or doc files
|
|
388
389
|
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
390
|
+
# 4. Exclude build artifacts (dist/, build/, .cache/) to prevent double-counting
|
|
389
391
|
agent_files = [
|
|
390
392
|
f
|
|
391
393
|
for f in all_md_files
|
|
@@ -396,6 +398,11 @@ def sync_remote_agents_on_startup():
|
|
|
396
398
|
and f.name.lower() not in pm_templates
|
|
397
399
|
and f.name.lower() not in doc_files
|
|
398
400
|
and f.name.lower() != "base-agent.md"
|
|
401
|
+
# Exclude build artifacts (prevents double-counting source + built files)
|
|
402
|
+
and not any(
|
|
403
|
+
part in str(f).split("/")
|
|
404
|
+
for part in ["dist", "build", ".cache"]
|
|
405
|
+
)
|
|
399
406
|
)
|
|
400
407
|
]
|
|
401
408
|
agent_count = len(agent_files)
|
|
@@ -409,8 +416,8 @@ def sync_remote_agents_on_startup():
|
|
|
409
416
|
show_counter=True,
|
|
410
417
|
)
|
|
411
418
|
|
|
412
|
-
# Deploy agents
|
|
413
|
-
deploy_target = Path.
|
|
419
|
+
# Deploy agents to project-level directory where Claude Code expects them
|
|
420
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
414
421
|
deployment_result = deployment_service.deploy_agents(
|
|
415
422
|
target_dir=deploy_target,
|
|
416
423
|
force_rebuild=False, # Only deploy if versions differ
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
namespace: mpm/
|
|
2
|
+
namespace: mpm/system
|
|
3
3
|
command: organize
|
|
4
|
-
aliases: [mpm-
|
|
5
|
-
migration_target: /mpm/
|
|
6
|
-
category:
|
|
7
|
-
deprecated_aliases: [mpm-organize]
|
|
4
|
+
aliases: [mpm-organize]
|
|
5
|
+
migration_target: /mpm/system:organize
|
|
6
|
+
category: system
|
|
8
7
|
description: Organize project files into proper directories with intelligent pattern detection
|
|
9
8
|
---
|
|
10
9
|
# /mpm-organize
|
|
@@ -316,6 +316,33 @@ class AgentSourceConfiguration:
|
|
|
316
316
|
|
|
317
317
|
return errors
|
|
318
318
|
|
|
319
|
+
def list_sources(self) -> list[dict]:
|
|
320
|
+
"""Return list of source configurations as dictionaries.
|
|
321
|
+
|
|
322
|
+
This method converts GitRepository objects to dictionaries for CLI
|
|
323
|
+
and API compatibility. Called by GitSourceManager and CLI commands.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of dicts with keys: identifier, url, subdirectory, enabled, priority
|
|
327
|
+
|
|
328
|
+
Example:
|
|
329
|
+
>>> config = AgentSourceConfiguration()
|
|
330
|
+
>>> sources = config.list_sources()
|
|
331
|
+
>>> for source in sources:
|
|
332
|
+
... print(f"{source['identifier']} (priority: {source['priority']})")
|
|
333
|
+
"""
|
|
334
|
+
repos = self.get_enabled_repositories()
|
|
335
|
+
return [
|
|
336
|
+
{
|
|
337
|
+
"identifier": repo.identifier,
|
|
338
|
+
"url": repo.url,
|
|
339
|
+
"subdirectory": repo.subdirectory,
|
|
340
|
+
"enabled": repo.enabled,
|
|
341
|
+
"priority": repo.priority,
|
|
342
|
+
}
|
|
343
|
+
for repo in repos
|
|
344
|
+
]
|
|
345
|
+
|
|
319
346
|
def __repr__(self) -> str:
|
|
320
347
|
"""Return string representation of configuration."""
|
|
321
348
|
return (
|
|
@@ -115,7 +115,11 @@ class AgentLoader:
|
|
|
115
115
|
return agents
|
|
116
116
|
|
|
117
117
|
def discover_local_json_templates(self) -> Dict[str, Dict[str, Any]]:
|
|
118
|
-
"""Discover local JSON agent templates
|
|
118
|
+
"""Discover local JSON agent templates.
|
|
119
|
+
|
|
120
|
+
NOTE: This method is kept for backward compatibility but is deprecated.
|
|
121
|
+
The new architecture uses SOURCE (~/.claude-mpm/cache/remote-agents/)
|
|
122
|
+
and DEPLOYMENT (.claude/agents/) locations only.
|
|
119
123
|
|
|
120
124
|
Returns:
|
|
121
125
|
Dictionary mapping agent IDs to agent metadata
|
|
@@ -125,11 +129,10 @@ class AgentLoader:
|
|
|
125
129
|
local_agents = {}
|
|
126
130
|
|
|
127
131
|
# Check for local JSON templates in priority order
|
|
132
|
+
# NOTE: These directories are deprecated in the simplified architecture
|
|
128
133
|
template_dirs = [
|
|
129
|
-
Path.cwd()
|
|
130
|
-
/ ".claude-mpm"
|
|
131
|
-
/ "agents", # Project local agents (highest priority)
|
|
132
|
-
Path.home() / ".claude-mpm" / "agents", # User local agents
|
|
134
|
+
Path.cwd() / ".claude-mpm" / "agents", # Deprecated: Project local agents
|
|
135
|
+
Path.home() / ".claude-mpm" / "agents", # Deprecated: User local agents
|
|
133
136
|
]
|
|
134
137
|
|
|
135
138
|
for priority, template_dir in enumerate(template_dirs):
|
claude_mpm/core/socketio_pool.py
CHANGED
|
@@ -55,8 +55,8 @@ class CircuitState(Enum):
|
|
|
55
55
|
class ConnectionStats:
|
|
56
56
|
"""Connection statistics for monitoring."""
|
|
57
57
|
|
|
58
|
-
created_at: datetime = field(default_factory=datetime.now)
|
|
59
|
-
last_used: datetime = field(default_factory=datetime.now)
|
|
58
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
59
|
+
last_used: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
60
60
|
events_sent: int = 0
|
|
61
61
|
errors: int = 0
|
|
62
62
|
consecutive_errors: int = 0
|
|
@@ -70,7 +70,7 @@ class BatchEvent:
|
|
|
70
70
|
namespace: str
|
|
71
71
|
event: str
|
|
72
72
|
data: Dict[str, Any]
|
|
73
|
-
timestamp: datetime = field(default_factory=datetime.now)
|
|
73
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
class CircuitBreaker:
|
|
@@ -174,26 +174,16 @@ class UnifiedAgentRegistry:
|
|
|
174
174
|
if project_path.exists():
|
|
175
175
|
self.discovery_paths.append(project_path)
|
|
176
176
|
|
|
177
|
-
#
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
local_project_path.exists()
|
|
181
|
-
and local_project_path not in self.discovery_paths
|
|
182
|
-
):
|
|
183
|
-
self.discovery_paths.append(local_project_path)
|
|
184
|
-
logger.debug(f"Added local project templates path: {local_project_path}")
|
|
177
|
+
# NOTE: .claude-mpm/agents/ is deprecated in the simplified architecture
|
|
178
|
+
# Source agents come from ~/.claude-mpm/cache/remote-agents/
|
|
179
|
+
# Deployed agents go to .claude/agents/
|
|
185
180
|
|
|
186
|
-
# User-level agents
|
|
181
|
+
# User-level agents (deprecated in simplified architecture)
|
|
182
|
+
# Keeping for backward compatibility but not actively used
|
|
187
183
|
user_path = self.path_manager.get_user_agents_dir()
|
|
188
184
|
if user_path.exists():
|
|
189
185
|
self.discovery_paths.append(user_path)
|
|
190
186
|
|
|
191
|
-
# Also check for user JSON templates in ~/.claude-mpm/agents/
|
|
192
|
-
local_user_path = Path.home() / ".claude-mpm" / "agents"
|
|
193
|
-
if local_user_path.exists() and local_user_path not in self.discovery_paths:
|
|
194
|
-
self.discovery_paths.append(local_user_path)
|
|
195
|
-
logger.debug(f"Added local user templates path: {local_user_path}")
|
|
196
|
-
|
|
197
187
|
# System-level agents (includes templates as a subdirectory)
|
|
198
188
|
system_path = self.path_manager.get_system_agents_dir()
|
|
199
189
|
if system_path.exists():
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Cross-process correlation storage using .claude-mpm directory."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_correlation_dir() -> Path:
|
|
9
|
+
"""Get correlation directory in project's .claude-mpm folder."""
|
|
10
|
+
# Use CWD's .claude-mpm directory (where hooks run from)
|
|
11
|
+
cwd = Path.cwd()
|
|
12
|
+
return cwd / ".claude-mpm" / "correlations"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TTL_SECONDS = 3600 # 1 hour
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CorrelationManager:
|
|
19
|
+
"""Manages correlation IDs across separate hook processes."""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def store(session_id: str, tool_call_id: str, tool_name: str) -> None:
|
|
23
|
+
"""Store correlation data for later retrieval by post_tool."""
|
|
24
|
+
correlation_dir = get_correlation_dir()
|
|
25
|
+
correlation_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
filepath = correlation_dir / f"correlation_{session_id}.json"
|
|
27
|
+
data = {
|
|
28
|
+
"tool_call_id": tool_call_id,
|
|
29
|
+
"tool_name": tool_name,
|
|
30
|
+
"timestamp": time.time(),
|
|
31
|
+
}
|
|
32
|
+
filepath.write_text(json.dumps(data))
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def retrieve(session_id: str) -> str | None:
|
|
36
|
+
"""Retrieve and delete correlation data from temp file."""
|
|
37
|
+
correlation_dir = get_correlation_dir()
|
|
38
|
+
filepath = correlation_dir / f"correlation_{session_id}.json"
|
|
39
|
+
if not filepath.exists():
|
|
40
|
+
return None
|
|
41
|
+
try:
|
|
42
|
+
data = json.loads(filepath.read_text())
|
|
43
|
+
filepath.unlink() # Delete after reading
|
|
44
|
+
return data.get("tool_call_id")
|
|
45
|
+
except (json.JSONDecodeError, OSError):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def cleanup_old() -> None:
|
|
50
|
+
"""Remove correlation files older than TTL."""
|
|
51
|
+
correlation_dir = get_correlation_dir()
|
|
52
|
+
if not correlation_dir.exists():
|
|
53
|
+
return
|
|
54
|
+
now = time.time()
|
|
55
|
+
for filepath in correlation_dir.glob("correlation_*.json"):
|
|
56
|
+
try:
|
|
57
|
+
if now - filepath.stat().st_mtime > TTL_SECONDS:
|
|
58
|
+
filepath.unlink()
|
|
59
|
+
except OSError:
|
|
60
|
+
pass
|
|
@@ -9,6 +9,7 @@ import os
|
|
|
9
9
|
import re
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
|
+
import uuid
|
|
12
13
|
from datetime import datetime, timezone
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Optional
|
|
@@ -134,6 +135,9 @@ class EventHandlers:
|
|
|
134
135
|
tool_name = event.get("tool_name", "")
|
|
135
136
|
tool_input = event.get("tool_input", {})
|
|
136
137
|
|
|
138
|
+
# Generate unique tool call ID for correlation with post_tool event
|
|
139
|
+
tool_call_id = str(uuid.uuid4())
|
|
140
|
+
|
|
137
141
|
# Extract key parameters based on tool type
|
|
138
142
|
tool_params = extract_tool_parameters(tool_name, tool_input)
|
|
139
143
|
|
|
@@ -144,6 +148,8 @@ class EventHandlers:
|
|
|
144
148
|
working_dir = event.get("cwd", "")
|
|
145
149
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
146
150
|
|
|
151
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
152
|
+
|
|
147
153
|
pre_tool_data = {
|
|
148
154
|
"tool_name": tool_name,
|
|
149
155
|
"operation_type": operation_type,
|
|
@@ -151,15 +157,27 @@ class EventHandlers:
|
|
|
151
157
|
"session_id": event.get("session_id", ""),
|
|
152
158
|
"working_directory": working_dir,
|
|
153
159
|
"git_branch": git_branch,
|
|
154
|
-
"timestamp":
|
|
160
|
+
"timestamp": timestamp,
|
|
155
161
|
"parameter_count": len(tool_input) if isinstance(tool_input, dict) else 0,
|
|
156
162
|
"is_file_operation": tool_name
|
|
157
163
|
in ["Write", "Edit", "MultiEdit", "Read", "LS", "Glob"],
|
|
158
164
|
"is_execution": tool_name in ["Bash", "NotebookEdit"],
|
|
159
165
|
"is_delegation": tool_name == "Task",
|
|
160
166
|
"security_risk": assess_security_risk(tool_name, tool_input),
|
|
167
|
+
"correlation_id": tool_call_id, # Add correlation_id for pre/post correlation
|
|
161
168
|
}
|
|
162
169
|
|
|
170
|
+
# Store tool_call_id using CorrelationManager for cross-process retrieval
|
|
171
|
+
if session_id:
|
|
172
|
+
from .correlation_manager import CorrelationManager
|
|
173
|
+
|
|
174
|
+
CorrelationManager.store(session_id, tool_call_id, tool_name)
|
|
175
|
+
if DEBUG:
|
|
176
|
+
print(
|
|
177
|
+
f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
|
|
178
|
+
file=sys.stderr,
|
|
179
|
+
)
|
|
180
|
+
|
|
163
181
|
# Add delegation-specific data if this is a Task tool
|
|
164
182
|
if tool_name == "Task" and isinstance(tool_input, dict):
|
|
165
183
|
self._handle_task_delegation(tool_input, pre_tool_data, session_id)
|
|
@@ -375,6 +393,7 @@ class EventHandlers:
|
|
|
375
393
|
"""
|
|
376
394
|
tool_name = event.get("tool_name", "")
|
|
377
395
|
exit_code = event.get("exit_code", 0)
|
|
396
|
+
session_id = event.get("session_id", "")
|
|
378
397
|
|
|
379
398
|
# Extract result data
|
|
380
399
|
result_data = extract_tool_results(event)
|
|
@@ -386,6 +405,16 @@ class EventHandlers:
|
|
|
386
405
|
working_dir = event.get("cwd", "")
|
|
387
406
|
git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
|
|
388
407
|
|
|
408
|
+
# Retrieve tool_call_id using CorrelationManager for cross-process correlation
|
|
409
|
+
from .correlation_manager import CorrelationManager
|
|
410
|
+
|
|
411
|
+
tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
|
|
412
|
+
if DEBUG and tool_call_id:
|
|
413
|
+
print(
|
|
414
|
+
f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
|
|
415
|
+
file=sys.stderr,
|
|
416
|
+
)
|
|
417
|
+
|
|
389
418
|
post_tool_data = {
|
|
390
419
|
"tool_name": tool_name,
|
|
391
420
|
"exit_code": exit_code,
|
|
@@ -399,7 +428,7 @@ class EventHandlers:
|
|
|
399
428
|
),
|
|
400
429
|
"duration_ms": duration,
|
|
401
430
|
"result_summary": result_data,
|
|
402
|
-
"session_id":
|
|
431
|
+
"session_id": session_id,
|
|
403
432
|
"working_directory": working_dir,
|
|
404
433
|
"git_branch": git_branch,
|
|
405
434
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
@@ -412,6 +441,10 @@ class EventHandlers:
|
|
|
412
441
|
),
|
|
413
442
|
}
|
|
414
443
|
|
|
444
|
+
# Add correlation_id if available for correlation with pre_tool
|
|
445
|
+
if tool_call_id:
|
|
446
|
+
post_tool_data["correlation_id"] = tool_call_id
|
|
447
|
+
|
|
415
448
|
# Handle Task delegation completion for memory hooks and response tracking
|
|
416
449
|
if tool_name == "Task":
|
|
417
450
|
session_id = event.get("session_id", "")
|
|
@@ -304,6 +304,10 @@ class ClaudeHookHandler:
|
|
|
304
304
|
# Perform periodic cleanup if needed
|
|
305
305
|
if self.state_manager.increment_events_processed():
|
|
306
306
|
self.state_manager.cleanup_old_entries()
|
|
307
|
+
# Also cleanup old correlation files
|
|
308
|
+
from .correlation_manager import CorrelationManager
|
|
309
|
+
|
|
310
|
+
CorrelationManager.cleanup_old()
|
|
307
311
|
if DEBUG:
|
|
308
312
|
print(
|
|
309
313
|
f"🧹 Performed cleanup after {self.state_manager.events_processed} events",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -115,6 +115,9 @@ class ConnectionManagerService:
|
|
|
115
115
|
- Fallback: HTTP POST for reliability when direct connection fails
|
|
116
116
|
- Eliminates duplicate events from multiple emission paths
|
|
117
117
|
"""
|
|
118
|
+
# Extract tool_call_id from data if present for correlation
|
|
119
|
+
tool_call_id = data.get("tool_call_id")
|
|
120
|
+
|
|
118
121
|
# Create event data for normalization
|
|
119
122
|
raw_event = {
|
|
120
123
|
"type": "hook",
|
|
@@ -123,6 +126,7 @@ class ConnectionManagerService:
|
|
|
123
126
|
"data": data,
|
|
124
127
|
"source": "claude_hooks", # Identify the source
|
|
125
128
|
"session_id": data.get("sessionId"), # Include session if available
|
|
129
|
+
"correlation_id": tool_call_id, # Set from tool_call_id for event correlation
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
# Normalize the event using EventNormalizer for consistent schema
|