claude-mpm 4.3.11__py3-none-any.whl → 4.3.13__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 +390 -28
- claude_mpm/agents/templates/data_engineer.json +39 -14
- claude_mpm/agents/templates/research.json +20 -8
- claude_mpm/agents/templates/web_qa.json +25 -10
- claude_mpm/cli/__init__.py +1 -0
- claude_mpm/cli/commands/agent_manager.py +3 -3
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/aggregate.py +1 -1
- claude_mpm/cli/commands/config.py +2 -2
- claude_mpm/cli/commands/configure.py +5 -5
- claude_mpm/cli/commands/configure_tui.py +7 -7
- claude_mpm/cli/commands/dashboard.py +1 -1
- claude_mpm/cli/commands/debug.py +5 -5
- claude_mpm/cli/commands/mcp.py +1 -1
- claude_mpm/cli/commands/mcp_command_router.py +12 -1
- claude_mpm/cli/commands/mcp_config.py +154 -0
- claude_mpm/cli/commands/mcp_external_commands.py +249 -0
- claude_mpm/cli/commands/mcp_install_commands.py +93 -24
- claude_mpm/cli/commands/mcp_setup_external.py +870 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init_handler.py +1 -1
- claude_mpm/cli/commands/run.py +114 -0
- claude_mpm/cli/commands/search.py +292 -0
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +15 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +245 -0
- claude_mpm/cli/startup_logging.py +3 -5
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/constants.py +1 -0
- claude_mpm/core/agent_registry.py +12 -8
- claude_mpm/core/agent_session_manager.py +8 -8
- claude_mpm/core/api_validator.py +4 -4
- claude_mpm/core/base_service.py +10 -10
- claude_mpm/core/cache.py +5 -5
- claude_mpm/core/config_constants.py +1 -1
- claude_mpm/core/container.py +1 -1
- claude_mpm/core/error_handler.py +2 -2
- claude_mpm/core/file_utils.py +1 -1
- claude_mpm/core/framework_loader.py +3 -3
- claude_mpm/core/hook_manager.py +8 -6
- claude_mpm/core/instruction_reinforcement_hook.py +2 -2
- claude_mpm/core/interactive_session.py +1 -1
- claude_mpm/core/lazy.py +3 -3
- claude_mpm/core/log_manager.py +16 -12
- claude_mpm/core/logger.py +16 -11
- claude_mpm/core/logging_config.py +4 -2
- claude_mpm/core/oneshot_session.py +1 -1
- claude_mpm/core/optimized_agent_loader.py +6 -6
- claude_mpm/core/output_style_manager.py +1 -1
- claude_mpm/core/pm_hook_interceptor.py +3 -3
- claude_mpm/core/service_registry.py +1 -1
- claude_mpm/core/session_manager.py +11 -9
- claude_mpm/core/socketio_pool.py +13 -13
- claude_mpm/core/types.py +2 -2
- claude_mpm/core/unified_agent_registry.py +9 -2
- claude_mpm/core/unified_paths.py +1 -1
- claude_mpm/dashboard/analysis_runner.py +4 -4
- claude_mpm/dashboard/api/simple_directory.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +4 -2
- claude_mpm/hooks/base_hook.py +2 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +4 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +12 -12
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -4
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +3 -3
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +15 -14
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +4 -4
- claude_mpm/hooks/claude_hooks/installer.py +3 -3
- claude_mpm/hooks/claude_hooks/memory_integration.py +3 -3
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +5 -5
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +3 -3
- claude_mpm/hooks/claude_hooks/services/state_manager.py +8 -7
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +3 -3
- claude_mpm/hooks/claude_hooks/tool_analysis.py +2 -2
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/hooks/tool_call_interceptor.py +2 -2
- claude_mpm/models/agent_session.py +5 -5
- claude_mpm/services/__init__.py +1 -1
- claude_mpm/services/agent_capabilities_service.py +1 -1
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -13
- claude_mpm/services/agents/deployment/agent_discovery_service.py +22 -6
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +7 -5
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +3 -1
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +1 -1
- claude_mpm/services/agents/deployment/agent_operation_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_state_service.py +2 -2
- claude_mpm/services/agents/deployment/agent_template_builder.py +1 -1
- claude_mpm/services/agents/deployment/agent_versioning.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +2 -3
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +5 -3
- claude_mpm/services/agents/loading/base_agent_manager.py +2 -2
- claude_mpm/services/agents/local_template_manager.py +6 -6
- claude_mpm/services/agents/management/agent_management_service.py +3 -3
- claude_mpm/services/agents/memory/content_manager.py +3 -3
- claude_mpm/services/agents/memory/memory_format_service.py +2 -2
- claude_mpm/services/agents/memory/template_generator.py +3 -3
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/agents/registry/modification_tracker.py +2 -2
- claude_mpm/services/async_session_logger.py +3 -3
- claude_mpm/services/claude_session_logger.py +4 -4
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/cli/agent_listing_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +1 -0
- claude_mpm/services/cli/memory_crud_service.py +11 -6
- claude_mpm/services/cli/memory_output_formatter.py +1 -1
- claude_mpm/services/cli/session_manager.py +15 -11
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/core/memory_manager.py +81 -23
- claude_mpm/services/core/path_resolver.py +2 -2
- claude_mpm/services/diagnostics/checks/installation_check.py +1 -1
- claude_mpm/services/event_aggregator.py +4 -2
- claude_mpm/services/event_bus/direct_relay.py +5 -3
- claude_mpm/services/event_bus/event_bus.py +3 -3
- claude_mpm/services/event_bus/relay.py +6 -4
- claude_mpm/services/events/consumers/dead_letter.py +5 -3
- claude_mpm/services/events/core.py +3 -3
- claude_mpm/services/events/producers/hook.py +6 -6
- claude_mpm/services/events/producers/system.py +8 -8
- claude_mpm/services/exceptions.py +5 -5
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +2 -2
- claude_mpm/services/hook_installer_service.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +6 -4
- claude_mpm/services/infrastructure/daemon_manager.py +2 -2
- claude_mpm/services/infrastructure/logging.py +2 -2
- claude_mpm/services/mcp_config_manager.py +439 -0
- claude_mpm/services/mcp_gateway/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/auto_configure.py +3 -3
- claude_mpm/services/mcp_gateway/config/config_loader.py +1 -1
- claude_mpm/services/mcp_gateway/config/configuration.py +18 -1
- claude_mpm/services/mcp_gateway/core/base.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +52 -0
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +10 -8
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +4 -4
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -3
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +15 -15
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +7 -5
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +443 -0
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +5 -5
- claude_mpm/services/mcp_gateway/tools/hello_world.py +9 -9
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +16 -16
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +17 -17
- claude_mpm/services/memory/builder.py +7 -5
- claude_mpm/services/memory/indexed_memory.py +4 -4
- claude_mpm/services/memory/optimizer.py +6 -6
- claude_mpm/services/memory/router.py +3 -3
- claude_mpm/services/monitor/daemon.py +1 -1
- claude_mpm/services/monitor/daemon_manager.py +6 -6
- claude_mpm/services/monitor/event_emitter.py +2 -2
- claude_mpm/services/monitor/handlers/file.py +1 -1
- claude_mpm/services/monitor/management/lifecycle.py +1 -1
- claude_mpm/services/monitor/server.py +4 -4
- claude_mpm/services/monitor_build_service.py +2 -2
- claude_mpm/services/port_manager.py +2 -2
- claude_mpm/services/response_tracker.py +2 -2
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/socketio/client_proxy.py +2 -2
- claude_mpm/services/socketio/dashboard_server.py +4 -3
- claude_mpm/services/socketio/event_normalizer.py +12 -8
- claude_mpm/services/socketio/handlers/base.py +2 -2
- claude_mpm/services/socketio/handlers/connection.py +10 -10
- claude_mpm/services/socketio/handlers/connection_handler.py +13 -10
- claude_mpm/services/socketio/handlers/file.py +1 -1
- claude_mpm/services/socketio/handlers/git.py +1 -1
- claude_mpm/services/socketio/handlers/hook.py +16 -15
- claude_mpm/services/socketio/migration_utils.py +1 -1
- claude_mpm/services/socketio/monitor_client.py +5 -5
- claude_mpm/services/socketio/server/broadcaster.py +9 -7
- claude_mpm/services/socketio/server/connection_manager.py +2 -2
- claude_mpm/services/socketio/server/core.py +7 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +18 -11
- claude_mpm/services/socketio/server/main.py +13 -13
- claude_mpm/services/socketio_client_manager.py +4 -4
- claude_mpm/services/system_instructions_service.py +2 -2
- claude_mpm/services/ticket_services/validation_service.py +1 -1
- claude_mpm/services/utility_service.py +5 -2
- claude_mpm/services/version_control/branch_strategy.py +2 -2
- claude_mpm/services/version_control/git_operations.py +22 -20
- claude_mpm/services/version_control/semantic_versioning.py +3 -3
- claude_mpm/services/version_control/version_parser.py +7 -5
- claude_mpm/services/visualization/mermaid_generator.py +1 -1
- claude_mpm/storage/state_storage.py +1 -1
- claude_mpm/tools/code_tree_analyzer.py +19 -18
- claude_mpm/tools/code_tree_builder.py +2 -2
- claude_mpm/tools/code_tree_events.py +10 -8
- claude_mpm/tools/socketio_debug.py +3 -3
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- claude_mpm/utils/dependency_strategies.py +8 -3
- claude_mpm/utils/environment_context.py +2 -2
- claude_mpm/utils/error_handler.py +2 -2
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/imports.py +1 -1
- claude_mpm/utils/log_cleanup.py +21 -7
- claude_mpm/validation/agent_validator.py +2 -2
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/METADATA +4 -1
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/RECORD +207 -200
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.11.dist-info → claude_mpm-4.3.13.dist-info}/top_level.txt +0 -0
|
@@ -19,7 +19,7 @@ import contextlib
|
|
|
19
19
|
import os
|
|
20
20
|
import threading
|
|
21
21
|
import time
|
|
22
|
-
from datetime import datetime
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Dict, Optional
|
|
25
25
|
|
|
@@ -365,7 +365,7 @@ class UnifiedMonitorServer:
|
|
|
365
365
|
return web.Response(text=f"Error: {e!s}", status=500)
|
|
366
366
|
|
|
367
367
|
# File content endpoint for file viewer
|
|
368
|
-
async def api_file_handler(request):
|
|
368
|
+
async def api_file_handler(request): # noqa: PLR0911
|
|
369
369
|
"""Handle file content requests."""
|
|
370
370
|
import json
|
|
371
371
|
import os
|
|
@@ -481,7 +481,7 @@ class UnifiedMonitorServer:
|
|
|
481
481
|
config = {
|
|
482
482
|
"workingDirectory": os.getcwd(),
|
|
483
483
|
"gitBranch": "Unknown",
|
|
484
|
-
"serverTime": datetime.
|
|
484
|
+
"serverTime": datetime.now(timezone.utc).isoformat() + "Z",
|
|
485
485
|
"service": "unified-monitor",
|
|
486
486
|
}
|
|
487
487
|
|
|
@@ -661,7 +661,7 @@ class UnifiedMonitorServer:
|
|
|
661
661
|
|
|
662
662
|
# Create heartbeat data
|
|
663
663
|
heartbeat_data = {
|
|
664
|
-
"timestamp": datetime.
|
|
664
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
665
665
|
"type": "heartbeat",
|
|
666
666
|
"server_uptime": uptime_seconds,
|
|
667
667
|
"server_uptime_formatted": uptime_str,
|
|
@@ -156,9 +156,9 @@ class MonitorBuildService(BaseService):
|
|
|
156
156
|
info: Build information dictionary
|
|
157
157
|
"""
|
|
158
158
|
# Add timestamp
|
|
159
|
-
from datetime import datetime
|
|
159
|
+
from datetime import datetime, timezone
|
|
160
160
|
|
|
161
|
-
info["last_updated"] = datetime.
|
|
161
|
+
info["last_updated"] = datetime.now(timezone.utc).isoformat()
|
|
162
162
|
|
|
163
163
|
# Write atomically using temp file and rename
|
|
164
164
|
temp_fd, temp_path = tempfile.mkstemp(
|
|
@@ -243,7 +243,7 @@ class PortManager:
|
|
|
243
243
|
|
|
244
244
|
return any(pattern in cmdline_lower for pattern in daemon_patterns)
|
|
245
245
|
|
|
246
|
-
def kill_process_on_port(self, port: int, force: bool = False) -> bool:
|
|
246
|
+
def kill_process_on_port(self, port: int, force: bool = False) -> bool: # noqa: PLR0911
|
|
247
247
|
"""Kill a process using a specific port if it's safe to do so.
|
|
248
248
|
|
|
249
249
|
WHY: Automatically reclaim ports from our debug scripts while preserving
|
|
@@ -354,7 +354,7 @@ class PortManager:
|
|
|
354
354
|
|
|
355
355
|
def find_available_port(
|
|
356
356
|
self, preferred_port: Optional[int] = None, reclaim: bool = True
|
|
357
|
-
) -> Optional[int]:
|
|
357
|
+
) -> Optional[int]: # noqa: PLR0911
|
|
358
358
|
"""Find an available port, preferring the specified port if given.
|
|
359
359
|
|
|
360
360
|
WHY: Enhanced to intelligently reclaim ports from our debug processes
|
|
@@ -19,7 +19,7 @@ DESIGN DECISIONS:
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import logging
|
|
22
|
-
from datetime import datetime
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
23
|
from typing import Any, Dict, Optional
|
|
24
24
|
|
|
25
25
|
from claude_mpm.core.config import Config
|
|
@@ -144,7 +144,7 @@ class ResponseTracker:
|
|
|
144
144
|
{
|
|
145
145
|
"agent": agent_name,
|
|
146
146
|
"tracked_by": "ResponseTracker",
|
|
147
|
-
"tracking_timestamp": datetime.now().isoformat(),
|
|
147
|
+
"tracking_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
148
148
|
}
|
|
149
149
|
)
|
|
150
150
|
|
|
@@ -13,6 +13,7 @@ Extracted from ClaudeRunner to follow Single Responsibility Principle.
|
|
|
13
13
|
|
|
14
14
|
import time
|
|
15
15
|
import uuid
|
|
16
|
+
from datetime import datetime, timezone
|
|
16
17
|
from typing import Any, Dict, List, Optional
|
|
17
18
|
|
|
18
19
|
from claude_mpm.core.base_service import BaseService
|
|
@@ -153,7 +154,7 @@ class SessionManagementService(BaseService, SessionManagementInterface):
|
|
|
153
154
|
session_logs_dir.mkdir(parents=True, exist_ok=True)
|
|
154
155
|
|
|
155
156
|
# Generate unique session log filename
|
|
156
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
157
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
157
158
|
session_id = str(uuid.uuid4())[:8]
|
|
158
159
|
log_filename = f"session_{timestamp}_{session_id}.jsonl"
|
|
159
160
|
log_file = session_logs_dir / log_filename
|
|
@@ -183,7 +184,7 @@ class SessionManagementService(BaseService, SessionManagementInterface):
|
|
|
183
184
|
from datetime import datetime
|
|
184
185
|
|
|
185
186
|
# Add timestamp to event data
|
|
186
|
-
event_data["timestamp"] = datetime.now().isoformat()
|
|
187
|
+
event_data["timestamp"] = datetime.now(timezone.utc).isoformat()
|
|
187
188
|
|
|
188
189
|
# Append to log file as JSONL
|
|
189
190
|
with open(log_file, "a") as f:
|
|
@@ -13,7 +13,7 @@ organization and to reduce the complexity of the main server file.
|
|
|
13
13
|
import asyncio
|
|
14
14
|
import threading
|
|
15
15
|
import time
|
|
16
|
-
from datetime import datetime
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
17
|
from typing import Any, Dict, List, Optional
|
|
18
18
|
|
|
19
19
|
try:
|
|
@@ -172,7 +172,7 @@ class SocketIOClientProxy:
|
|
|
172
172
|
try:
|
|
173
173
|
event = {
|
|
174
174
|
"type": event_type,
|
|
175
|
-
"timestamp": datetime.now().isoformat(),
|
|
175
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
176
176
|
"data": data,
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -16,7 +16,7 @@ DESIGN DECISIONS:
|
|
|
16
16
|
- Maintains backward compatibility with existing dashboard clients
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
20
|
from typing import Any, Dict, List, Optional
|
|
21
21
|
|
|
22
22
|
try:
|
|
@@ -125,7 +125,7 @@ class DashboardServer(SocketIOServiceInterface):
|
|
|
125
125
|
# Update state
|
|
126
126
|
self.running = self.dashboard_server.is_running()
|
|
127
127
|
if self.running:
|
|
128
|
-
self.stats["start_time"] = datetime.now().isoformat()
|
|
128
|
+
self.stats["start_time"] = datetime.now(timezone.utc).isoformat()
|
|
129
129
|
self.stats["monitor_connected"] = monitor_connected
|
|
130
130
|
|
|
131
131
|
self.logger.info(
|
|
@@ -224,7 +224,8 @@ class DashboardServer(SocketIOServiceInterface):
|
|
|
224
224
|
"monitor_stats": monitor_stats,
|
|
225
225
|
"uptime": (
|
|
226
226
|
(
|
|
227
|
-
datetime.now(
|
|
227
|
+
datetime.now(timezone.utc)
|
|
228
|
+
- datetime.fromisoformat(self.stats["start_time"])
|
|
228
229
|
).total_seconds()
|
|
229
230
|
if self.stats["start_time"]
|
|
230
231
|
else 0
|
|
@@ -15,7 +15,7 @@ DESIGN DECISION: Transform all events to a consistent schema:
|
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
17
|
from dataclasses import dataclass, field
|
|
18
|
-
from datetime import datetime
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
19
|
from enum import Enum
|
|
20
20
|
from typing import Any, Dict, Optional, Tuple
|
|
21
21
|
|
|
@@ -243,7 +243,7 @@ class EventNormalizer:
|
|
|
243
243
|
source="system",
|
|
244
244
|
type="unknown",
|
|
245
245
|
subtype="error",
|
|
246
|
-
timestamp=datetime.now().isoformat(),
|
|
246
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
247
247
|
data={"original": str(event_data), "error": str(e)},
|
|
248
248
|
)
|
|
249
249
|
|
|
@@ -277,7 +277,9 @@ class EventNormalizer:
|
|
|
277
277
|
source=source,
|
|
278
278
|
type=event_data.get("type", "unknown"),
|
|
279
279
|
subtype=event_data.get("subtype", "generic"),
|
|
280
|
-
timestamp=event_data.get(
|
|
280
|
+
timestamp=event_data.get(
|
|
281
|
+
"timestamp", datetime.now(timezone.utc).isoformat()
|
|
282
|
+
),
|
|
281
283
|
data=event_data.get("data", {}),
|
|
282
284
|
)
|
|
283
285
|
|
|
@@ -340,7 +342,7 @@ class EventNormalizer:
|
|
|
340
342
|
|
|
341
343
|
return "unknown"
|
|
342
344
|
|
|
343
|
-
def _map_event_name(self, event_name: str) -> Tuple[str, str]:
|
|
345
|
+
def _map_event_name(self, event_name: str) -> Tuple[str, str]: # noqa: PLR0911
|
|
344
346
|
"""Map event name to (type, subtype) tuple.
|
|
345
347
|
|
|
346
348
|
WHY: Consistent categorization helps clients filter and handle events.
|
|
@@ -553,16 +555,18 @@ class EventNormalizer:
|
|
|
553
555
|
# Convert other formats
|
|
554
556
|
try:
|
|
555
557
|
if isinstance(timestamp, (int, float)):
|
|
556
|
-
return datetime.fromtimestamp(
|
|
557
|
-
|
|
558
|
+
return datetime.fromtimestamp(
|
|
559
|
+
timestamp, tz=timezone.utc
|
|
560
|
+
).isoformat()
|
|
561
|
+
except Exception:
|
|
558
562
|
pass
|
|
559
563
|
|
|
560
564
|
# Generate new timestamp if not found
|
|
561
|
-
return datetime.now().isoformat()
|
|
565
|
+
return datetime.now(timezone.utc).isoformat()
|
|
562
566
|
|
|
563
567
|
def _determine_source(
|
|
564
568
|
self, event_data: Any, event_type: str, source_override: Optional[str] = None
|
|
565
|
-
) -> str:
|
|
569
|
+
) -> str: # noqa: PLR0911
|
|
566
570
|
"""Determine the source of an event.
|
|
567
571
|
|
|
568
572
|
WHY: Knowing where events originate helps with debugging,
|
|
@@ -5,7 +5,7 @@ logging, error handling, and access to the server instance. All handler
|
|
|
5
5
|
classes inherit from this to ensure consistent behavior.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from datetime import datetime
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
10
10
|
|
|
11
11
|
from ....core.logger import get_logger
|
|
@@ -107,7 +107,7 @@ class BaseEventHandler:
|
|
|
107
107
|
"""
|
|
108
108
|
event = {
|
|
109
109
|
"type": event_type,
|
|
110
|
-
"timestamp": datetime.
|
|
110
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
111
111
|
"data": data,
|
|
112
112
|
}
|
|
113
113
|
self.event_history.append(event)
|
|
@@ -8,7 +8,7 @@ from other handlers makes connection management more maintainable.
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import functools
|
|
10
10
|
import time
|
|
11
|
-
from datetime import datetime
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
12
|
from typing import Any, Callable, Dict, List, Optional
|
|
13
13
|
|
|
14
14
|
from .base import BaseEventHandler
|
|
@@ -48,7 +48,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
|
|
|
48
48
|
f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
|
|
49
49
|
f"(close to {timeout_seconds}s timeout)"
|
|
50
50
|
)
|
|
51
|
-
except:
|
|
51
|
+
except Exception:
|
|
52
52
|
print(
|
|
53
53
|
f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)"
|
|
54
54
|
)
|
|
@@ -65,7 +65,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
|
|
|
65
65
|
logger.error(
|
|
66
66
|
f"❌ Handler {handler_name} timed out after {elapsed:.2f}s"
|
|
67
67
|
)
|
|
68
|
-
except:
|
|
68
|
+
except Exception:
|
|
69
69
|
print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
70
70
|
|
|
71
71
|
return None
|
|
@@ -80,7 +80,7 @@ def timeout_handler(timeout_seconds: float = 5.0):
|
|
|
80
80
|
logger.error(
|
|
81
81
|
f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}"
|
|
82
82
|
)
|
|
83
|
-
except:
|
|
83
|
+
except Exception:
|
|
84
84
|
print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
85
85
|
raise
|
|
86
86
|
|
|
@@ -274,7 +274,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
274
274
|
# Force disconnect if still connected
|
|
275
275
|
try:
|
|
276
276
|
await self.sio.disconnect(sid)
|
|
277
|
-
except:
|
|
277
|
+
except Exception:
|
|
278
278
|
pass # Already disconnected
|
|
279
279
|
|
|
280
280
|
self.logger.info(f"🔌 Cleaned up stale connection: {sid}")
|
|
@@ -326,7 +326,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
326
326
|
status_data = {
|
|
327
327
|
"type": "connection",
|
|
328
328
|
"subtype": "status",
|
|
329
|
-
"timestamp": datetime.
|
|
329
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
330
330
|
"source": "server",
|
|
331
331
|
"session_id": self.server.session_id,
|
|
332
332
|
"data": {
|
|
@@ -348,13 +348,13 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
348
348
|
{
|
|
349
349
|
"type": "connection",
|
|
350
350
|
"subtype": "welcome",
|
|
351
|
-
"timestamp": datetime.
|
|
351
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
352
352
|
"source": "server",
|
|
353
353
|
"session_id": self.server.session_id,
|
|
354
354
|
"data": {
|
|
355
355
|
"message": "Connected to Claude MPM Socket.IO server",
|
|
356
356
|
"client_id": sid,
|
|
357
|
-
"server_time": datetime.
|
|
357
|
+
"server_time": datetime.now(timezone.utc).isoformat() + "Z",
|
|
358
358
|
"build_info": monitor_build_info,
|
|
359
359
|
},
|
|
360
360
|
},
|
|
@@ -401,7 +401,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
401
401
|
status_data = {
|
|
402
402
|
"type": "connection",
|
|
403
403
|
"subtype": "status",
|
|
404
|
-
"timestamp": datetime.
|
|
404
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
405
405
|
"source": "server",
|
|
406
406
|
"session_id": self.server.session_id,
|
|
407
407
|
"data": {
|
|
@@ -456,7 +456,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
456
456
|
{
|
|
457
457
|
"type": "connection",
|
|
458
458
|
"subtype": "subscribed",
|
|
459
|
-
"timestamp": datetime.
|
|
459
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
460
460
|
"source": "server",
|
|
461
461
|
"data": {"channels": channels},
|
|
462
462
|
},
|
|
@@ -8,7 +8,7 @@ DESIGN DECISION: Centralized connection event handling ensures consistent
|
|
|
8
8
|
state management and provides resilient event delivery across reconnections.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from datetime import datetime
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
12
|
|
|
13
13
|
from .base import BaseEventHandler
|
|
14
14
|
|
|
@@ -50,7 +50,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
50
50
|
# Store client info
|
|
51
51
|
self.server.client_info[sid] = {
|
|
52
52
|
"client_id": conn.client_id,
|
|
53
|
-
"connected_at": datetime.now().isoformat(),
|
|
53
|
+
"connected_at": datetime.now(timezone.utc).isoformat(),
|
|
54
54
|
"user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
|
|
55
55
|
"remote_addr": environ.get("REMOTE_ADDR", "unknown"),
|
|
56
56
|
}
|
|
@@ -61,7 +61,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
61
61
|
{
|
|
62
62
|
"client_id": conn.client_id,
|
|
63
63
|
"sid": sid,
|
|
64
|
-
"timestamp": datetime.now().isoformat(),
|
|
64
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
65
65
|
"server_version": self.get_server_version(),
|
|
66
66
|
},
|
|
67
67
|
room=sid,
|
|
@@ -120,7 +120,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
120
120
|
self.server.stats["connections_total"] += 1
|
|
121
121
|
|
|
122
122
|
self.server.client_info[sid] = {
|
|
123
|
-
"connected_at": datetime.now().isoformat(),
|
|
123
|
+
"connected_at": datetime.now(timezone.utc).isoformat(),
|
|
124
124
|
"user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
|
|
125
125
|
"remote_addr": environ.get("REMOTE_ADDR", "unknown"),
|
|
126
126
|
}
|
|
@@ -185,8 +185,8 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
185
185
|
await sio.emit(
|
|
186
186
|
"pong",
|
|
187
187
|
{
|
|
188
|
-
"timestamp": datetime.now().isoformat(),
|
|
189
|
-
"server_time": datetime.now().timestamp(),
|
|
188
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
189
|
+
"server_time": datetime.now(timezone.utc).timestamp(),
|
|
190
190
|
},
|
|
191
191
|
room=sid,
|
|
192
192
|
)
|
|
@@ -210,7 +210,10 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
210
210
|
# Optional: Send confirmation
|
|
211
211
|
await sio.emit(
|
|
212
212
|
"ack_confirmed",
|
|
213
|
-
{
|
|
213
|
+
{
|
|
214
|
+
"sequence": sequence,
|
|
215
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
216
|
+
},
|
|
214
217
|
room=sid,
|
|
215
218
|
)
|
|
216
219
|
|
|
@@ -263,7 +266,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
263
266
|
"""Get connection statistics for debugging."""
|
|
264
267
|
try:
|
|
265
268
|
stats = {
|
|
266
|
-
"timestamp": datetime.now().isoformat(),
|
|
269
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
267
270
|
"total_connections": len(self.server.connected_clients),
|
|
268
271
|
"server_stats": self.server.stats,
|
|
269
272
|
}
|
|
@@ -312,7 +315,7 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
312
315
|
if self.server.stats.get("start_time")
|
|
313
316
|
else None
|
|
314
317
|
),
|
|
315
|
-
"timestamp": datetime.now().isoformat(),
|
|
318
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
316
319
|
}
|
|
317
320
|
|
|
318
321
|
await self.server.core.sio.emit("server_status", status_data, room=sid)
|
|
@@ -326,5 +329,5 @@ class EnhancedConnectionEventHandler(BaseEventHandler):
|
|
|
326
329
|
from claude_mpm.services.version_service import VersionService
|
|
327
330
|
|
|
328
331
|
return VersionService().get_version()
|
|
329
|
-
except:
|
|
332
|
+
except Exception:
|
|
330
333
|
return "unknown"
|
|
@@ -108,7 +108,7 @@ class FileEventHandler(BaseEventHandler):
|
|
|
108
108
|
file_path: str,
|
|
109
109
|
working_dir: Optional[str] = None,
|
|
110
110
|
max_size: int = 1024 * 1024,
|
|
111
|
-
) -> EventData:
|
|
111
|
+
) -> EventData: # noqa: PLR0911
|
|
112
112
|
"""Safely read file content with security checks.
|
|
113
113
|
|
|
114
114
|
WHY: File reading must be secure to prevent directory traversal attacks
|
|
@@ -601,7 +601,7 @@ class GitEventHandler(BaseEventHandler):
|
|
|
601
601
|
file_path: str,
|
|
602
602
|
timestamp: Optional[str] = None,
|
|
603
603
|
working_dir: Optional[str] = None,
|
|
604
|
-
) -> Dict[str, Any]:
|
|
604
|
+
) -> Dict[str, Any]: # noqa: PLR0911
|
|
605
605
|
"""Generate git diff for a specific file operation.
|
|
606
606
|
|
|
607
607
|
WHY: This method generates a git diff showing the changes made to a file
|
|
@@ -4,7 +4,7 @@ WHY: This module handles hook events from Claude to track session information,
|
|
|
4
4
|
agent delegations, and other hook-based activity for the system heartbeat.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from datetime import datetime
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
8
|
from typing import Any, Dict
|
|
9
9
|
|
|
10
10
|
from .base import BaseEventHandler
|
|
@@ -66,7 +66,8 @@ class HookEventHandler(BaseEventHandler):
|
|
|
66
66
|
"type": "hook",
|
|
67
67
|
"event": hook_event,
|
|
68
68
|
"data": hook_data,
|
|
69
|
-
"timestamp": data.get("timestamp")
|
|
69
|
+
"timestamp": data.get("timestamp")
|
|
70
|
+
or datetime.now(timezone.utc).isoformat(),
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
# Add the event to history for replay
|
|
@@ -105,11 +106,11 @@ class HookEventHandler(BaseEventHandler):
|
|
|
105
106
|
if hasattr(self.server, "active_sessions"):
|
|
106
107
|
self.server.active_sessions[session_id] = {
|
|
107
108
|
"session_id": session_id,
|
|
108
|
-
"start_time": datetime.now().isoformat(),
|
|
109
|
+
"start_time": datetime.now(timezone.utc).isoformat(),
|
|
109
110
|
"agent": agent_type,
|
|
110
111
|
"status": "active",
|
|
111
112
|
"prompt": data.get("prompt", "")[:100], # First 100 chars
|
|
112
|
-
"last_activity": datetime.now().isoformat(),
|
|
113
|
+
"last_activity": datetime.now(timezone.utc).isoformat(),
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
self.logger.debug(
|
|
@@ -132,9 +133,9 @@ class HookEventHandler(BaseEventHandler):
|
|
|
132
133
|
if session_id in self.server.active_sessions:
|
|
133
134
|
# Mark as completed rather than removing immediately
|
|
134
135
|
self.server.active_sessions[session_id]["status"] = "completed"
|
|
135
|
-
self.server.active_sessions[session_id][
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
self.server.active_sessions[session_id]["last_activity"] = datetime.now(
|
|
137
|
+
timezone.utc
|
|
138
|
+
).isoformat()
|
|
138
139
|
|
|
139
140
|
self.logger.debug(
|
|
140
141
|
f"Marked session completed: session={session_id[:8]}..."
|
|
@@ -156,18 +157,18 @@ class HookEventHandler(BaseEventHandler):
|
|
|
156
157
|
if session_id not in self.server.active_sessions:
|
|
157
158
|
self.server.active_sessions[session_id] = {
|
|
158
159
|
"session_id": session_id,
|
|
159
|
-
"start_time": datetime.now().isoformat(),
|
|
160
|
+
"start_time": datetime.now(timezone.utc).isoformat(),
|
|
160
161
|
"agent": "pm", # Default to PM
|
|
161
162
|
"status": "active",
|
|
162
163
|
"prompt": data.get("prompt_text", "")[:100],
|
|
163
164
|
"working_directory": data.get("working_directory", ""),
|
|
164
|
-
"last_activity": datetime.now().isoformat(),
|
|
165
|
+
"last_activity": datetime.now(timezone.utc).isoformat(),
|
|
165
166
|
}
|
|
166
167
|
else:
|
|
167
168
|
# Update last activity
|
|
168
|
-
self.server.active_sessions[session_id][
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
self.server.active_sessions[session_id]["last_activity"] = datetime.now(
|
|
170
|
+
timezone.utc
|
|
171
|
+
).isoformat()
|
|
171
172
|
|
|
172
173
|
async def _handle_pre_tool(self, data: Dict[str, Any]):
|
|
173
174
|
"""Handle pre-tool events.
|
|
@@ -190,9 +191,9 @@ class HookEventHandler(BaseEventHandler):
|
|
|
190
191
|
if session_id in self.server.active_sessions:
|
|
191
192
|
self.server.active_sessions[session_id]["agent"] = agent_type
|
|
192
193
|
self.server.active_sessions[session_id]["status"] = "delegated"
|
|
193
|
-
self.server.active_sessions[session_id][
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
self.server.active_sessions[session_id]["last_activity"] = datetime.now(
|
|
195
|
+
timezone.utc
|
|
196
|
+
).isoformat()
|
|
196
197
|
|
|
197
198
|
self.logger.debug(
|
|
198
199
|
f"Updated session delegation: session={session_id[:8]}..., agent={agent_type}"
|
|
@@ -223,7 +223,7 @@ class EventTypeMapper:
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
@classmethod
|
|
226
|
-
def map_event_type(cls, old_type: str) -> Tuple[str, str]:
|
|
226
|
+
def map_event_type(cls, old_type: str) -> Tuple[str, str]: # noqa: PLR0911
|
|
227
227
|
"""Map an old event type to new type/subtype.
|
|
228
228
|
|
|
229
229
|
WHY: Provides consistent categorization for all events.
|
|
@@ -16,7 +16,7 @@ DESIGN DECISIONS:
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import threading
|
|
18
18
|
import time
|
|
19
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
20
|
from typing import Any, Callable, Dict
|
|
21
21
|
|
|
22
22
|
try:
|
|
@@ -119,7 +119,7 @@ class MonitorClient:
|
|
|
119
119
|
# Use the event loop from the client thread
|
|
120
120
|
if hasattr(self.client, "disconnect"):
|
|
121
121
|
asyncio.run(self.client.disconnect())
|
|
122
|
-
except:
|
|
122
|
+
except Exception:
|
|
123
123
|
pass
|
|
124
124
|
|
|
125
125
|
self.connected = False
|
|
@@ -152,7 +152,7 @@ class MonitorClient:
|
|
|
152
152
|
"monitor_url": self.monitor_url,
|
|
153
153
|
"uptime": (
|
|
154
154
|
(
|
|
155
|
-
datetime.now()
|
|
155
|
+
datetime.now(timezone.utc)
|
|
156
156
|
- datetime.fromisoformat(self.stats["last_connected"])
|
|
157
157
|
).total_seconds()
|
|
158
158
|
if self.stats["last_connected"] and self.connected
|
|
@@ -224,7 +224,7 @@ class MonitorClient:
|
|
|
224
224
|
self.connected = True
|
|
225
225
|
self.connecting = False
|
|
226
226
|
self.stats["successful_connections"] += 1
|
|
227
|
-
self.stats["last_connected"] = datetime.now().isoformat()
|
|
227
|
+
self.stats["last_connected"] = datetime.now(timezone.utc).isoformat()
|
|
228
228
|
self.reconnect_delay = 1.0 # Reset reconnect delay on successful connection
|
|
229
229
|
|
|
230
230
|
self.logger.info(f"Connected to monitor server at {self.monitor_url}")
|
|
@@ -255,7 +255,7 @@ class MonitorClient:
|
|
|
255
255
|
"""Handle disconnection."""
|
|
256
256
|
self.logger.info("Disconnected from monitor server")
|
|
257
257
|
self.connected = False
|
|
258
|
-
self.stats["last_disconnected"] = datetime.now().isoformat()
|
|
258
|
+
self.stats["last_disconnected"] = datetime.now(timezone.utc).isoformat()
|
|
259
259
|
|
|
260
260
|
@self.client.event
|
|
261
261
|
async def connect_error(data):
|
|
@@ -13,7 +13,7 @@ import asyncio
|
|
|
13
13
|
import time
|
|
14
14
|
from collections import deque
|
|
15
15
|
from dataclasses import dataclass
|
|
16
|
-
from datetime import datetime
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
17
|
from typing import Any, Deque, Dict, List, Optional, Set
|
|
18
18
|
|
|
19
19
|
from ..event_normalizer import EventNormalizer
|
|
@@ -284,7 +284,7 @@ class SocketIOEventBroadcaster:
|
|
|
284
284
|
# Reconstruct the raw event
|
|
285
285
|
raw_event = {
|
|
286
286
|
"type": event.event_type,
|
|
287
|
-
"timestamp": datetime.now().isoformat(),
|
|
287
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
288
288
|
"data": {**event.data, "retry_attempt": event.attempt_count + 1},
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -323,7 +323,7 @@ class SocketIOEventBroadcaster:
|
|
|
323
323
|
# Create raw event for normalization
|
|
324
324
|
raw_event = {
|
|
325
325
|
"type": event_type,
|
|
326
|
-
"timestamp": datetime.now().isoformat(),
|
|
326
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
327
327
|
"data": data,
|
|
328
328
|
}
|
|
329
329
|
|
|
@@ -388,11 +388,11 @@ class SocketIOEventBroadcaster:
|
|
|
388
388
|
asyncio.run_coroutine_threadsafe(
|
|
389
389
|
update_activities(), self.loop
|
|
390
390
|
)
|
|
391
|
-
except:
|
|
391
|
+
except Exception:
|
|
392
392
|
pass # Non-critical
|
|
393
393
|
|
|
394
394
|
self.logger.debug(f"Broadcasted event: {event_type}")
|
|
395
|
-
except:
|
|
395
|
+
except Exception:
|
|
396
396
|
# Will be added to retry queue below
|
|
397
397
|
pass
|
|
398
398
|
else:
|
|
@@ -426,13 +426,15 @@ class SocketIOEventBroadcaster:
|
|
|
426
426
|
"session_id": session_id,
|
|
427
427
|
"launch_method": launch_method,
|
|
428
428
|
"working_dir": working_dir,
|
|
429
|
-
"timestamp": datetime.now().isoformat(),
|
|
429
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
430
430
|
},
|
|
431
431
|
)
|
|
432
432
|
|
|
433
433
|
def session_ended(self):
|
|
434
434
|
"""Notify that a session has ended."""
|
|
435
|
-
self.broadcast_event(
|
|
435
|
+
self.broadcast_event(
|
|
436
|
+
"session_ended", {"timestamp": datetime.now(timezone.utc).isoformat()}
|
|
437
|
+
)
|
|
436
438
|
|
|
437
439
|
def claude_status_changed(
|
|
438
440
|
self, status: str, pid: Optional[int] = None, message: str = ""
|
|
@@ -14,7 +14,7 @@ import contextlib
|
|
|
14
14
|
import time
|
|
15
15
|
from collections import deque
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
|
-
from datetime import datetime
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
18
|
from enum import Enum
|
|
19
19
|
from typing import Any, Deque, Dict, List, Optional
|
|
20
20
|
from uuid import uuid4
|
|
@@ -464,7 +464,7 @@ class ConnectionManager:
|
|
|
464
464
|
async with self._lock:
|
|
465
465
|
now = time.time()
|
|
466
466
|
report = {
|
|
467
|
-
"timestamp": datetime.now().isoformat(),
|
|
467
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
468
468
|
"total_connections": len(self.connections),
|
|
469
469
|
"healthy": 0,
|
|
470
470
|
"stale": 0,
|