omnimancer-cli 0.1.0__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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error handling for read-before-write functionality.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive error handling for read-before-write operations,
|
|
5
|
+
including custom exceptions, error recovery strategies, and error context management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ReadBeforeWriteErrorType(Enum):
|
|
18
|
+
"""Types of errors that can occur in read-before-write operations."""
|
|
19
|
+
|
|
20
|
+
FILE_READ_ERROR = "file_read_error"
|
|
21
|
+
FILE_WRITE_ERROR = "file_write_error"
|
|
22
|
+
USER_INTERFACE_ERROR = "user_interface_error"
|
|
23
|
+
PERMISSION_ERROR = "permission_error"
|
|
24
|
+
ENCODING_ERROR = "encoding_error"
|
|
25
|
+
CONTENT_VALIDATION_ERROR = "content_validation_error"
|
|
26
|
+
CALLBACK_ERROR = "callback_error"
|
|
27
|
+
DIFF_GENERATION_ERROR = "diff_generation_error"
|
|
28
|
+
PREVIEW_ERROR = "preview_error"
|
|
29
|
+
USER_REJECTION = "user_rejection"
|
|
30
|
+
TIMEOUT_ERROR = "timeout_error"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RecoveryStrategy(Enum):
|
|
34
|
+
"""Strategies for recovering from read-before-write errors."""
|
|
35
|
+
|
|
36
|
+
RETRY = "retry"
|
|
37
|
+
FALLBACK_TO_REGULAR_WRITE = "fallback_to_regular_write"
|
|
38
|
+
SKIP_OPERATION = "skip_operation"
|
|
39
|
+
PROMPT_USER = "prompt_user"
|
|
40
|
+
LOG_AND_CONTINUE = "log_and_continue"
|
|
41
|
+
ABORT_OPERATION = "abort_operation"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ErrorContext:
|
|
46
|
+
"""Context information for read-before-write errors."""
|
|
47
|
+
|
|
48
|
+
error_type: ReadBeforeWriteErrorType
|
|
49
|
+
file_path: str
|
|
50
|
+
operation: str # 'create' or 'modify'
|
|
51
|
+
encoding: str
|
|
52
|
+
content_length: int
|
|
53
|
+
timestamp: datetime
|
|
54
|
+
original_exception: Optional[Exception] = None
|
|
55
|
+
recovery_attempted: bool = False
|
|
56
|
+
recovery_strategy: Optional[RecoveryStrategy] = None
|
|
57
|
+
recovery_success: Optional[bool] = None
|
|
58
|
+
additional_context: Dict[str, Any] = None
|
|
59
|
+
|
|
60
|
+
def __post_init__(self):
|
|
61
|
+
if self.additional_context is None:
|
|
62
|
+
self.additional_context = {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ReadBeforeWriteError(Exception):
|
|
66
|
+
"""Base exception for read-before-write operations."""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
message: str,
|
|
71
|
+
error_type: ReadBeforeWriteErrorType,
|
|
72
|
+
context: Optional[ErrorContext] = None,
|
|
73
|
+
original_exception: Optional[Exception] = None,
|
|
74
|
+
):
|
|
75
|
+
super().__init__(message)
|
|
76
|
+
self.message = message
|
|
77
|
+
self.error_type = error_type
|
|
78
|
+
self.context = context
|
|
79
|
+
self.original_exception = original_exception
|
|
80
|
+
|
|
81
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
82
|
+
"""Convert error to dictionary for serialization."""
|
|
83
|
+
return {
|
|
84
|
+
"message": self.message,
|
|
85
|
+
"error_type": self.error_type.value,
|
|
86
|
+
"context": {
|
|
87
|
+
"file_path": self.context.file_path if self.context else None,
|
|
88
|
+
"operation": self.context.operation if self.context else None,
|
|
89
|
+
"encoding": self.context.encoding if self.context else None,
|
|
90
|
+
"timestamp": (
|
|
91
|
+
self.context.timestamp.isoformat() if self.context else None
|
|
92
|
+
),
|
|
93
|
+
"recovery_attempted": (
|
|
94
|
+
self.context.recovery_attempted if self.context else False
|
|
95
|
+
),
|
|
96
|
+
"recovery_strategy": (
|
|
97
|
+
self.context.recovery_strategy.value
|
|
98
|
+
if self.context and self.context.recovery_strategy
|
|
99
|
+
else None
|
|
100
|
+
),
|
|
101
|
+
"recovery_success": (
|
|
102
|
+
self.context.recovery_success if self.context else None
|
|
103
|
+
),
|
|
104
|
+
"additional_context": (
|
|
105
|
+
self.context.additional_context if self.context else {}
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
"original_exception": (
|
|
109
|
+
str(self.original_exception) if self.original_exception else None
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class FileReadError(ReadBeforeWriteError):
|
|
115
|
+
"""Error reading existing file content."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, file_path: str, original_exception: Exception):
|
|
118
|
+
context = ErrorContext(
|
|
119
|
+
error_type=ReadBeforeWriteErrorType.FILE_READ_ERROR,
|
|
120
|
+
file_path=file_path,
|
|
121
|
+
operation="read",
|
|
122
|
+
encoding="unknown",
|
|
123
|
+
content_length=0,
|
|
124
|
+
timestamp=datetime.now(),
|
|
125
|
+
original_exception=original_exception,
|
|
126
|
+
)
|
|
127
|
+
super().__init__(
|
|
128
|
+
f"Failed to read existing content from {file_path}: {str(original_exception)}",
|
|
129
|
+
ReadBeforeWriteErrorType.FILE_READ_ERROR,
|
|
130
|
+
context,
|
|
131
|
+
original_exception,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class FileWriteError(ReadBeforeWriteError):
|
|
136
|
+
"""Error writing file content after review."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, file_path: str, original_exception: Exception):
|
|
139
|
+
context = ErrorContext(
|
|
140
|
+
error_type=ReadBeforeWriteErrorType.FILE_WRITE_ERROR,
|
|
141
|
+
file_path=file_path,
|
|
142
|
+
operation="write",
|
|
143
|
+
encoding="unknown",
|
|
144
|
+
content_length=0,
|
|
145
|
+
timestamp=datetime.now(),
|
|
146
|
+
original_exception=original_exception,
|
|
147
|
+
)
|
|
148
|
+
super().__init__(
|
|
149
|
+
f"Failed to write reviewed content to {file_path}: {str(original_exception)}",
|
|
150
|
+
ReadBeforeWriteErrorType.FILE_WRITE_ERROR,
|
|
151
|
+
context,
|
|
152
|
+
original_exception,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class UserInterfaceError(ReadBeforeWriteError):
|
|
157
|
+
"""Error in user interface interaction."""
|
|
158
|
+
|
|
159
|
+
def __init__(self, message: str, original_exception: Optional[Exception] = None):
|
|
160
|
+
context = ErrorContext(
|
|
161
|
+
error_type=ReadBeforeWriteErrorType.USER_INTERFACE_ERROR,
|
|
162
|
+
file_path="unknown",
|
|
163
|
+
operation="ui_interaction",
|
|
164
|
+
encoding="unknown",
|
|
165
|
+
content_length=0,
|
|
166
|
+
timestamp=datetime.now(),
|
|
167
|
+
original_exception=original_exception,
|
|
168
|
+
)
|
|
169
|
+
super().__init__(
|
|
170
|
+
f"User interface error: {message}",
|
|
171
|
+
ReadBeforeWriteErrorType.USER_INTERFACE_ERROR,
|
|
172
|
+
context,
|
|
173
|
+
original_exception,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class CallbackError(ReadBeforeWriteError):
|
|
178
|
+
"""Error in user review callback execution."""
|
|
179
|
+
|
|
180
|
+
def __init__(self, file_path: str, original_exception: Exception):
|
|
181
|
+
context = ErrorContext(
|
|
182
|
+
error_type=ReadBeforeWriteErrorType.CALLBACK_ERROR,
|
|
183
|
+
file_path=file_path,
|
|
184
|
+
operation="callback",
|
|
185
|
+
encoding="unknown",
|
|
186
|
+
content_length=0,
|
|
187
|
+
timestamp=datetime.now(),
|
|
188
|
+
original_exception=original_exception,
|
|
189
|
+
)
|
|
190
|
+
super().__init__(
|
|
191
|
+
f"Error in user review callback for {file_path}: {str(original_exception)}",
|
|
192
|
+
ReadBeforeWriteErrorType.CALLBACK_ERROR,
|
|
193
|
+
context,
|
|
194
|
+
original_exception,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class ContentValidationError(ReadBeforeWriteError):
|
|
199
|
+
"""Error validating file content."""
|
|
200
|
+
|
|
201
|
+
def __init__(self, file_path: str, validation_issue: str):
|
|
202
|
+
context = ErrorContext(
|
|
203
|
+
error_type=ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR,
|
|
204
|
+
file_path=file_path,
|
|
205
|
+
operation="validation",
|
|
206
|
+
encoding="unknown",
|
|
207
|
+
content_length=0,
|
|
208
|
+
timestamp=datetime.now(),
|
|
209
|
+
additional_context={"validation_issue": validation_issue},
|
|
210
|
+
)
|
|
211
|
+
super().__init__(
|
|
212
|
+
f"Content validation failed for {file_path}: {validation_issue}",
|
|
213
|
+
ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR,
|
|
214
|
+
context,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class DiffGenerationError(ReadBeforeWriteError):
|
|
219
|
+
"""Error generating content diff."""
|
|
220
|
+
|
|
221
|
+
def __init__(self, file_path: str, original_exception: Exception):
|
|
222
|
+
context = ErrorContext(
|
|
223
|
+
error_type=ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
|
|
224
|
+
file_path=file_path,
|
|
225
|
+
operation="diff_generation",
|
|
226
|
+
encoding="unknown",
|
|
227
|
+
content_length=0,
|
|
228
|
+
timestamp=datetime.now(),
|
|
229
|
+
original_exception=original_exception,
|
|
230
|
+
)
|
|
231
|
+
super().__init__(
|
|
232
|
+
f"Failed to generate diff for {file_path}: {str(original_exception)}",
|
|
233
|
+
ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
|
|
234
|
+
context,
|
|
235
|
+
original_exception,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class UserRejectionError(ReadBeforeWriteError):
|
|
240
|
+
"""User rejected the file modification."""
|
|
241
|
+
|
|
242
|
+
def __init__(self, file_path: str, reason: str):
|
|
243
|
+
context = ErrorContext(
|
|
244
|
+
error_type=ReadBeforeWriteErrorType.USER_REJECTION,
|
|
245
|
+
file_path=file_path,
|
|
246
|
+
operation="user_decision",
|
|
247
|
+
encoding="unknown",
|
|
248
|
+
content_length=0,
|
|
249
|
+
timestamp=datetime.now(),
|
|
250
|
+
additional_context={"rejection_reason": reason},
|
|
251
|
+
)
|
|
252
|
+
super().__init__(
|
|
253
|
+
f"User rejected modification of {file_path}: {reason}",
|
|
254
|
+
ReadBeforeWriteErrorType.USER_REJECTION,
|
|
255
|
+
context,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class ReadBeforeWriteErrorHandler:
|
|
260
|
+
"""
|
|
261
|
+
Comprehensive error handler for read-before-write operations.
|
|
262
|
+
|
|
263
|
+
Provides error recovery strategies, logging, and fallback mechanisms.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(self, enable_recovery: bool = True, log_errors: bool = True):
|
|
267
|
+
"""
|
|
268
|
+
Initialize error handler.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
enable_recovery: Whether to attempt error recovery
|
|
272
|
+
log_errors: Whether to log errors
|
|
273
|
+
"""
|
|
274
|
+
self.enable_recovery = enable_recovery
|
|
275
|
+
self.log_errors = log_errors
|
|
276
|
+
self.error_history: List[ReadBeforeWriteError] = []
|
|
277
|
+
|
|
278
|
+
# Recovery strategy mappings
|
|
279
|
+
self.recovery_strategies = {
|
|
280
|
+
ReadBeforeWriteErrorType.FILE_READ_ERROR: RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE,
|
|
281
|
+
ReadBeforeWriteErrorType.FILE_WRITE_ERROR: RecoveryStrategy.RETRY,
|
|
282
|
+
ReadBeforeWriteErrorType.USER_INTERFACE_ERROR: RecoveryStrategy.PROMPT_USER,
|
|
283
|
+
ReadBeforeWriteErrorType.PERMISSION_ERROR: RecoveryStrategy.ABORT_OPERATION,
|
|
284
|
+
ReadBeforeWriteErrorType.ENCODING_ERROR: RecoveryStrategy.PROMPT_USER,
|
|
285
|
+
ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR: RecoveryStrategy.PROMPT_USER,
|
|
286
|
+
ReadBeforeWriteErrorType.CALLBACK_ERROR: RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE,
|
|
287
|
+
ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR: RecoveryStrategy.LOG_AND_CONTINUE,
|
|
288
|
+
ReadBeforeWriteErrorType.PREVIEW_ERROR: RecoveryStrategy.LOG_AND_CONTINUE,
|
|
289
|
+
ReadBeforeWriteErrorType.USER_REJECTION: RecoveryStrategy.SKIP_OPERATION,
|
|
290
|
+
ReadBeforeWriteErrorType.TIMEOUT_ERROR: RecoveryStrategy.PROMPT_USER,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
def handle_error(
|
|
294
|
+
self,
|
|
295
|
+
error: ReadBeforeWriteError,
|
|
296
|
+
retry_count: int = 0,
|
|
297
|
+
max_retries: int = 2,
|
|
298
|
+
) -> Dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Handle a read-before-write error with appropriate recovery strategy.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
error: The error to handle
|
|
304
|
+
retry_count: Current retry attempt count
|
|
305
|
+
max_retries: Maximum number of retries allowed
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Dict with error handling result and recovery information
|
|
309
|
+
"""
|
|
310
|
+
# Record error in history
|
|
311
|
+
self.error_history.append(error)
|
|
312
|
+
|
|
313
|
+
# Log error if enabled
|
|
314
|
+
if self.log_errors:
|
|
315
|
+
self._log_error(error)
|
|
316
|
+
|
|
317
|
+
# Get recovery strategy
|
|
318
|
+
strategy = self.recovery_strategies.get(
|
|
319
|
+
error.error_type, RecoveryStrategy.ABORT_OPERATION
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Apply recovery strategy
|
|
323
|
+
recovery_result = self._apply_recovery_strategy(
|
|
324
|
+
error, strategy, retry_count, max_retries
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Update error context with recovery information
|
|
328
|
+
if error.context:
|
|
329
|
+
error.context.recovery_attempted = True
|
|
330
|
+
error.context.recovery_strategy = strategy
|
|
331
|
+
error.context.recovery_success = recovery_result.get("success", False)
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
"error": error.to_dict(),
|
|
335
|
+
"recovery_strategy": strategy.value,
|
|
336
|
+
"recovery_result": recovery_result,
|
|
337
|
+
"should_retry": recovery_result.get("should_retry", False),
|
|
338
|
+
"should_abort": recovery_result.get("should_abort", False),
|
|
339
|
+
"fallback_action": recovery_result.get("fallback_action"),
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def _apply_recovery_strategy(
|
|
343
|
+
self,
|
|
344
|
+
error: ReadBeforeWriteError,
|
|
345
|
+
strategy: RecoveryStrategy,
|
|
346
|
+
retry_count: int,
|
|
347
|
+
max_retries: int,
|
|
348
|
+
) -> Dict[str, Any]:
|
|
349
|
+
"""Apply the specified recovery strategy."""
|
|
350
|
+
if not self.enable_recovery:
|
|
351
|
+
return {
|
|
352
|
+
"success": False,
|
|
353
|
+
"message": "Error recovery disabled",
|
|
354
|
+
"should_abort": True,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if strategy == RecoveryStrategy.RETRY:
|
|
358
|
+
if retry_count < max_retries:
|
|
359
|
+
return {
|
|
360
|
+
"success": True,
|
|
361
|
+
"message": f"Retrying operation (attempt {retry_count + 1}/{max_retries})",
|
|
362
|
+
"should_retry": True,
|
|
363
|
+
}
|
|
364
|
+
else:
|
|
365
|
+
return {
|
|
366
|
+
"success": False,
|
|
367
|
+
"message": f"Maximum retries ({max_retries}) exceeded",
|
|
368
|
+
"should_abort": True,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
elif strategy == RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE:
|
|
372
|
+
return {
|
|
373
|
+
"success": True,
|
|
374
|
+
"message": "Falling back to regular write operation without review",
|
|
375
|
+
"fallback_action": "regular_write",
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
elif strategy == RecoveryStrategy.SKIP_OPERATION:
|
|
379
|
+
return {
|
|
380
|
+
"success": True,
|
|
381
|
+
"message": "Skipping operation due to user rejection or other reason",
|
|
382
|
+
"fallback_action": "skip",
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
elif strategy == RecoveryStrategy.LOG_AND_CONTINUE:
|
|
386
|
+
return {
|
|
387
|
+
"success": True,
|
|
388
|
+
"message": "Error logged, continuing with operation",
|
|
389
|
+
"fallback_action": "continue_with_warning",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
elif strategy == RecoveryStrategy.PROMPT_USER:
|
|
393
|
+
return {
|
|
394
|
+
"success": False,
|
|
395
|
+
"message": "User intervention required",
|
|
396
|
+
"fallback_action": "prompt_user",
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
elif strategy == RecoveryStrategy.ABORT_OPERATION:
|
|
400
|
+
return {
|
|
401
|
+
"success": False,
|
|
402
|
+
"message": "Operation aborted due to unrecoverable error",
|
|
403
|
+
"should_abort": True,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
"success": False,
|
|
408
|
+
"message": f"Unknown recovery strategy: {strategy}",
|
|
409
|
+
"should_abort": True,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
def _log_error(self, error: ReadBeforeWriteError):
|
|
413
|
+
"""Log error with appropriate severity level."""
|
|
414
|
+
log_message = f"Read-before-write error: {error.message}"
|
|
415
|
+
|
|
416
|
+
if error.context:
|
|
417
|
+
log_message += f" [File: {error.context.file_path}, Operation: {error.context.operation}]"
|
|
418
|
+
|
|
419
|
+
# Determine log level based on error type
|
|
420
|
+
if error.error_type in [ReadBeforeWriteErrorType.USER_REJECTION]:
|
|
421
|
+
logger.info(log_message)
|
|
422
|
+
elif error.error_type in [
|
|
423
|
+
ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
|
|
424
|
+
ReadBeforeWriteErrorType.PREVIEW_ERROR,
|
|
425
|
+
]:
|
|
426
|
+
logger.warning(log_message)
|
|
427
|
+
else:
|
|
428
|
+
logger.error(log_message, exc_info=error.original_exception)
|
|
429
|
+
|
|
430
|
+
def get_error_statistics(self) -> Dict[str, Any]:
|
|
431
|
+
"""Get statistics about handled errors."""
|
|
432
|
+
if not self.error_history:
|
|
433
|
+
return {"total_errors": 0}
|
|
434
|
+
|
|
435
|
+
error_counts = {}
|
|
436
|
+
recovery_counts = {}
|
|
437
|
+
|
|
438
|
+
for error in self.error_history:
|
|
439
|
+
error_type = error.error_type.value
|
|
440
|
+
error_counts[error_type] = error_counts.get(error_type, 0) + 1
|
|
441
|
+
|
|
442
|
+
if error.context and error.context.recovery_strategy:
|
|
443
|
+
recovery_strategy = error.context.recovery_strategy.value
|
|
444
|
+
recovery_counts[recovery_strategy] = (
|
|
445
|
+
recovery_counts.get(recovery_strategy, 0) + 1
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
successful_recoveries = sum(
|
|
449
|
+
1
|
|
450
|
+
for error in self.error_history
|
|
451
|
+
if error.context and error.context.recovery_success
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"total_errors": len(self.error_history),
|
|
456
|
+
"error_types": error_counts,
|
|
457
|
+
"recovery_strategies_used": recovery_counts,
|
|
458
|
+
"successful_recoveries": successful_recoveries,
|
|
459
|
+
"recovery_success_rate": (
|
|
460
|
+
successful_recoveries / len(self.error_history)
|
|
461
|
+
if self.error_history
|
|
462
|
+
else 0
|
|
463
|
+
),
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
def clear_error_history(self):
|
|
467
|
+
"""Clear the error history."""
|
|
468
|
+
self.error_history.clear()
|
|
469
|
+
|
|
470
|
+
def set_recovery_strategy(
|
|
471
|
+
self, error_type: ReadBeforeWriteErrorType, strategy: RecoveryStrategy
|
|
472
|
+
):
|
|
473
|
+
"""Set custom recovery strategy for an error type."""
|
|
474
|
+
self.recovery_strategies[error_type] = strategy
|
|
475
|
+
logger.info(f"Set recovery strategy for {error_type.value}: {strategy.value}")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
# Convenience function for creating error handler
|
|
479
|
+
def create_error_handler(
|
|
480
|
+
enable_recovery: bool = True, log_errors: bool = True
|
|
481
|
+
) -> ReadBeforeWriteErrorHandler:
|
|
482
|
+
"""
|
|
483
|
+
Create a configured error handler for read-before-write operations.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
enable_recovery: Whether to enable error recovery
|
|
487
|
+
log_errors: Whether to log errors
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Configured ReadBeforeWriteErrorHandler instance
|
|
491
|
+
"""
|
|
492
|
+
return ReadBeforeWriteErrorHandler(enable_recovery, log_errors)
|