crackerjack 0.33.0__py3-none-any.whl → 0.33.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.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +618 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
+
import typing as t
|
|
4
5
|
import uuid
|
|
5
6
|
from contextlib import suppress
|
|
6
7
|
from pathlib import Path
|
|
@@ -17,7 +18,6 @@ console = Console()
|
|
|
17
18
|
|
|
18
19
|
class JobManager:
|
|
19
20
|
def __init__(self, progress_dir: Path) -> None:
|
|
20
|
-
# Validate and secure the progress directory path
|
|
21
21
|
self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
|
|
22
22
|
self.active_connections: dict[str, set[Any]] = {}
|
|
23
23
|
self.known_jobs: set[str] = set()
|
|
@@ -26,16 +26,13 @@ class JobManager:
|
|
|
26
26
|
self.progress_dir.mkdir(exist_ok=True)
|
|
27
27
|
|
|
28
28
|
def validate_job_id(self, job_id: str) -> bool:
|
|
29
|
-
"""Validate job ID using secure input validator."""
|
|
30
29
|
if not job_id:
|
|
31
30
|
return False
|
|
32
31
|
|
|
33
|
-
# First check if it's a valid UUID
|
|
34
32
|
with suppress(ValueError):
|
|
35
33
|
uuid.UUID(job_id)
|
|
36
34
|
return True
|
|
37
35
|
|
|
38
|
-
# Use secure input validator for additional validation
|
|
39
36
|
result = get_input_validator().validate_job_id(job_id)
|
|
40
37
|
return result.valid
|
|
41
38
|
|
|
@@ -50,47 +47,43 @@ class JobManager:
|
|
|
50
47
|
if not self.active_connections[job_id]:
|
|
51
48
|
del self.active_connections[job_id]
|
|
52
49
|
|
|
53
|
-
async def broadcast_to_job(self, job_id: str, data: dict) -> None:
|
|
50
|
+
async def broadcast_to_job(self, job_id: str, data: dict[str, t.Any]) -> None:
|
|
54
51
|
if job_id not in self.active_connections:
|
|
55
52
|
return
|
|
56
53
|
|
|
57
54
|
timeout_manager = get_timeout_manager()
|
|
58
55
|
connections = self.active_connections[job_id].copy()
|
|
59
56
|
|
|
60
|
-
# Create websocket send tasks
|
|
61
57
|
send_tasks = self._create_broadcast_tasks(connections, timeout_manager, data)
|
|
62
58
|
|
|
63
|
-
# Execute broadcast with timeout handling
|
|
64
59
|
if send_tasks:
|
|
65
60
|
await self._execute_broadcast_tasks(job_id, send_tasks)
|
|
66
61
|
|
|
67
62
|
def _create_broadcast_tasks(
|
|
68
|
-
self, connections: set, timeout_manager, data: dict
|
|
69
|
-
) -> list:
|
|
70
|
-
"""Create tasks for all websocket sends with timeout."""
|
|
63
|
+
self, connections: set[t.Any], timeout_manager: t.Any, data: dict[str, t.Any]
|
|
64
|
+
) -> list[tuple[t.Any, asyncio.Task[t.Any]]]:
|
|
71
65
|
send_tasks = []
|
|
72
66
|
for websocket in connections:
|
|
73
67
|
task = asyncio.create_task(
|
|
74
68
|
timeout_manager.with_timeout(
|
|
75
69
|
"websocket_broadcast",
|
|
76
70
|
websocket.send_json(data),
|
|
77
|
-
timeout=2.0,
|
|
71
|
+
timeout=2.0,
|
|
78
72
|
)
|
|
79
73
|
)
|
|
80
74
|
send_tasks.append((websocket, task))
|
|
81
75
|
return send_tasks
|
|
82
76
|
|
|
83
|
-
async def _execute_broadcast_tasks(
|
|
84
|
-
|
|
77
|
+
async def _execute_broadcast_tasks(
|
|
78
|
+
self, job_id: str, send_tasks: list[t.Any]
|
|
79
|
+
) -> None:
|
|
85
80
|
try:
|
|
86
|
-
# Use asyncio.wait with timeout for batch sending
|
|
87
81
|
done, pending = await asyncio.wait(
|
|
88
82
|
[task for _, task in send_tasks],
|
|
89
|
-
timeout=5.0,
|
|
83
|
+
timeout=5.0,
|
|
90
84
|
return_when=asyncio.ALL_COMPLETED,
|
|
91
85
|
)
|
|
92
86
|
|
|
93
|
-
# Handle completed and pending tasks
|
|
94
87
|
await self._handle_broadcast_results(job_id, send_tasks, done, pending)
|
|
95
88
|
|
|
96
89
|
except Exception as e:
|
|
@@ -98,10 +91,12 @@ class JobManager:
|
|
|
98
91
|
await self._cleanup_failed_broadcast(job_id, send_tasks)
|
|
99
92
|
|
|
100
93
|
async def _handle_broadcast_results(
|
|
101
|
-
self,
|
|
94
|
+
self,
|
|
95
|
+
job_id: str,
|
|
96
|
+
send_tasks: list[t.Any],
|
|
97
|
+
done: set[t.Any],
|
|
98
|
+
pending: set[t.Any],
|
|
102
99
|
) -> None:
|
|
103
|
-
"""Handle results of broadcast tasks."""
|
|
104
|
-
# Cancel any pending tasks and remove failed connections
|
|
105
100
|
for websocket, task in send_tasks:
|
|
106
101
|
if task in pending:
|
|
107
102
|
task.cancel()
|
|
@@ -112,12 +107,12 @@ class JobManager:
|
|
|
112
107
|
except Exception:
|
|
113
108
|
self.remove_connection(job_id, websocket)
|
|
114
109
|
|
|
115
|
-
# Wait for cancelled tasks to complete
|
|
116
110
|
if pending:
|
|
117
111
|
await asyncio.gather(*pending, return_exceptions=True)
|
|
118
112
|
|
|
119
|
-
async def _cleanup_failed_broadcast(
|
|
120
|
-
|
|
113
|
+
async def _cleanup_failed_broadcast(
|
|
114
|
+
self, job_id: str, send_tasks: list[t.Any]
|
|
115
|
+
) -> None:
|
|
121
116
|
for websocket, task in send_tasks:
|
|
122
117
|
if not task.done():
|
|
123
118
|
task.cancel()
|
|
@@ -127,7 +122,7 @@ class JobManager:
|
|
|
127
122
|
if not self.progress_dir.exists():
|
|
128
123
|
return None
|
|
129
124
|
|
|
130
|
-
progress_files = list(self.progress_dir.glob("job-*.json"))
|
|
125
|
+
progress_files = list[t.Any](self.progress_dir.glob("job-*.json"))
|
|
131
126
|
if not progress_files:
|
|
132
127
|
return None
|
|
133
128
|
|
|
@@ -139,11 +134,10 @@ class JobManager:
|
|
|
139
134
|
progress_file.stem[4:] if progress_file.stem.startswith("job -") else None
|
|
140
135
|
)
|
|
141
136
|
|
|
142
|
-
def get_job_progress(self, job_id: str) -> dict | None:
|
|
137
|
+
def get_job_progress(self, job_id: str) -> dict[str, t.Any] | None:
|
|
143
138
|
if not self.validate_job_id(job_id):
|
|
144
139
|
return None
|
|
145
140
|
|
|
146
|
-
# Use secure path joining to create progress file path
|
|
147
141
|
try:
|
|
148
142
|
progress_file = SecurePathValidator.secure_path_join(
|
|
149
143
|
self.progress_dir, f"job-{job_id}.json"
|
|
@@ -151,21 +145,18 @@ class JobManager:
|
|
|
151
145
|
if not progress_file.exists():
|
|
152
146
|
return None
|
|
153
147
|
|
|
154
|
-
# Validate file size before reading
|
|
155
148
|
SecurePathValidator.validate_file_size(progress_file)
|
|
156
149
|
|
|
157
|
-
return json.loads(progress_file.read_text())
|
|
150
|
+
return json.loads(progress_file.read_text()) # type: ignore[no-any-return]
|
|
158
151
|
except (json.JSONDecodeError, OSError):
|
|
159
152
|
return None
|
|
160
153
|
|
|
161
154
|
async def _process_progress_file(self, progress_file: Path) -> None:
|
|
162
|
-
# Validate the progress file path is within our allowed directory
|
|
163
155
|
try:
|
|
164
156
|
validated_file = SecurePathValidator.validate_safe_path(
|
|
165
157
|
progress_file, self.progress_dir
|
|
166
158
|
)
|
|
167
159
|
except Exception:
|
|
168
|
-
# If path validation fails, skip processing this file
|
|
169
160
|
return
|
|
170
161
|
|
|
171
162
|
job_id = self.extract_job_id_from_file(validated_file)
|
|
@@ -185,20 +176,18 @@ class JobManager:
|
|
|
185
176
|
|
|
186
177
|
while self.is_running:
|
|
187
178
|
try:
|
|
188
|
-
# Monitor directory changes with timeout protection
|
|
189
179
|
async with timeout_manager.timeout_context(
|
|
190
180
|
"file_operations",
|
|
191
|
-
timeout=10.0,
|
|
181
|
+
timeout=10.0,
|
|
192
182
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
193
183
|
):
|
|
194
184
|
if self.progress_dir.exists():
|
|
195
|
-
# Process files with individual timeouts
|
|
196
185
|
for progress_file in self.progress_dir.glob("job-*.json"):
|
|
197
186
|
try:
|
|
198
187
|
await timeout_manager.with_timeout(
|
|
199
188
|
"file_operations",
|
|
200
189
|
self._process_progress_file(progress_file),
|
|
201
|
-
timeout=5.0,
|
|
190
|
+
timeout=5.0,
|
|
202
191
|
)
|
|
203
192
|
except Exception as e:
|
|
204
193
|
console.print(
|
|
@@ -206,7 +195,6 @@ class JobManager:
|
|
|
206
195
|
)
|
|
207
196
|
continue
|
|
208
197
|
|
|
209
|
-
# Reset error count on successful cycle
|
|
210
198
|
consecutive_errors = 0
|
|
211
199
|
await asyncio.sleep(1)
|
|
212
200
|
|
|
@@ -214,14 +202,12 @@ class JobManager:
|
|
|
214
202
|
consecutive_errors += 1
|
|
215
203
|
console.print(f"[red]Progress monitoring error: {e}[/red]")
|
|
216
204
|
|
|
217
|
-
# Implement exponential backoff for repeated errors
|
|
218
205
|
if consecutive_errors >= max_consecutive_errors:
|
|
219
206
|
console.print(
|
|
220
207
|
f"[red]Too many consecutive errors ({consecutive_errors}), stopping monitor[/red]"
|
|
221
208
|
)
|
|
222
209
|
break
|
|
223
210
|
|
|
224
|
-
# Exponential backoff with max delay
|
|
225
211
|
delay = min(5 * (2 ** (consecutive_errors - 1)), 60)
|
|
226
212
|
await asyncio.sleep(delay)
|
|
227
213
|
|
|
@@ -232,19 +218,20 @@ class JobManager:
|
|
|
232
218
|
timeout_manager = get_timeout_manager()
|
|
233
219
|
|
|
234
220
|
try:
|
|
235
|
-
# Start file monitor with timeout protection
|
|
236
221
|
async with timeout_manager.timeout_context(
|
|
237
222
|
"file_operations",
|
|
238
|
-
timeout=30.0,
|
|
223
|
+
timeout=30.0,
|
|
239
224
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
240
225
|
):
|
|
241
226
|
monitor = create_progress_monitor(self.progress_dir)
|
|
242
227
|
await monitor.start()
|
|
243
228
|
|
|
244
|
-
def on_progress_update(
|
|
229
|
+
def on_progress_update(
|
|
230
|
+
job_id: str, progress_data: dict[str, t.Any]
|
|
231
|
+
) -> None:
|
|
245
232
|
if job_id and self.validate_job_id(job_id):
|
|
246
|
-
|
|
247
|
-
async def safe_broadcast():
|
|
233
|
+
|
|
234
|
+
async def safe_broadcast() -> None:
|
|
248
235
|
try:
|
|
249
236
|
await timeout_manager.with_timeout(
|
|
250
237
|
"websocket_broadcast",
|
|
@@ -256,13 +243,12 @@ class JobManager:
|
|
|
256
243
|
f"[yellow]Broadcast failed for job {job_id}: {e}[/yellow]"
|
|
257
244
|
)
|
|
258
245
|
|
|
259
|
-
asyncio.create_task(safe_broadcast())
|
|
246
|
+
asyncio.create_task(safe_broadcast()) # type: ignore[no-untyped-call]
|
|
260
247
|
|
|
261
248
|
if job_id not in self.known_jobs:
|
|
262
249
|
self.known_jobs.add(job_id)
|
|
263
250
|
console.print(f"[green]New job detected: {job_id}[/green]")
|
|
264
251
|
|
|
265
|
-
# Start directory monitoring with proper timeout handling
|
|
266
252
|
await self._monitor_directory_changes()
|
|
267
253
|
|
|
268
254
|
except Exception as e:
|
|
@@ -273,18 +259,17 @@ class JobManager:
|
|
|
273
259
|
|
|
274
260
|
while self.is_running:
|
|
275
261
|
try:
|
|
276
|
-
# Cleanup cycle with timeout protection
|
|
277
262
|
await timeout_manager.with_timeout(
|
|
278
263
|
"file_operations",
|
|
279
264
|
self._perform_cleanup_cycle(),
|
|
280
|
-
timeout=30.0,
|
|
265
|
+
timeout=30.0,
|
|
281
266
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
282
267
|
)
|
|
283
|
-
await asyncio.sleep(3600)
|
|
268
|
+
await asyncio.sleep(3600)
|
|
284
269
|
except Exception as e:
|
|
285
270
|
console.print(f"[red]Cleanup error: {e}[/red]")
|
|
286
|
-
|
|
287
|
-
await asyncio.sleep(1800)
|
|
271
|
+
|
|
272
|
+
await asyncio.sleep(1800)
|
|
288
273
|
|
|
289
274
|
async def _perform_cleanup_cycle(self) -> None:
|
|
290
275
|
if not self.progress_dir.exists():
|
|
@@ -318,17 +303,16 @@ class JobManager:
|
|
|
318
303
|
|
|
319
304
|
while self.is_running:
|
|
320
305
|
try:
|
|
321
|
-
# Timeout check with its own timeout protection
|
|
322
306
|
await timeout_manager.with_timeout(
|
|
323
307
|
"file_operations",
|
|
324
308
|
self._check_and_timeout_stuck_jobs(),
|
|
325
|
-
timeout=60.0,
|
|
309
|
+
timeout=60.0,
|
|
326
310
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
327
311
|
)
|
|
328
|
-
await asyncio.sleep(300)
|
|
312
|
+
await asyncio.sleep(300)
|
|
329
313
|
except Exception as e:
|
|
330
314
|
console.print(f"[red]Timeout check error: {e}[/red]")
|
|
331
|
-
|
|
315
|
+
|
|
332
316
|
await asyncio.sleep(300)
|
|
333
317
|
|
|
334
318
|
async def _check_and_timeout_stuck_jobs(self) -> None:
|
|
@@ -352,12 +336,10 @@ class JobManager:
|
|
|
352
336
|
timeout_seconds: int,
|
|
353
337
|
) -> None:
|
|
354
338
|
try:
|
|
355
|
-
# Validate the progress file path is secure
|
|
356
339
|
validated_file = SecurePathValidator.validate_safe_path(
|
|
357
340
|
progress_file, self.progress_dir
|
|
358
341
|
)
|
|
359
342
|
|
|
360
|
-
# Validate file size before reading
|
|
361
343
|
SecurePathValidator.validate_file_size(validated_file)
|
|
362
344
|
|
|
363
345
|
progress_data = json.loads(validated_file.read_text())
|
|
@@ -371,12 +353,11 @@ class JobManager:
|
|
|
371
353
|
self._timeout_job(progress_data, validated_file)
|
|
372
354
|
|
|
373
355
|
except (json.JSONDecodeError, OSError, Exception):
|
|
374
|
-
# Catch validation errors as well as file errors
|
|
375
356
|
pass
|
|
376
357
|
|
|
377
358
|
def _should_timeout_job(
|
|
378
359
|
self,
|
|
379
|
-
progress_data: dict,
|
|
360
|
+
progress_data: dict[str, t.Any],
|
|
380
361
|
progress_file: Path,
|
|
381
362
|
current_time: float,
|
|
382
363
|
timeout_seconds: int,
|
|
@@ -386,7 +367,9 @@ class JobManager:
|
|
|
386
367
|
and current_time - progress_file.stat().st_mtime > timeout_seconds
|
|
387
368
|
)
|
|
388
369
|
|
|
389
|
-
def _timeout_job(
|
|
370
|
+
def _timeout_job(
|
|
371
|
+
self, progress_data: dict[str, t.Any], progress_file: Path
|
|
372
|
+
) -> None:
|
|
390
373
|
progress_data["status"] = "failed"
|
|
391
374
|
progress_data["message"] = "Job timed out (no updates for 30 minutes)"
|
|
392
375
|
|