claude-mpm 4.15.2__py3-none-any.whl → 4.20.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +255 -23
- claude_mpm/agents/PM_INSTRUCTIONS.md +40 -0
- claude_mpm/agents/agent_loader.py +4 -4
- 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 +216 -37
- 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 +9 -2
- claude_mpm/agents/templates/prompt-engineer.json +5 -1
- claude_mpm/agents/templates/python_engineer.json +19 -4
- 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 +23 -8
- 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/commands/__init__.py +2 -0
- claude_mpm/cli/commands/configure.py +164 -16
- claude_mpm/cli/commands/configure_agent_display.py +6 -6
- claude_mpm/cli/commands/configure_behavior_manager.py +8 -8
- claude_mpm/cli/commands/configure_navigation.py +20 -18
- claude_mpm/cli/commands/configure_startup_manager.py +14 -14
- claude_mpm/cli/commands/configure_template_editor.py +8 -8
- claude_mpm/cli/commands/mpm_init.py +109 -24
- claude_mpm/cli/commands/skills.py +434 -0
- claude_mpm/cli/executor.py +2 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/startup.py +83 -0
- claude_mpm/commands/mpm-auto-configure.md +52 -0
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-init.md +112 -6
- 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/constants.py +12 -0
- claude_mpm/core/config.py +42 -0
- claude_mpm/core/enums.py +18 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/types.py +2 -9
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +8 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +1 -1
- 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/local_template_deployment.py +1 -1
- 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/recommender.py +47 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +87 -0
- claude_mpm/services/cli/session_resume_helper.py +352 -0
- claude_mpm/services/core/models/health.py +1 -28
- claude_mpm/services/core/path_resolver.py +1 -1
- 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 +1 -1
- claude_mpm/services/local_ops/crash_detector.py +1 -1
- claude_mpm/services/local_ops/health_checks/http_check.py +2 -1
- claude_mpm/services/local_ops/health_checks/process_check.py +2 -1
- claude_mpm/services/local_ops/health_checks/resource_check.py +2 -1
- claude_mpm/services/local_ops/health_manager.py +1 -1
- claude_mpm/services/local_ops/restart_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +7 -131
- 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 +10 -0
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +7 -7
- claude_mpm/services/unified/deployment_strategies/local.py +1 -1
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +42 -0
- claude_mpm/skills/agent_skills_injector.py +331 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -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/collaboration/brainstorming/SKILL.md +75 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +184 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +107 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/code-reviewer.md +146 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +118 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +177 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +175 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/common-failures.md +213 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +314 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +227 -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/main/artifacts-builder/SKILL.md +74 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +32 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +328 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +150 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +372 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +209 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +302 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +111 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +65 -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/testing/condition-based-waiting/SKILL.md +123 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +304 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +96 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +40 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +107 -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/skills/skills_registry.py +351 -0
- claude_mpm/skills/skills_service.py +730 -0
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/METADATA +211 -33
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/RECORD +195 -115
- claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
- 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-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/top_level.txt +0 -0
|
@@ -6,8 +6,9 @@ Monitors individual process health including CPU, memory, file descriptors, and
|
|
|
6
6
|
from typing import List
|
|
7
7
|
|
|
8
8
|
from claude_mpm.core.constants import ResourceLimits, TimeoutConfig
|
|
9
|
+
from claude_mpm.core.enums import HealthStatus
|
|
9
10
|
|
|
10
|
-
from .base import BaseMonitoringService, HealthMetric
|
|
11
|
+
from .base import BaseMonitoringService, HealthMetric
|
|
11
12
|
|
|
12
13
|
try:
|
|
13
14
|
import psutil
|
|
@@ -66,7 +67,7 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
66
67
|
HealthMetric(
|
|
67
68
|
name="psutil_availability",
|
|
68
69
|
value=False,
|
|
69
|
-
status=HealthStatus.
|
|
70
|
+
status=HealthStatus.DEGRADED,
|
|
70
71
|
message="psutil not available for process monitoring",
|
|
71
72
|
)
|
|
72
73
|
)
|
|
@@ -77,7 +78,7 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
77
78
|
HealthMetric(
|
|
78
79
|
name="process_exists",
|
|
79
80
|
value=False,
|
|
80
|
-
status=HealthStatus.
|
|
81
|
+
status=HealthStatus.UNHEALTHY,
|
|
81
82
|
message=f"Process {self.pid} not found",
|
|
82
83
|
)
|
|
83
84
|
)
|
|
@@ -90,7 +91,7 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
90
91
|
HealthMetric(
|
|
91
92
|
name="process_exists",
|
|
92
93
|
value=False,
|
|
93
|
-
status=HealthStatus.
|
|
94
|
+
status=HealthStatus.UNHEALTHY,
|
|
94
95
|
message=f"Process {self.pid} is no longer running",
|
|
95
96
|
)
|
|
96
97
|
)
|
|
@@ -119,7 +120,7 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
119
120
|
HealthMetric(
|
|
120
121
|
name="process_exists",
|
|
121
122
|
value=False,
|
|
122
|
-
status=HealthStatus.
|
|
123
|
+
status=HealthStatus.UNHEALTHY,
|
|
123
124
|
message=f"Process {self.pid} no longer exists",
|
|
124
125
|
)
|
|
125
126
|
)
|
|
@@ -153,7 +154,7 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
153
154
|
status=(
|
|
154
155
|
HealthStatus.HEALTHY
|
|
155
156
|
if process_healthy
|
|
156
|
-
else HealthStatus.
|
|
157
|
+
else HealthStatus.UNHEALTHY
|
|
157
158
|
),
|
|
158
159
|
message=f"Process status: {status}",
|
|
159
160
|
)
|
|
@@ -179,9 +180,9 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
179
180
|
cpu_status = HealthStatus.HEALTHY
|
|
180
181
|
if cpu_percent > self.cpu_threshold:
|
|
181
182
|
cpu_status = (
|
|
182
|
-
HealthStatus.
|
|
183
|
+
HealthStatus.DEGRADED
|
|
183
184
|
if cpu_percent < self.cpu_threshold * 1.2
|
|
184
|
-
else HealthStatus.
|
|
185
|
+
else HealthStatus.UNHEALTHY
|
|
185
186
|
)
|
|
186
187
|
|
|
187
188
|
metrics.append(
|
|
@@ -213,9 +214,9 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
213
214
|
memory_status = HealthStatus.HEALTHY
|
|
214
215
|
if memory_mb > self.memory_threshold_mb:
|
|
215
216
|
memory_status = (
|
|
216
|
-
HealthStatus.
|
|
217
|
+
HealthStatus.DEGRADED
|
|
217
218
|
if memory_mb < self.memory_threshold_mb * 1.2
|
|
218
|
-
else HealthStatus.
|
|
219
|
+
else HealthStatus.UNHEALTHY
|
|
219
220
|
)
|
|
220
221
|
|
|
221
222
|
metrics.append(
|
|
@@ -256,9 +257,9 @@ class ProcessHealthService(BaseMonitoringService):
|
|
|
256
257
|
fd_status = HealthStatus.HEALTHY
|
|
257
258
|
if fd_count > self.fd_threshold:
|
|
258
259
|
fd_status = (
|
|
259
|
-
HealthStatus.
|
|
260
|
+
HealthStatus.DEGRADED
|
|
260
261
|
if fd_count < self.fd_threshold * 1.2
|
|
261
|
-
else HealthStatus.
|
|
262
|
+
else HealthStatus.UNHEALTHY
|
|
262
263
|
)
|
|
263
264
|
|
|
264
265
|
metrics.append(
|
|
@@ -5,7 +5,8 @@ Monitors system-wide resource usage including CPU, memory, and disk utilization.
|
|
|
5
5
|
|
|
6
6
|
from typing import Dict, List, Optional
|
|
7
7
|
|
|
8
|
-
from .
|
|
8
|
+
from ....core.enums import HealthStatus
|
|
9
|
+
from .base import BaseMonitoringService, HealthMetric
|
|
9
10
|
|
|
10
11
|
try:
|
|
11
12
|
import psutil
|
|
@@ -53,7 +54,7 @@ class ResourceMonitorService(BaseMonitoringService):
|
|
|
53
54
|
HealthMetric(
|
|
54
55
|
name="psutil_availability",
|
|
55
56
|
value=False,
|
|
56
|
-
status=HealthStatus.
|
|
57
|
+
status=HealthStatus.DEGRADED,
|
|
57
58
|
message="psutil not available for resource monitoring",
|
|
58
59
|
)
|
|
59
60
|
)
|
|
@@ -182,9 +183,9 @@ class ResourceMonitorService(BaseMonitoringService):
|
|
|
182
183
|
# Load is concerning if > cpu_count
|
|
183
184
|
load_status = HealthStatus.HEALTHY
|
|
184
185
|
if load1 > cpu_count:
|
|
185
|
-
load_status = HealthStatus.
|
|
186
|
+
load_status = HealthStatus.DEGRADED
|
|
186
187
|
if load1 > cpu_count * 1.5:
|
|
187
|
-
load_status = HealthStatus.
|
|
188
|
+
load_status = HealthStatus.UNHEALTHY
|
|
188
189
|
|
|
189
190
|
metrics.append(
|
|
190
191
|
HealthMetric(
|
|
@@ -220,8 +221,8 @@ class ResourceMonitorService(BaseMonitoringService):
|
|
|
220
221
|
if value < threshold:
|
|
221
222
|
return HealthStatus.HEALTHY
|
|
222
223
|
if value < threshold * 1.1: # 10% above threshold
|
|
223
|
-
return HealthStatus.
|
|
224
|
-
return HealthStatus.
|
|
224
|
+
return HealthStatus.DEGRADED
|
|
225
|
+
return HealthStatus.UNHEALTHY
|
|
225
226
|
|
|
226
227
|
def get_resource_summary(self) -> Optional[Dict[str, float]]:
|
|
227
228
|
"""Get quick resource summary without full health check.
|
|
@@ -6,7 +6,8 @@ Monitors service-specific metrics like client connections, event processing, and
|
|
|
6
6
|
import time
|
|
7
7
|
from typing import Any, Dict, List
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from ....core.enums import HealthStatus
|
|
10
|
+
from .base import BaseMonitoringService, HealthMetric
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class ServiceHealthService(BaseMonitoringService):
|
|
@@ -79,9 +80,9 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
79
80
|
|
|
80
81
|
# Determine status based on thresholds
|
|
81
82
|
if client_count > self.max_clients:
|
|
82
|
-
client_status = HealthStatus.
|
|
83
|
+
client_status = HealthStatus.UNHEALTHY
|
|
83
84
|
elif client_count > self.max_clients * 0.8:
|
|
84
|
-
client_status = HealthStatus.
|
|
85
|
+
client_status = HealthStatus.DEGRADED
|
|
85
86
|
else:
|
|
86
87
|
client_status = HealthStatus.HEALTHY
|
|
87
88
|
|
|
@@ -129,7 +130,7 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
129
130
|
# Determine status based on rate
|
|
130
131
|
rate_status = HealthStatus.HEALTHY
|
|
131
132
|
if event_rate == 0 and events_processed > 0:
|
|
132
|
-
rate_status = HealthStatus.
|
|
133
|
+
rate_status = HealthStatus.DEGRADED # Processing stopped
|
|
133
134
|
|
|
134
135
|
metrics.append(
|
|
135
136
|
HealthMetric(
|
|
@@ -157,9 +158,9 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
157
158
|
queue_size = self.service_stats["event_queue_size"]
|
|
158
159
|
queue_status = HealthStatus.HEALTHY
|
|
159
160
|
if queue_size > 1000:
|
|
160
|
-
queue_status = HealthStatus.
|
|
161
|
+
queue_status = HealthStatus.DEGRADED
|
|
161
162
|
if queue_size > 5000:
|
|
162
|
-
queue_status = HealthStatus.
|
|
163
|
+
queue_status = HealthStatus.UNHEALTHY
|
|
163
164
|
|
|
164
165
|
metrics.append(
|
|
165
166
|
HealthMetric(
|
|
@@ -191,9 +192,9 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
191
192
|
|
|
192
193
|
# Determine status based on rate
|
|
193
194
|
if error_rate > self.max_error_rate:
|
|
194
|
-
error_status = HealthStatus.
|
|
195
|
+
error_status = HealthStatus.UNHEALTHY
|
|
195
196
|
elif error_rate > self.max_error_rate * 0.5:
|
|
196
|
-
error_status = HealthStatus.
|
|
197
|
+
error_status = HealthStatus.DEGRADED
|
|
197
198
|
else:
|
|
198
199
|
error_status = HealthStatus.HEALTHY
|
|
199
200
|
|
|
@@ -213,7 +214,7 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
213
214
|
name="total_errors",
|
|
214
215
|
value=errors,
|
|
215
216
|
status=(
|
|
216
|
-
HealthStatus.HEALTHY if errors == 0 else HealthStatus.
|
|
217
|
+
HealthStatus.HEALTHY if errors == 0 else HealthStatus.DEGRADED
|
|
217
218
|
),
|
|
218
219
|
)
|
|
219
220
|
)
|
|
@@ -228,7 +229,7 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
228
229
|
status=(
|
|
229
230
|
HealthStatus.HEALTHY
|
|
230
231
|
if recent_errors == 0
|
|
231
|
-
else HealthStatus.
|
|
232
|
+
else HealthStatus.DEGRADED
|
|
232
233
|
),
|
|
233
234
|
)
|
|
234
235
|
)
|
|
@@ -263,9 +264,9 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
263
264
|
|
|
264
265
|
# Determine status based on staleness
|
|
265
266
|
if time_since_activity > self.stale_activity_seconds * 2:
|
|
266
|
-
activity_status = HealthStatus.
|
|
267
|
+
activity_status = HealthStatus.UNHEALTHY
|
|
267
268
|
elif time_since_activity > self.stale_activity_seconds:
|
|
268
|
-
activity_status = HealthStatus.
|
|
269
|
+
activity_status = HealthStatus.DEGRADED
|
|
269
270
|
else:
|
|
270
271
|
activity_status = HealthStatus.HEALTHY
|
|
271
272
|
|
|
@@ -282,7 +283,7 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
282
283
|
HealthMetric(
|
|
283
284
|
name="time_since_last_activity",
|
|
284
285
|
value=-1,
|
|
285
|
-
status=HealthStatus.
|
|
286
|
+
status=HealthStatus.DEGRADED,
|
|
286
287
|
message="No last activity recorded",
|
|
287
288
|
)
|
|
288
289
|
)
|
|
@@ -307,9 +308,9 @@ class ServiceHealthService(BaseMonitoringService):
|
|
|
307
308
|
|
|
308
309
|
# Determine status based on response time
|
|
309
310
|
if avg_time > 1000: # > 1 second
|
|
310
|
-
time_status = HealthStatus.
|
|
311
|
+
time_status = HealthStatus.UNHEALTHY
|
|
311
312
|
elif avg_time > 500: # > 500ms
|
|
312
|
-
time_status = HealthStatus.
|
|
313
|
+
time_status = HealthStatus.DEGRADED
|
|
313
314
|
else:
|
|
314
315
|
time_status = HealthStatus.HEALTHY
|
|
315
316
|
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Resume Log Generator Service.
|
|
2
|
+
|
|
3
|
+
Automatically generates session resume logs when approaching or hitting token limits.
|
|
4
|
+
Integrates with session management and response tracking infrastructure.
|
|
5
|
+
|
|
6
|
+
Triggers:
|
|
7
|
+
- model_context_window_exceeded (stop_reason)
|
|
8
|
+
- Manual pause command
|
|
9
|
+
- 95% token threshold reached
|
|
10
|
+
- Session end with high token usage (>85%)
|
|
11
|
+
|
|
12
|
+
Design Principles:
|
|
13
|
+
- Atomic file operations (via state_storage)
|
|
14
|
+
- Non-blocking generation
|
|
15
|
+
- Graceful degradation if generation fails
|
|
16
|
+
- Integration with existing session state
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
from claude_mpm.core.logging_utils import get_logger
|
|
24
|
+
from claude_mpm.models.resume_log import ContextMetrics, ResumeLog
|
|
25
|
+
from claude_mpm.storage.state_storage import StateStorage
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ResumeLogGenerator:
|
|
31
|
+
"""Service for generating session resume logs."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
storage_dir: Optional[Path] = None,
|
|
36
|
+
config: Optional[Dict[str, Any]] = None,
|
|
37
|
+
):
|
|
38
|
+
"""Initialize resume log generator.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
storage_dir: Directory for resume logs (default: .claude-mpm/resume-logs)
|
|
42
|
+
config: Configuration dictionary
|
|
43
|
+
"""
|
|
44
|
+
self.storage_dir = storage_dir or Path.home() / ".claude-mpm" / "resume-logs"
|
|
45
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# State storage for atomic writes
|
|
48
|
+
self.state_storage = StateStorage(
|
|
49
|
+
storage_dir=self.storage_dir.parent / "storage"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Configuration
|
|
53
|
+
self.config = config or {}
|
|
54
|
+
self.enabled = (
|
|
55
|
+
self.config.get("context_management", {})
|
|
56
|
+
.get("resume_logs", {})
|
|
57
|
+
.get("enabled", True)
|
|
58
|
+
)
|
|
59
|
+
self.auto_generate = (
|
|
60
|
+
self.config.get("context_management", {})
|
|
61
|
+
.get("resume_logs", {})
|
|
62
|
+
.get("auto_generate", True)
|
|
63
|
+
)
|
|
64
|
+
self.max_tokens = (
|
|
65
|
+
self.config.get("context_management", {})
|
|
66
|
+
.get("resume_logs", {})
|
|
67
|
+
.get("max_tokens", 10000)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Trigger thresholds
|
|
71
|
+
thresholds = self.config.get("context_management", {}).get("thresholds", {})
|
|
72
|
+
self.threshold_caution = thresholds.get("caution", 0.70)
|
|
73
|
+
self.threshold_warning = thresholds.get("warning", 0.85)
|
|
74
|
+
self.threshold_critical = thresholds.get("critical", 0.95)
|
|
75
|
+
|
|
76
|
+
logger.info(
|
|
77
|
+
f"ResumeLogGenerator initialized (enabled={self.enabled}, auto_generate={self.auto_generate})"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def should_generate(
|
|
81
|
+
self,
|
|
82
|
+
stop_reason: Optional[str] = None,
|
|
83
|
+
token_usage_pct: Optional[float] = None,
|
|
84
|
+
manual_trigger: bool = False,
|
|
85
|
+
) -> bool:
|
|
86
|
+
"""Determine if resume log should be generated.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
stop_reason: Claude API stop_reason
|
|
90
|
+
token_usage_pct: Current token usage percentage (0.0-1.0)
|
|
91
|
+
manual_trigger: Manual pause/stop command
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if resume log should be generated
|
|
95
|
+
"""
|
|
96
|
+
if not self.enabled or not self.auto_generate:
|
|
97
|
+
return manual_trigger # Only generate on manual trigger if auto is disabled
|
|
98
|
+
|
|
99
|
+
# Trigger conditions
|
|
100
|
+
triggers = [
|
|
101
|
+
stop_reason == "max_tokens",
|
|
102
|
+
stop_reason == "model_context_window_exceeded",
|
|
103
|
+
manual_trigger,
|
|
104
|
+
token_usage_pct and token_usage_pct >= self.threshold_critical,
|
|
105
|
+
token_usage_pct
|
|
106
|
+
and token_usage_pct >= self.threshold_warning, # Generate at 85% too
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
should_gen = any(triggers)
|
|
110
|
+
|
|
111
|
+
if should_gen:
|
|
112
|
+
reason = "unknown"
|
|
113
|
+
if stop_reason:
|
|
114
|
+
reason = f"stop_reason={stop_reason}"
|
|
115
|
+
elif manual_trigger:
|
|
116
|
+
reason = "manual_trigger"
|
|
117
|
+
elif token_usage_pct:
|
|
118
|
+
reason = f"token_usage={token_usage_pct:.1%}"
|
|
119
|
+
|
|
120
|
+
logger.info(f"Resume log generation triggered: {reason}")
|
|
121
|
+
|
|
122
|
+
return should_gen
|
|
123
|
+
|
|
124
|
+
def generate_from_session_state(
|
|
125
|
+
self,
|
|
126
|
+
session_id: str,
|
|
127
|
+
session_state: Dict[str, Any],
|
|
128
|
+
stop_reason: Optional[str] = None,
|
|
129
|
+
) -> Optional[ResumeLog]:
|
|
130
|
+
"""Generate resume log from session state data.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
session_id: Current session ID
|
|
134
|
+
session_state: Session state dictionary
|
|
135
|
+
stop_reason: Claude API stop_reason
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Generated ResumeLog or None if generation failed
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
# Extract context metrics
|
|
142
|
+
context_data = session_state.get("context_metrics", {})
|
|
143
|
+
context_metrics = ContextMetrics(
|
|
144
|
+
total_budget=context_data.get("total_budget", 200000),
|
|
145
|
+
used_tokens=context_data.get("used_tokens", 0),
|
|
146
|
+
remaining_tokens=context_data.get("remaining_tokens", 0),
|
|
147
|
+
percentage_used=context_data.get("percentage_used", 0.0),
|
|
148
|
+
stop_reason=stop_reason or context_data.get("stop_reason"),
|
|
149
|
+
model=context_data.get("model", "claude-sonnet-4.5"),
|
|
150
|
+
session_id=session_id,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Extract content from session state
|
|
154
|
+
mission_summary = session_state.get("mission_summary", "")
|
|
155
|
+
accomplishments = session_state.get("accomplishments", [])
|
|
156
|
+
key_findings = session_state.get("key_findings", [])
|
|
157
|
+
decisions_made = session_state.get("decisions_made", [])
|
|
158
|
+
next_steps = session_state.get("next_steps", [])
|
|
159
|
+
critical_context = session_state.get("critical_context", {})
|
|
160
|
+
|
|
161
|
+
# Extract metadata
|
|
162
|
+
files_modified = session_state.get("files_modified", [])
|
|
163
|
+
agents_used = session_state.get("agents_used", {})
|
|
164
|
+
errors_encountered = session_state.get("errors_encountered", [])
|
|
165
|
+
warnings = session_state.get("warnings", [])
|
|
166
|
+
|
|
167
|
+
# Create resume log
|
|
168
|
+
resume_log = ResumeLog(
|
|
169
|
+
session_id=session_id,
|
|
170
|
+
previous_session_id=session_state.get("previous_session_id"),
|
|
171
|
+
context_metrics=context_metrics,
|
|
172
|
+
mission_summary=mission_summary,
|
|
173
|
+
accomplishments=accomplishments,
|
|
174
|
+
key_findings=key_findings,
|
|
175
|
+
decisions_made=decisions_made,
|
|
176
|
+
next_steps=next_steps,
|
|
177
|
+
critical_context=critical_context,
|
|
178
|
+
files_modified=files_modified,
|
|
179
|
+
agents_used=agents_used,
|
|
180
|
+
errors_encountered=errors_encountered,
|
|
181
|
+
warnings=warnings,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
logger.info(f"Generated resume log for session {session_id}")
|
|
185
|
+
return resume_log
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(
|
|
189
|
+
f"Failed to generate resume log from session state: {e}", exc_info=True
|
|
190
|
+
)
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def generate_from_todo_list(
|
|
194
|
+
self,
|
|
195
|
+
session_id: str,
|
|
196
|
+
todos: List[Dict[str, Any]],
|
|
197
|
+
context_metrics: Optional[ContextMetrics] = None,
|
|
198
|
+
) -> Optional[ResumeLog]:
|
|
199
|
+
"""Generate resume log from TODO list.
|
|
200
|
+
|
|
201
|
+
Useful when session state is minimal but TODO list has rich information.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
session_id: Current session ID
|
|
205
|
+
todos: TODO list items
|
|
206
|
+
context_metrics: Context metrics (optional)
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Generated ResumeLog or None if generation failed
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
# Categorize todos
|
|
213
|
+
completed = [t for t in todos if t.get("status") == "completed"]
|
|
214
|
+
in_progress = [t for t in todos if t.get("status") == "in_progress"]
|
|
215
|
+
pending = [t for t in todos if t.get("status") == "pending"]
|
|
216
|
+
|
|
217
|
+
# Build accomplishments from completed tasks
|
|
218
|
+
accomplishments = [f"✓ {task['content']}" for task in completed]
|
|
219
|
+
|
|
220
|
+
# Build next steps from in-progress and pending
|
|
221
|
+
next_steps = []
|
|
222
|
+
for task in in_progress:
|
|
223
|
+
next_steps.append(f"[IN PROGRESS] {task['content']}")
|
|
224
|
+
for task in pending:
|
|
225
|
+
next_steps.append(f"[PENDING] {task['content']}")
|
|
226
|
+
|
|
227
|
+
# Create mission summary
|
|
228
|
+
mission_summary = f"Working on {len(todos)} tasks: {len(completed)} completed, {len(in_progress)} in progress, {len(pending)} pending."
|
|
229
|
+
|
|
230
|
+
# Use provided context metrics or create default
|
|
231
|
+
if context_metrics is None:
|
|
232
|
+
context_metrics = ContextMetrics(session_id=session_id)
|
|
233
|
+
|
|
234
|
+
# Create resume log
|
|
235
|
+
resume_log = ResumeLog(
|
|
236
|
+
session_id=session_id,
|
|
237
|
+
context_metrics=context_metrics,
|
|
238
|
+
mission_summary=mission_summary,
|
|
239
|
+
accomplishments=accomplishments,
|
|
240
|
+
next_steps=next_steps,
|
|
241
|
+
critical_context={
|
|
242
|
+
"total_tasks": len(todos),
|
|
243
|
+
"completed_tasks": len(completed),
|
|
244
|
+
"in_progress_tasks": len(in_progress),
|
|
245
|
+
"pending_tasks": len(pending),
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
logger.info(f"Generated resume log from TODO list for session {session_id}")
|
|
250
|
+
return resume_log
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(
|
|
254
|
+
f"Failed to generate resume log from TODO list: {e}", exc_info=True
|
|
255
|
+
)
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
def save_resume_log(self, resume_log: ResumeLog) -> Optional[Path]:
|
|
259
|
+
"""Save resume log to storage.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
resume_log: ResumeLog instance to save
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Path to saved file or None if save failed
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
# Save as markdown (primary format)
|
|
269
|
+
md_path = resume_log.save(storage_dir=self.storage_dir)
|
|
270
|
+
|
|
271
|
+
# Also save as JSON for programmatic access
|
|
272
|
+
json_path = self.storage_dir / f"session-{resume_log.session_id}.json"
|
|
273
|
+
self.state_storage.write_json(
|
|
274
|
+
data=resume_log.to_dict(),
|
|
275
|
+
file_path=json_path,
|
|
276
|
+
atomic=True,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
logger.info(f"Resume log saved: {md_path}")
|
|
280
|
+
return md_path
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.error(f"Failed to save resume log: {e}", exc_info=True)
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
def load_resume_log(self, session_id: str) -> Optional[str]:
|
|
287
|
+
"""Load resume log markdown content.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
session_id: Session ID to load
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Markdown content or None if not found
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
md_path = self.storage_dir / f"session-{session_id}.md"
|
|
297
|
+
|
|
298
|
+
if not md_path.exists():
|
|
299
|
+
logger.debug(f"Resume log not found for session {session_id}")
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
content = md_path.read_text(encoding="utf-8")
|
|
303
|
+
logger.info(f"Loaded resume log for session {session_id}")
|
|
304
|
+
return content
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Failed to load resume log: {e}", exc_info=True)
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
def list_resume_logs(self) -> List[Dict[str, Any]]:
|
|
311
|
+
"""List all available resume logs.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of resume log metadata
|
|
315
|
+
"""
|
|
316
|
+
try:
|
|
317
|
+
logs = []
|
|
318
|
+
|
|
319
|
+
for md_file in self.storage_dir.glob("session-*.md"):
|
|
320
|
+
# Extract session ID from filename
|
|
321
|
+
session_id = md_file.stem.replace("session-", "")
|
|
322
|
+
|
|
323
|
+
# Check if JSON metadata exists
|
|
324
|
+
json_file = md_file.with_suffix(".json")
|
|
325
|
+
metadata = {}
|
|
326
|
+
if json_file.exists():
|
|
327
|
+
json_data = self.state_storage.read_json(json_file)
|
|
328
|
+
if json_data:
|
|
329
|
+
metadata = {
|
|
330
|
+
"session_id": session_id,
|
|
331
|
+
"created_at": json_data.get("created_at"),
|
|
332
|
+
"previous_session_id": json_data.get("previous_session_id"),
|
|
333
|
+
"context_metrics": json_data.get("context_metrics", {}),
|
|
334
|
+
"file_path": str(md_file),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if metadata:
|
|
338
|
+
logs.append(metadata)
|
|
339
|
+
else:
|
|
340
|
+
# Fallback to file metadata
|
|
341
|
+
logs.append(
|
|
342
|
+
{
|
|
343
|
+
"session_id": session_id,
|
|
344
|
+
"file_path": str(md_file),
|
|
345
|
+
"modified_at": datetime.fromtimestamp(
|
|
346
|
+
md_file.stat().st_mtime, tz=timezone.utc
|
|
347
|
+
).isoformat(),
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Sort by creation time (newest first)
|
|
352
|
+
logs.sort(
|
|
353
|
+
key=lambda x: x.get("created_at", x.get("modified_at", "")),
|
|
354
|
+
reverse=True,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
logger.debug(f"Found {len(logs)} resume logs")
|
|
358
|
+
return logs
|
|
359
|
+
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.error(f"Failed to list resume logs: {e}", exc_info=True)
|
|
362
|
+
return []
|
|
363
|
+
|
|
364
|
+
def cleanup_old_logs(self, keep_count: int = 10) -> int:
|
|
365
|
+
"""Clean up old resume logs, keeping only the most recent.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
keep_count: Number of logs to keep
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Number of logs deleted
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
logs = self.list_resume_logs()
|
|
375
|
+
|
|
376
|
+
if len(logs) <= keep_count:
|
|
377
|
+
logger.debug(
|
|
378
|
+
f"No cleanup needed ({len(logs)} logs <= {keep_count} keep)"
|
|
379
|
+
)
|
|
380
|
+
return 0
|
|
381
|
+
|
|
382
|
+
# Delete old logs
|
|
383
|
+
deleted = 0
|
|
384
|
+
for log in logs[keep_count:]:
|
|
385
|
+
try:
|
|
386
|
+
md_path = Path(log["file_path"])
|
|
387
|
+
json_path = md_path.with_suffix(".json")
|
|
388
|
+
|
|
389
|
+
if md_path.exists():
|
|
390
|
+
md_path.unlink()
|
|
391
|
+
deleted += 1
|
|
392
|
+
|
|
393
|
+
if json_path.exists():
|
|
394
|
+
json_path.unlink()
|
|
395
|
+
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.warning(f"Failed to delete log {log['session_id']}: {e}")
|
|
398
|
+
|
|
399
|
+
logger.info(f"Cleaned up {deleted} old resume logs (kept {keep_count})")
|
|
400
|
+
return deleted
|
|
401
|
+
|
|
402
|
+
except Exception as e:
|
|
403
|
+
logger.error(f"Failed to cleanup old logs: {e}", exc_info=True)
|
|
404
|
+
return 0
|
|
405
|
+
|
|
406
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
407
|
+
"""Get resume log statistics.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Dictionary with statistics
|
|
411
|
+
"""
|
|
412
|
+
try:
|
|
413
|
+
logs = self.list_resume_logs()
|
|
414
|
+
|
|
415
|
+
total_size = 0
|
|
416
|
+
for log in logs:
|
|
417
|
+
path = Path(log["file_path"])
|
|
418
|
+
if path.exists():
|
|
419
|
+
total_size += path.stat().st_size
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
"enabled": self.enabled,
|
|
423
|
+
"auto_generate": self.auto_generate,
|
|
424
|
+
"total_logs": len(logs),
|
|
425
|
+
"storage_dir": str(self.storage_dir),
|
|
426
|
+
"total_size_kb": round(total_size / 1024, 2),
|
|
427
|
+
"thresholds": {
|
|
428
|
+
"caution": f"{self.threshold_caution:.0%}",
|
|
429
|
+
"warning": f"{self.threshold_warning:.0%}",
|
|
430
|
+
"critical": f"{self.threshold_critical:.0%}",
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.error(f"Failed to get stats: {e}", exc_info=True)
|
|
436
|
+
return {
|
|
437
|
+
"enabled": self.enabled,
|
|
438
|
+
"error": str(e),
|
|
439
|
+
}
|
|
@@ -59,6 +59,7 @@ Note: ProcessStatus has been consolidated into ServiceState (core.enums) as of P
|
|
|
59
59
|
"""
|
|
60
60
|
|
|
61
61
|
# Re-export data models and interfaces for convenience
|
|
62
|
+
from claude_mpm.core.enums import HealthStatus
|
|
62
63
|
from claude_mpm.services.core.interfaces.health import (
|
|
63
64
|
IHealthCheck,
|
|
64
65
|
IHealthCheckManager,
|
|
@@ -80,7 +81,6 @@ from claude_mpm.services.core.interfaces.stability import (
|
|
|
80
81
|
from claude_mpm.services.core.models.health import (
|
|
81
82
|
DeploymentHealth,
|
|
82
83
|
HealthCheckResult,
|
|
83
|
-
HealthStatus,
|
|
84
84
|
)
|
|
85
85
|
from claude_mpm.services.core.models.process import (
|
|
86
86
|
PROTECTED_PORT_RANGES,
|