kollabor 0.4.9__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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
core/io/input_errors.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Error handling and recovery mechanisms for input processing."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Dict, List, Optional, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ErrorSeverity(Enum):
|
|
15
|
+
"""Severity levels for input errors."""
|
|
16
|
+
|
|
17
|
+
LOW = "low"
|
|
18
|
+
MEDIUM = "medium"
|
|
19
|
+
HIGH = "high"
|
|
20
|
+
CRITICAL = "critical"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ErrorType(Enum):
|
|
24
|
+
"""Types of input errors."""
|
|
25
|
+
|
|
26
|
+
IO_ERROR = "io_error"
|
|
27
|
+
PARSING_ERROR = "parsing_error"
|
|
28
|
+
BUFFER_ERROR = "buffer_error"
|
|
29
|
+
EVENT_ERROR = "event_error"
|
|
30
|
+
TIMEOUT_ERROR = "timeout_error"
|
|
31
|
+
VALIDATION_ERROR = "validation_error"
|
|
32
|
+
SYSTEM_ERROR = "system_error"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class InputError:
|
|
37
|
+
"""Represents an input handling error."""
|
|
38
|
+
|
|
39
|
+
type: ErrorType
|
|
40
|
+
severity: ErrorSeverity
|
|
41
|
+
message: str
|
|
42
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
43
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
resolved: bool = False
|
|
45
|
+
retry_count: int = 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ErrorRecoveryStrategy:
|
|
49
|
+
"""Base class for error recovery strategies."""
|
|
50
|
+
|
|
51
|
+
def can_handle(self, error: InputError) -> bool:
|
|
52
|
+
"""Check if this strategy can handle the error.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
error: The error to potentially handle.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if this strategy can handle the error.
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
async def recover(self, error: InputError, context: Dict[str, Any]) -> bool:
|
|
63
|
+
"""Attempt to recover from the error.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
error: The error to recover from.
|
|
67
|
+
context: Additional context for recovery.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if recovery was successful.
|
|
71
|
+
"""
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class IOErrorRecovery(ErrorRecoveryStrategy):
|
|
76
|
+
"""Recovery strategy for I/O errors."""
|
|
77
|
+
|
|
78
|
+
def can_handle(self, error: InputError) -> bool:
|
|
79
|
+
return error.type == ErrorType.IO_ERROR
|
|
80
|
+
|
|
81
|
+
async def recover(self, error: InputError, context: Dict[str, Any]) -> bool:
|
|
82
|
+
"""Recover from I/O errors by waiting and resetting."""
|
|
83
|
+
if error.retry_count >= 3:
|
|
84
|
+
logger.error("IO error recovery failed after 3 attempts")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
# Exponential backoff
|
|
88
|
+
wait_time = min(0.1 * (2**error.retry_count), 1.0)
|
|
89
|
+
await asyncio.sleep(wait_time)
|
|
90
|
+
|
|
91
|
+
logger.info(
|
|
92
|
+
f"Attempting IO error recovery " f"(attempt {error.retry_count + 1})"
|
|
93
|
+
)
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class BufferErrorRecovery(ErrorRecoveryStrategy):
|
|
98
|
+
"""Recovery strategy for buffer errors."""
|
|
99
|
+
|
|
100
|
+
def can_handle(self, error: InputError) -> bool:
|
|
101
|
+
return error.type == ErrorType.BUFFER_ERROR
|
|
102
|
+
|
|
103
|
+
async def recover(self, error: InputError, context: Dict[str, Any]) -> bool:
|
|
104
|
+
"""Recover from buffer errors by resetting buffer if needed."""
|
|
105
|
+
buffer_manager = context.get("buffer_manager")
|
|
106
|
+
if not buffer_manager:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
if error.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]:
|
|
110
|
+
# Clear buffer on severe errors
|
|
111
|
+
buffer_manager.clear()
|
|
112
|
+
logger.warning("Buffer cleared due to severe error")
|
|
113
|
+
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EventErrorRecovery(ErrorRecoveryStrategy):
|
|
118
|
+
"""Recovery strategy for event system errors."""
|
|
119
|
+
|
|
120
|
+
def can_handle(self, error: InputError) -> bool:
|
|
121
|
+
return error.type == ErrorType.EVENT_ERROR
|
|
122
|
+
|
|
123
|
+
async def recover(self, error: InputError, context: Dict[str, Any]) -> bool:
|
|
124
|
+
"""Recover from event system errors."""
|
|
125
|
+
if error.severity == ErrorSeverity.CRITICAL:
|
|
126
|
+
logger.error("Critical event error - recovery not possible")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
# For non-critical event errors, just log and continue
|
|
130
|
+
logger.warning(f"Event error recovered: {error.message}")
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class InputErrorHandler:
|
|
135
|
+
"""Centralized error handling and recovery system for input processing."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
138
|
+
"""Initialize the error handler.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
config: Configuration for error handling behavior.
|
|
142
|
+
"""
|
|
143
|
+
self.config = config or {}
|
|
144
|
+
self._errors: List[InputError] = []
|
|
145
|
+
self._error_counts: Dict[ErrorType, int] = {}
|
|
146
|
+
self._last_error_time: Optional[datetime] = None
|
|
147
|
+
self._error_threshold = self.config.get("error_threshold", 10)
|
|
148
|
+
self._error_window = timedelta(
|
|
149
|
+
minutes=self.config.get("error_window_minutes", 5)
|
|
150
|
+
)
|
|
151
|
+
self._max_errors = self.config.get("max_errors", 100)
|
|
152
|
+
|
|
153
|
+
# Initialize recovery strategies
|
|
154
|
+
self._recovery_strategies: List[ErrorRecoveryStrategy] = [
|
|
155
|
+
IOErrorRecovery(),
|
|
156
|
+
BufferErrorRecovery(),
|
|
157
|
+
EventErrorRecovery(),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
logger.info("InputErrorHandler initialized")
|
|
161
|
+
|
|
162
|
+
def add_recovery_strategy(self, strategy: ErrorRecoveryStrategy) -> None:
|
|
163
|
+
"""Add a custom recovery strategy.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
strategy: Recovery strategy to add.
|
|
167
|
+
"""
|
|
168
|
+
self._recovery_strategies.append(strategy)
|
|
169
|
+
logger.debug(f"Added recovery strategy: {strategy.__class__.__name__}")
|
|
170
|
+
|
|
171
|
+
async def handle_error(
|
|
172
|
+
self,
|
|
173
|
+
error_type: ErrorType,
|
|
174
|
+
message: str,
|
|
175
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
176
|
+
context: Optional[Dict[str, Any]] = None,
|
|
177
|
+
) -> bool:
|
|
178
|
+
"""Handle an input error.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
error_type: Type of error that occurred.
|
|
182
|
+
message: Error message.
|
|
183
|
+
severity: Severity level of the error.
|
|
184
|
+
context: Additional context for error handling.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if error was handled successfully.
|
|
188
|
+
"""
|
|
189
|
+
error = InputError(
|
|
190
|
+
type=error_type,
|
|
191
|
+
severity=severity,
|
|
192
|
+
message=message,
|
|
193
|
+
context=context or {},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Record error
|
|
197
|
+
self._record_error(error)
|
|
198
|
+
|
|
199
|
+
# Check for error storm
|
|
200
|
+
if self._is_error_storm():
|
|
201
|
+
logger.critical("Error storm detected - input system may be unstable")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
# Attempt recovery
|
|
205
|
+
return await self._attempt_recovery(error, context or {})
|
|
206
|
+
|
|
207
|
+
def _record_error(self, error: InputError) -> None:
|
|
208
|
+
"""Record an error in the error log.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
error: Error to record.
|
|
212
|
+
"""
|
|
213
|
+
self._errors.append(error)
|
|
214
|
+
self._error_counts[error.type] = self._error_counts.get(error.type, 0) + 1
|
|
215
|
+
self._last_error_time = error.timestamp
|
|
216
|
+
|
|
217
|
+
# Maintain error log size
|
|
218
|
+
if len(self._errors) > self._max_errors:
|
|
219
|
+
self._errors = self._errors[-self._max_errors :]
|
|
220
|
+
|
|
221
|
+
severity_val = getattr(error.severity, "value", error.severity)
|
|
222
|
+
type_val = getattr(error.type, "value", error.type)
|
|
223
|
+
logger.debug(f"Recorded {severity_val} {type_val}: {error.message}")
|
|
224
|
+
|
|
225
|
+
def _is_error_storm(self) -> bool:
|
|
226
|
+
"""Check if we're experiencing an error storm.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if error storm is detected.
|
|
230
|
+
"""
|
|
231
|
+
if not self._last_error_time:
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
# Count recent errors
|
|
235
|
+
cutoff_time = datetime.now() - self._error_window
|
|
236
|
+
recent_errors = [e for e in self._errors if e.timestamp >= cutoff_time]
|
|
237
|
+
|
|
238
|
+
return len(recent_errors) >= self._error_threshold
|
|
239
|
+
|
|
240
|
+
async def _attempt_recovery(
|
|
241
|
+
self, error: InputError, context: Dict[str, Any]
|
|
242
|
+
) -> bool:
|
|
243
|
+
"""Attempt to recover from an error using available strategies.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
error: Error to recover from.
|
|
247
|
+
context: Context for recovery.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
True if recovery was successful.
|
|
251
|
+
"""
|
|
252
|
+
for strategy in self._recovery_strategies:
|
|
253
|
+
if strategy.can_handle(error):
|
|
254
|
+
try:
|
|
255
|
+
error.retry_count += 1
|
|
256
|
+
success = await strategy.recover(error, context)
|
|
257
|
+
|
|
258
|
+
if success:
|
|
259
|
+
error.resolved = True
|
|
260
|
+
strategy_name = strategy.__class__.__name__
|
|
261
|
+
logger.debug(f"Error recovered using {strategy_name}")
|
|
262
|
+
return True
|
|
263
|
+
else:
|
|
264
|
+
strategy_name = strategy.__class__.__name__
|
|
265
|
+
logger.warning(f"Recovery failed using {strategy_name}")
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Recovery strategy failed: {e}")
|
|
269
|
+
|
|
270
|
+
error_type_val = getattr(error.type, "value", error.type)
|
|
271
|
+
logger.error(f"No recovery strategy could handle {error_type_val} error")
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
def get_error_stats(self) -> Dict[str, Any]:
|
|
275
|
+
"""Get error statistics.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Dictionary containing error statistics.
|
|
279
|
+
"""
|
|
280
|
+
recent_cutoff = datetime.now() - timedelta(minutes=5)
|
|
281
|
+
recent_errors = [e for e in self._errors if e.timestamp >= recent_cutoff]
|
|
282
|
+
|
|
283
|
+
last_error_iso = (
|
|
284
|
+
self._last_error_time.isoformat() if self._last_error_time else None
|
|
285
|
+
)
|
|
286
|
+
return {
|
|
287
|
+
"total_errors": len(self._errors),
|
|
288
|
+
"recent_errors": len(recent_errors),
|
|
289
|
+
"error_counts": dict(self._error_counts),
|
|
290
|
+
"last_error": last_error_iso,
|
|
291
|
+
"resolved_errors": len([e for e in self._errors if e.resolved]),
|
|
292
|
+
"unresolved_errors": len([e for e in self._errors if not e.resolved]),
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
def clear_old_errors(self, max_age_hours: int = 24) -> int:
|
|
296
|
+
"""Clear old errors from the log.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
max_age_hours: Maximum age of errors to keep in hours.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Number of errors cleared.
|
|
303
|
+
"""
|
|
304
|
+
cutoff_time = datetime.now() - timedelta(hours=max_age_hours)
|
|
305
|
+
initial_count = len(self._errors)
|
|
306
|
+
|
|
307
|
+
self._errors = [e for e in self._errors if e.timestamp >= cutoff_time]
|
|
308
|
+
|
|
309
|
+
cleared_count = initial_count - len(self._errors)
|
|
310
|
+
if cleared_count > 0:
|
|
311
|
+
logger.info(f"Cleared {cleared_count} old errors")
|
|
312
|
+
|
|
313
|
+
return cleared_count
|