claude-mpm 4.13.2__py3-none-any.whl → 4.18.2__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/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +48 -17
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +227 -8
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
- claude_mpm/agents/templates/api_qa.json +7 -1
- claude_mpm/agents/templates/clerk-ops.json +8 -1
- claude_mpm/agents/templates/code_analyzer.json +4 -1
- claude_mpm/agents/templates/dart_engineer.json +11 -1
- claude_mpm/agents/templates/data_engineer.json +11 -1
- claude_mpm/agents/templates/documentation.json +6 -1
- claude_mpm/agents/templates/engineer.json +18 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
- claude_mpm/agents/templates/golang_engineer.json +11 -1
- claude_mpm/agents/templates/java_engineer.json +12 -2
- claude_mpm/agents/templates/local_ops_agent.json +1217 -6
- claude_mpm/agents/templates/nextjs_engineer.json +11 -1
- claude_mpm/agents/templates/ops.json +8 -1
- claude_mpm/agents/templates/php-engineer.json +11 -1
- claude_mpm/agents/templates/project_organizer.json +10 -3
- claude_mpm/agents/templates/prompt-engineer.json +5 -1
- claude_mpm/agents/templates/python_engineer.json +11 -1
- claude_mpm/agents/templates/qa.json +7 -1
- claude_mpm/agents/templates/react_engineer.json +11 -1
- claude_mpm/agents/templates/refactoring_engineer.json +8 -1
- claude_mpm/agents/templates/research.json +4 -1
- claude_mpm/agents/templates/ruby-engineer.json +11 -1
- claude_mpm/agents/templates/rust_engineer.json +11 -1
- claude_mpm/agents/templates/security.json +6 -1
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +6 -1
- claude_mpm/agents/templates/typescript_engineer.json +11 -1
- claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
- claude_mpm/agents/templates/version_control.json +8 -1
- claude_mpm/agents/templates/web_qa.json +7 -1
- claude_mpm/agents/templates/web_ui.json +11 -1
- claude_mpm/cli/__init__.py +34 -706
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +204 -148
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +7 -9
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +294 -1788
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +39 -25
- claude_mpm/cli/commands/mpm_init_handler.py +8 -3
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/base_parser.py +98 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +10 -11
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/interfaces/__init__.py +74 -2
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/models/__init__.py +33 -0
- claude_mpm/services/core/models/agent_config.py +12 -28
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/memory_hook_service.py +4 -1
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +117 -8
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +238 -174
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Health Monitoring Data Models for Claude MPM Framework
|
|
3
|
+
=======================================================
|
|
4
|
+
|
|
5
|
+
WHY: This module defines data structures for health monitoring operations,
|
|
6
|
+
including health status, check results, and deployment health aggregations.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISION: Uses dataclasses for immutability and type safety. Provides
|
|
9
|
+
clear health status enum and structured check results.
|
|
10
|
+
|
|
11
|
+
ARCHITECTURE:
|
|
12
|
+
- HealthStatus: Enum of health states (HEALTHY, DEGRADED, UNHEALTHY, UNKNOWN)
|
|
13
|
+
- HealthCheckResult: Result of a single health check
|
|
14
|
+
- DeploymentHealth: Aggregated health status for a deployment
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import asdict, dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from typing import Any, Dict, List
|
|
20
|
+
|
|
21
|
+
from ....core.enums import HealthStatus
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class HealthCheckResult:
|
|
26
|
+
"""
|
|
27
|
+
Result of a single health check.
|
|
28
|
+
|
|
29
|
+
WHY: Contains all information about a specific health check execution,
|
|
30
|
+
enabling detailed analysis and debugging of health issues.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
status: HealthStatus of the check
|
|
34
|
+
check_type: Type of health check (http, process, resource)
|
|
35
|
+
message: Human-readable description of the result
|
|
36
|
+
details: Additional check-specific data
|
|
37
|
+
checked_at: Timestamp when check was performed
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
status: HealthStatus
|
|
41
|
+
check_type: str
|
|
42
|
+
message: str
|
|
43
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
checked_at: datetime = field(default_factory=datetime.now)
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
47
|
+
"""
|
|
48
|
+
Convert to dictionary for JSON serialization.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary representation with datetime converted to ISO format
|
|
52
|
+
"""
|
|
53
|
+
data = asdict(self)
|
|
54
|
+
data["status"] = self.status.value
|
|
55
|
+
data["checked_at"] = self.checked_at.isoformat()
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_dict(cls, data: Dict[str, Any]) -> "HealthCheckResult":
|
|
60
|
+
"""
|
|
61
|
+
Create HealthCheckResult from dictionary.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
data: Dictionary from JSON deserialization
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
HealthCheckResult instance
|
|
68
|
+
"""
|
|
69
|
+
# Convert ISO string to datetime
|
|
70
|
+
if isinstance(data.get("checked_at"), str):
|
|
71
|
+
data["checked_at"] = datetime.fromisoformat(data["checked_at"])
|
|
72
|
+
|
|
73
|
+
# Convert status string to enum
|
|
74
|
+
if isinstance(data.get("status"), str):
|
|
75
|
+
data["status"] = HealthStatus(data["status"])
|
|
76
|
+
|
|
77
|
+
return cls(**data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class DeploymentHealth:
|
|
82
|
+
"""
|
|
83
|
+
Aggregated health status for a deployment.
|
|
84
|
+
|
|
85
|
+
WHY: Combines results from multiple health checks to provide a
|
|
86
|
+
comprehensive health assessment of a deployment.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
deployment_id: Unique deployment identifier
|
|
90
|
+
overall_status: Aggregated health status
|
|
91
|
+
checks: List of individual health check results
|
|
92
|
+
last_check: Timestamp of the most recent health check
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
deployment_id: str
|
|
96
|
+
overall_status: HealthStatus
|
|
97
|
+
checks: List[HealthCheckResult] = field(default_factory=list)
|
|
98
|
+
last_check: datetime = field(default_factory=datetime.now)
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
101
|
+
"""
|
|
102
|
+
Convert to dictionary for JSON serialization.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Dictionary representation
|
|
106
|
+
"""
|
|
107
|
+
return {
|
|
108
|
+
"deployment_id": self.deployment_id,
|
|
109
|
+
"overall_status": self.overall_status.value,
|
|
110
|
+
"checks": [check.to_dict() for check in self.checks],
|
|
111
|
+
"last_check": self.last_check.isoformat(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def from_dict(cls, data: Dict[str, Any]) -> "DeploymentHealth":
|
|
116
|
+
"""
|
|
117
|
+
Create DeploymentHealth from dictionary.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
data: Dictionary from JSON deserialization
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
DeploymentHealth instance
|
|
124
|
+
"""
|
|
125
|
+
# Convert ISO string to datetime
|
|
126
|
+
if isinstance(data.get("last_check"), str):
|
|
127
|
+
data["last_check"] = datetime.fromisoformat(data["last_check"])
|
|
128
|
+
|
|
129
|
+
# Convert status string to enum
|
|
130
|
+
if isinstance(data.get("overall_status"), str):
|
|
131
|
+
data["overall_status"] = HealthStatus(data["overall_status"])
|
|
132
|
+
|
|
133
|
+
# Convert check dicts to HealthCheckResult objects
|
|
134
|
+
if isinstance(data.get("checks"), list):
|
|
135
|
+
data["checks"] = [
|
|
136
|
+
HealthCheckResult.from_dict(check) if isinstance(check, dict) else check
|
|
137
|
+
for check in data["checks"]
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
return cls(**data)
|
|
141
|
+
|
|
142
|
+
def get_check_by_type(self, check_type: str) -> HealthCheckResult | None:
|
|
143
|
+
"""
|
|
144
|
+
Get the result of a specific check type.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
check_type: Type of health check to retrieve
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
HealthCheckResult if found, None otherwise
|
|
151
|
+
"""
|
|
152
|
+
for check in self.checks:
|
|
153
|
+
if check.check_type == check_type:
|
|
154
|
+
return check
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
"DeploymentHealth",
|
|
160
|
+
"HealthCheckResult",
|
|
161
|
+
"HealthStatus",
|
|
162
|
+
]
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Process Management Data Models for Claude MPM Framework
|
|
3
|
+
========================================================
|
|
4
|
+
|
|
5
|
+
WHY: This module defines data structures for process management operations,
|
|
6
|
+
including deployment state and runtime information.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISION: Uses dataclasses for immutability and type safety. Provides
|
|
9
|
+
serialization methods for state persistence. Process status uses ServiceState
|
|
10
|
+
enum from core.enums (consolidated in Phase 3A Batch 24).
|
|
11
|
+
|
|
12
|
+
ARCHITECTURE:
|
|
13
|
+
- DeploymentState: Complete deployment information for persistence
|
|
14
|
+
- ProcessInfo: Runtime process information
|
|
15
|
+
- StartConfig: Configuration for spawning new processes
|
|
16
|
+
|
|
17
|
+
Note: ProcessStatus enum has been consolidated into ServiceState (core.enums).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from dataclasses import asdict, dataclass, field
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
from claude_mpm.core.enums import ServiceState
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class DeploymentState:
|
|
31
|
+
"""
|
|
32
|
+
Complete deployment state for persistence.
|
|
33
|
+
|
|
34
|
+
WHY: Contains all information needed to track, manage, and restart
|
|
35
|
+
a deployment. Serializable to JSON for state file storage.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
deployment_id: Unique identifier for this deployment
|
|
39
|
+
process_id: OS process ID (PID)
|
|
40
|
+
command: Command and arguments used to start process
|
|
41
|
+
working_directory: Working directory for the process
|
|
42
|
+
environment: Environment variables (beyond inherited ones)
|
|
43
|
+
port: Primary port used by the process (if applicable)
|
|
44
|
+
started_at: Timestamp when process was started
|
|
45
|
+
status: Current ServiceState (process lifecycle state)
|
|
46
|
+
metadata: Additional deployment-specific information
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
deployment_id: str
|
|
50
|
+
process_id: int
|
|
51
|
+
command: List[str]
|
|
52
|
+
working_directory: str
|
|
53
|
+
environment: Dict[str, str] = field(default_factory=dict)
|
|
54
|
+
port: Optional[int] = None
|
|
55
|
+
started_at: datetime = field(default_factory=datetime.now)
|
|
56
|
+
status: ServiceState = ServiceState.STARTING
|
|
57
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
"""
|
|
61
|
+
Convert to dictionary for JSON serialization.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dictionary representation with datetime converted to ISO format
|
|
65
|
+
"""
|
|
66
|
+
data = asdict(self)
|
|
67
|
+
data["started_at"] = self.started_at.isoformat()
|
|
68
|
+
data["status"] = self.status.value
|
|
69
|
+
return data
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, data: Dict[str, Any]) -> "DeploymentState":
|
|
73
|
+
"""
|
|
74
|
+
Create DeploymentState from dictionary.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data: Dictionary from JSON deserialization
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
DeploymentState instance
|
|
81
|
+
"""
|
|
82
|
+
# Convert ISO string to datetime
|
|
83
|
+
if isinstance(data.get("started_at"), str):
|
|
84
|
+
data["started_at"] = datetime.fromisoformat(data["started_at"])
|
|
85
|
+
|
|
86
|
+
# Convert status string to enum
|
|
87
|
+
if isinstance(data.get("status"), str):
|
|
88
|
+
status_value = data["status"]
|
|
89
|
+
# Handle legacy "crashed" status -> map to ERROR
|
|
90
|
+
if status_value == "crashed":
|
|
91
|
+
data["status"] = (
|
|
92
|
+
ServiceState.ERROR
|
|
93
|
+
) # CRASHED semantically maps to ERROR state
|
|
94
|
+
else:
|
|
95
|
+
data["status"] = ServiceState(status_value)
|
|
96
|
+
|
|
97
|
+
return cls(**data)
|
|
98
|
+
|
|
99
|
+
def to_json(self) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Serialize to JSON string.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
JSON string representation
|
|
105
|
+
"""
|
|
106
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_json(cls, json_str: str) -> "DeploymentState":
|
|
110
|
+
"""
|
|
111
|
+
Deserialize from JSON string.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
json_str: JSON string
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
DeploymentState instance
|
|
118
|
+
"""
|
|
119
|
+
return cls.from_dict(json.loads(json_str))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class ProcessInfo:
|
|
124
|
+
"""
|
|
125
|
+
Runtime process information.
|
|
126
|
+
|
|
127
|
+
WHY: Provides real-time process status including resource usage and
|
|
128
|
+
health information. Separate from DeploymentState to avoid mixing
|
|
129
|
+
persistent state with transient runtime data.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
deployment_id: Unique deployment identifier
|
|
133
|
+
process_id: OS process ID
|
|
134
|
+
status: Current ServiceState (process lifecycle state)
|
|
135
|
+
port: Port the process is using
|
|
136
|
+
uptime_seconds: How long the process has been running
|
|
137
|
+
memory_mb: Current memory usage in megabytes
|
|
138
|
+
cpu_percent: Current CPU usage percentage
|
|
139
|
+
is_responding: Whether the process responds to health checks
|
|
140
|
+
error_message: Error message if status is ERROR (crashed)
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
deployment_id: str
|
|
144
|
+
process_id: int
|
|
145
|
+
status: ServiceState
|
|
146
|
+
port: Optional[int] = None
|
|
147
|
+
uptime_seconds: float = 0.0
|
|
148
|
+
memory_mb: float = 0.0
|
|
149
|
+
cpu_percent: float = 0.0
|
|
150
|
+
is_responding: bool = False
|
|
151
|
+
error_message: Optional[str] = None
|
|
152
|
+
|
|
153
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
154
|
+
"""Convert to dictionary."""
|
|
155
|
+
data = asdict(self)
|
|
156
|
+
data["status"] = self.status.value
|
|
157
|
+
return data
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class StartConfig:
|
|
162
|
+
"""
|
|
163
|
+
Configuration for starting a new process.
|
|
164
|
+
|
|
165
|
+
WHY: Encapsulates all parameters needed to spawn a process. Provides
|
|
166
|
+
validation and sensible defaults.
|
|
167
|
+
|
|
168
|
+
Attributes:
|
|
169
|
+
command: Command and arguments to execute
|
|
170
|
+
working_directory: Working directory for the process
|
|
171
|
+
environment: Environment variables to set (beyond inherited)
|
|
172
|
+
port: Preferred port for the process
|
|
173
|
+
auto_find_port: If True, find alternative port if preferred is unavailable
|
|
174
|
+
metadata: Additional deployment metadata
|
|
175
|
+
deployment_id: Optional explicit deployment ID (generated if not provided)
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
command: List[str]
|
|
179
|
+
working_directory: str
|
|
180
|
+
environment: Dict[str, str] = field(default_factory=dict)
|
|
181
|
+
port: Optional[int] = None
|
|
182
|
+
auto_find_port: bool = True
|
|
183
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
184
|
+
deployment_id: Optional[str] = None
|
|
185
|
+
|
|
186
|
+
def __post_init__(self):
|
|
187
|
+
"""Validate configuration after initialization."""
|
|
188
|
+
if not self.command:
|
|
189
|
+
raise ValueError("Command cannot be empty")
|
|
190
|
+
|
|
191
|
+
if not self.working_directory:
|
|
192
|
+
raise ValueError("Working directory must be specified")
|
|
193
|
+
|
|
194
|
+
# Ensure working_directory is absolute
|
|
195
|
+
self.working_directory = str(Path(self.working_directory).absolute())
|
|
196
|
+
|
|
197
|
+
# Validate port range if specified
|
|
198
|
+
if self.port is not None:
|
|
199
|
+
if not (1024 <= self.port <= 65535):
|
|
200
|
+
raise ValueError(f"Port must be between 1024-65535, got {self.port}")
|
|
201
|
+
|
|
202
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
203
|
+
"""Convert to dictionary."""
|
|
204
|
+
return asdict(self)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Port range constants
|
|
208
|
+
PROTECTED_PORT_RANGES = [
|
|
209
|
+
(8765, 8785), # Claude MPM services (WebSocket, SocketIO, monitors)
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def is_port_protected(port: int) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Check if a port is in a protected range.
|
|
216
|
+
|
|
217
|
+
WHY: Prevents local-ops-agent from interfering with Claude MPM
|
|
218
|
+
system services.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
port: Port number to check
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
True if port is protected
|
|
225
|
+
"""
|
|
226
|
+
return any(start <= port <= end for start, end in PROTECTED_PORT_RANGES)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
__all__ = [
|
|
230
|
+
"PROTECTED_PORT_RANGES",
|
|
231
|
+
"DeploymentState",
|
|
232
|
+
"ProcessInfo",
|
|
233
|
+
"StartConfig",
|
|
234
|
+
"is_port_protected",
|
|
235
|
+
]
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Restart Management Data Models for Claude MPM Framework
|
|
3
|
+
========================================================
|
|
4
|
+
|
|
5
|
+
WHY: This module defines data structures for auto-restart operations,
|
|
6
|
+
including restart attempts, history tracking, and circuit breaker states.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISION: Uses dataclasses for immutability and type safety. Provides
|
|
9
|
+
serialization methods for state persistence across service restarts.
|
|
10
|
+
|
|
11
|
+
ARCHITECTURE:
|
|
12
|
+
- CircuitBreakerState: Enum of circuit breaker states
|
|
13
|
+
- RestartAttempt: Single restart attempt record
|
|
14
|
+
- RestartHistory: Complete restart history for a deployment
|
|
15
|
+
- RestartConfig: Configuration for restart policies
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from dataclasses import asdict, dataclass, field
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CircuitBreakerState(Enum):
|
|
25
|
+
"""
|
|
26
|
+
Circuit breaker state for restart management.
|
|
27
|
+
|
|
28
|
+
WHY: Circuit breaker prevents infinite restart loops by blocking
|
|
29
|
+
restarts after repeated failures within a time window.
|
|
30
|
+
|
|
31
|
+
States:
|
|
32
|
+
CLOSED: Normal operation, restarts allowed
|
|
33
|
+
OPEN: Circuit breaker tripped, restarts blocked
|
|
34
|
+
HALF_OPEN: Testing if service recovered (allows one restart)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
CLOSED = "closed"
|
|
38
|
+
OPEN = "open"
|
|
39
|
+
HALF_OPEN = "half_open"
|
|
40
|
+
|
|
41
|
+
def allows_restart(self) -> bool:
|
|
42
|
+
"""Check if state allows restart attempts."""
|
|
43
|
+
return self in (CircuitBreakerState.CLOSED, CircuitBreakerState.HALF_OPEN)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class RestartAttempt:
|
|
48
|
+
"""
|
|
49
|
+
Record of a single restart attempt.
|
|
50
|
+
|
|
51
|
+
WHY: Tracks detailed information about each restart attempt to enable
|
|
52
|
+
debugging and policy decisions (exponential backoff, circuit breaker).
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
attempt_number: Sequential attempt number (1-based)
|
|
56
|
+
deployment_id: Unique deployment identifier
|
|
57
|
+
started_at: When the restart was initiated
|
|
58
|
+
completed_at: When the restart finished (None if in progress)
|
|
59
|
+
success: Whether the restart succeeded
|
|
60
|
+
failure_reason: Optional reason for failure
|
|
61
|
+
backoff_seconds: Backoff time before this attempt
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
attempt_number: int
|
|
65
|
+
deployment_id: str
|
|
66
|
+
started_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
67
|
+
completed_at: Optional[datetime] = None
|
|
68
|
+
success: bool = False
|
|
69
|
+
failure_reason: Optional[str] = None
|
|
70
|
+
backoff_seconds: float = 0.0
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Convert to dictionary for JSON serialization.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary representation with datetime converted to ISO format
|
|
78
|
+
"""
|
|
79
|
+
return {
|
|
80
|
+
"attempt_number": self.attempt_number,
|
|
81
|
+
"deployment_id": self.deployment_id,
|
|
82
|
+
"started_at": self.started_at.isoformat(),
|
|
83
|
+
"completed_at": (
|
|
84
|
+
self.completed_at.isoformat() if self.completed_at else None
|
|
85
|
+
),
|
|
86
|
+
"success": self.success,
|
|
87
|
+
"failure_reason": self.failure_reason,
|
|
88
|
+
"backoff_seconds": self.backoff_seconds,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RestartAttempt":
|
|
93
|
+
"""
|
|
94
|
+
Create RestartAttempt from dictionary.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
data: Dictionary from JSON deserialization
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
RestartAttempt instance
|
|
101
|
+
"""
|
|
102
|
+
# Convert ISO strings to datetime
|
|
103
|
+
if isinstance(data.get("started_at"), str):
|
|
104
|
+
data["started_at"] = datetime.fromisoformat(data["started_at"])
|
|
105
|
+
|
|
106
|
+
if data.get("completed_at") and isinstance(data["completed_at"], str):
|
|
107
|
+
data["completed_at"] = datetime.fromisoformat(data["completed_at"])
|
|
108
|
+
|
|
109
|
+
return cls(**data)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class RestartHistory:
|
|
114
|
+
"""
|
|
115
|
+
Complete restart history for a deployment.
|
|
116
|
+
|
|
117
|
+
WHY: Maintains restart attempt history, circuit breaker state, and
|
|
118
|
+
failure window tracking to enable intelligent restart policies.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
deployment_id: Unique deployment identifier
|
|
122
|
+
attempts: List of restart attempts (newest first)
|
|
123
|
+
circuit_breaker_state: Current circuit breaker state
|
|
124
|
+
last_failure_window_start: Start of current failure window
|
|
125
|
+
failure_count_in_window: Number of failures in current window
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
deployment_id: str
|
|
129
|
+
attempts: List[RestartAttempt] = field(default_factory=list)
|
|
130
|
+
circuit_breaker_state: CircuitBreakerState = CircuitBreakerState.CLOSED
|
|
131
|
+
last_failure_window_start: Optional[datetime] = None
|
|
132
|
+
failure_count_in_window: int = 0
|
|
133
|
+
|
|
134
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
135
|
+
"""
|
|
136
|
+
Convert to dictionary for JSON serialization.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary representation
|
|
140
|
+
"""
|
|
141
|
+
return {
|
|
142
|
+
"deployment_id": self.deployment_id,
|
|
143
|
+
"attempts": [attempt.to_dict() for attempt in self.attempts],
|
|
144
|
+
"circuit_breaker_state": self.circuit_breaker_state.value,
|
|
145
|
+
"last_failure_window_start": (
|
|
146
|
+
self.last_failure_window_start.isoformat()
|
|
147
|
+
if self.last_failure_window_start
|
|
148
|
+
else None
|
|
149
|
+
),
|
|
150
|
+
"failure_count_in_window": self.failure_count_in_window,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RestartHistory":
|
|
155
|
+
"""
|
|
156
|
+
Create RestartHistory from dictionary.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
data: Dictionary from JSON deserialization
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
RestartHistory instance
|
|
163
|
+
"""
|
|
164
|
+
# Convert circuit breaker state string to enum
|
|
165
|
+
if isinstance(data.get("circuit_breaker_state"), str):
|
|
166
|
+
data["circuit_breaker_state"] = CircuitBreakerState(
|
|
167
|
+
data["circuit_breaker_state"]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Convert ISO string to datetime
|
|
171
|
+
if data.get("last_failure_window_start") and isinstance(
|
|
172
|
+
data["last_failure_window_start"], str
|
|
173
|
+
):
|
|
174
|
+
data["last_failure_window_start"] = datetime.fromisoformat(
|
|
175
|
+
data["last_failure_window_start"]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Convert attempt dicts to RestartAttempt objects
|
|
179
|
+
if isinstance(data.get("attempts"), list):
|
|
180
|
+
data["attempts"] = [
|
|
181
|
+
(
|
|
182
|
+
RestartAttempt.from_dict(attempt)
|
|
183
|
+
if isinstance(attempt, dict)
|
|
184
|
+
else attempt
|
|
185
|
+
)
|
|
186
|
+
for attempt in data["attempts"]
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
return cls(**data)
|
|
190
|
+
|
|
191
|
+
def get_latest_attempt(self) -> Optional[RestartAttempt]:
|
|
192
|
+
"""
|
|
193
|
+
Get the most recent restart attempt.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Latest RestartAttempt if any, None otherwise
|
|
197
|
+
"""
|
|
198
|
+
return self.attempts[0] if self.attempts else None
|
|
199
|
+
|
|
200
|
+
def get_attempt_count(self) -> int:
|
|
201
|
+
"""
|
|
202
|
+
Get total number of restart attempts.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Number of attempts
|
|
206
|
+
"""
|
|
207
|
+
return len(self.attempts)
|
|
208
|
+
|
|
209
|
+
def get_consecutive_failures(self) -> int:
|
|
210
|
+
"""
|
|
211
|
+
Get number of consecutive failures from the most recent attempt.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Count of consecutive failures
|
|
215
|
+
"""
|
|
216
|
+
count = 0
|
|
217
|
+
for attempt in self.attempts:
|
|
218
|
+
if not attempt.success:
|
|
219
|
+
count += 1
|
|
220
|
+
else:
|
|
221
|
+
break
|
|
222
|
+
return count
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class RestartConfig:
|
|
227
|
+
"""
|
|
228
|
+
Configuration for restart policies.
|
|
229
|
+
|
|
230
|
+
WHY: Encapsulates all restart policy parameters to enable flexible
|
|
231
|
+
configuration and testing.
|
|
232
|
+
|
|
233
|
+
Attributes:
|
|
234
|
+
max_attempts: Maximum restart attempts before giving up
|
|
235
|
+
initial_backoff_seconds: Initial backoff time (doubles each attempt)
|
|
236
|
+
max_backoff_seconds: Maximum backoff cap (default: 5 minutes)
|
|
237
|
+
backoff_multiplier: Backoff multiplier for exponential backoff
|
|
238
|
+
circuit_breaker_threshold: Failures to trip circuit breaker
|
|
239
|
+
circuit_breaker_window_seconds: Time window for failure counting
|
|
240
|
+
circuit_breaker_reset_seconds: Cooldown before resetting breaker
|
|
241
|
+
health_check_timeout_seconds: Time to wait for health check after restart
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
max_attempts: int = 5
|
|
245
|
+
initial_backoff_seconds: float = 2.0
|
|
246
|
+
max_backoff_seconds: float = 300.0 # 5 minutes
|
|
247
|
+
backoff_multiplier: float = 2.0
|
|
248
|
+
circuit_breaker_threshold: int = 3 # failures to trip breaker
|
|
249
|
+
circuit_breaker_window_seconds: int = 300 # 5 minute window
|
|
250
|
+
circuit_breaker_reset_seconds: int = 600 # 10 minute cooldown
|
|
251
|
+
health_check_timeout_seconds: int = 30 # wait for health check
|
|
252
|
+
|
|
253
|
+
def __post_init__(self):
|
|
254
|
+
"""Validate configuration after initialization."""
|
|
255
|
+
if self.max_attempts < 1:
|
|
256
|
+
raise ValueError("max_attempts must be >= 1")
|
|
257
|
+
|
|
258
|
+
if self.initial_backoff_seconds < 0:
|
|
259
|
+
raise ValueError("initial_backoff_seconds must be >= 0")
|
|
260
|
+
|
|
261
|
+
if self.max_backoff_seconds < self.initial_backoff_seconds:
|
|
262
|
+
raise ValueError("max_backoff_seconds must be >= initial_backoff_seconds")
|
|
263
|
+
|
|
264
|
+
if self.backoff_multiplier < 1.0:
|
|
265
|
+
raise ValueError("backoff_multiplier must be >= 1.0")
|
|
266
|
+
|
|
267
|
+
if self.circuit_breaker_threshold < 1:
|
|
268
|
+
raise ValueError("circuit_breaker_threshold must be >= 1")
|
|
269
|
+
|
|
270
|
+
if self.circuit_breaker_window_seconds < 1:
|
|
271
|
+
raise ValueError("circuit_breaker_window_seconds must be >= 1")
|
|
272
|
+
|
|
273
|
+
if self.circuit_breaker_reset_seconds < 1:
|
|
274
|
+
raise ValueError("circuit_breaker_reset_seconds must be >= 1")
|
|
275
|
+
|
|
276
|
+
if self.health_check_timeout_seconds < 1:
|
|
277
|
+
raise ValueError("health_check_timeout_seconds must be >= 1")
|
|
278
|
+
|
|
279
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
280
|
+
"""Convert to dictionary."""
|
|
281
|
+
return asdict(self)
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RestartConfig":
|
|
285
|
+
"""
|
|
286
|
+
Create RestartConfig from dictionary.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
data: Dictionary from JSON deserialization
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
RestartConfig instance
|
|
293
|
+
"""
|
|
294
|
+
return cls(**data)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
__all__ = [
|
|
298
|
+
"CircuitBreakerState",
|
|
299
|
+
"RestartAttempt",
|
|
300
|
+
"RestartConfig",
|
|
301
|
+
"RestartHistory",
|
|
302
|
+
]
|