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.
Files changed (128) hide show
  1. core/__init__.py +18 -0
  2. core/application.py +578 -0
  3. core/cli.py +193 -0
  4. core/commands/__init__.py +43 -0
  5. core/commands/executor.py +277 -0
  6. core/commands/menu_renderer.py +319 -0
  7. core/commands/parser.py +186 -0
  8. core/commands/registry.py +331 -0
  9. core/commands/system_commands.py +479 -0
  10. core/config/__init__.py +7 -0
  11. core/config/llm_task_config.py +110 -0
  12. core/config/loader.py +501 -0
  13. core/config/manager.py +112 -0
  14. core/config/plugin_config_manager.py +346 -0
  15. core/config/plugin_schema.py +424 -0
  16. core/config/service.py +399 -0
  17. core/effects/__init__.py +1 -0
  18. core/events/__init__.py +12 -0
  19. core/events/bus.py +129 -0
  20. core/events/executor.py +154 -0
  21. core/events/models.py +258 -0
  22. core/events/processor.py +176 -0
  23. core/events/registry.py +289 -0
  24. core/fullscreen/__init__.py +19 -0
  25. core/fullscreen/command_integration.py +290 -0
  26. core/fullscreen/components/__init__.py +12 -0
  27. core/fullscreen/components/animation.py +258 -0
  28. core/fullscreen/components/drawing.py +160 -0
  29. core/fullscreen/components/matrix_components.py +177 -0
  30. core/fullscreen/manager.py +302 -0
  31. core/fullscreen/plugin.py +204 -0
  32. core/fullscreen/renderer.py +282 -0
  33. core/fullscreen/session.py +324 -0
  34. core/io/__init__.py +52 -0
  35. core/io/buffer_manager.py +362 -0
  36. core/io/config_status_view.py +272 -0
  37. core/io/core_status_views.py +410 -0
  38. core/io/input_errors.py +313 -0
  39. core/io/input_handler.py +2655 -0
  40. core/io/input_mode_manager.py +402 -0
  41. core/io/key_parser.py +344 -0
  42. core/io/layout.py +587 -0
  43. core/io/message_coordinator.py +204 -0
  44. core/io/message_renderer.py +601 -0
  45. core/io/modal_interaction_handler.py +315 -0
  46. core/io/raw_input_processor.py +946 -0
  47. core/io/status_renderer.py +845 -0
  48. core/io/terminal_renderer.py +586 -0
  49. core/io/terminal_state.py +551 -0
  50. core/io/visual_effects.py +734 -0
  51. core/llm/__init__.py +26 -0
  52. core/llm/api_communication_service.py +863 -0
  53. core/llm/conversation_logger.py +473 -0
  54. core/llm/conversation_manager.py +414 -0
  55. core/llm/file_operations_executor.py +1401 -0
  56. core/llm/hook_system.py +402 -0
  57. core/llm/llm_service.py +1629 -0
  58. core/llm/mcp_integration.py +386 -0
  59. core/llm/message_display_service.py +450 -0
  60. core/llm/model_router.py +214 -0
  61. core/llm/plugin_sdk.py +396 -0
  62. core/llm/response_parser.py +848 -0
  63. core/llm/response_processor.py +364 -0
  64. core/llm/tool_executor.py +520 -0
  65. core/logging/__init__.py +19 -0
  66. core/logging/setup.py +208 -0
  67. core/models/__init__.py +5 -0
  68. core/models/base.py +23 -0
  69. core/plugins/__init__.py +13 -0
  70. core/plugins/collector.py +212 -0
  71. core/plugins/discovery.py +386 -0
  72. core/plugins/factory.py +263 -0
  73. core/plugins/registry.py +152 -0
  74. core/storage/__init__.py +5 -0
  75. core/storage/state_manager.py +84 -0
  76. core/ui/__init__.py +6 -0
  77. core/ui/config_merger.py +176 -0
  78. core/ui/config_widgets.py +369 -0
  79. core/ui/live_modal_renderer.py +276 -0
  80. core/ui/modal_actions.py +162 -0
  81. core/ui/modal_overlay_renderer.py +373 -0
  82. core/ui/modal_renderer.py +591 -0
  83. core/ui/modal_state_manager.py +443 -0
  84. core/ui/widget_integration.py +222 -0
  85. core/ui/widgets/__init__.py +27 -0
  86. core/ui/widgets/base_widget.py +136 -0
  87. core/ui/widgets/checkbox.py +85 -0
  88. core/ui/widgets/dropdown.py +140 -0
  89. core/ui/widgets/label.py +78 -0
  90. core/ui/widgets/slider.py +185 -0
  91. core/ui/widgets/text_input.py +224 -0
  92. core/utils/__init__.py +11 -0
  93. core/utils/config_utils.py +656 -0
  94. core/utils/dict_utils.py +212 -0
  95. core/utils/error_utils.py +275 -0
  96. core/utils/key_reader.py +171 -0
  97. core/utils/plugin_utils.py +267 -0
  98. core/utils/prompt_renderer.py +151 -0
  99. kollabor-0.4.9.dist-info/METADATA +298 -0
  100. kollabor-0.4.9.dist-info/RECORD +128 -0
  101. kollabor-0.4.9.dist-info/WHEEL +5 -0
  102. kollabor-0.4.9.dist-info/entry_points.txt +2 -0
  103. kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
  104. kollabor-0.4.9.dist-info/top_level.txt +4 -0
  105. kollabor_cli_main.py +20 -0
  106. plugins/__init__.py +1 -0
  107. plugins/enhanced_input/__init__.py +18 -0
  108. plugins/enhanced_input/box_renderer.py +103 -0
  109. plugins/enhanced_input/box_styles.py +142 -0
  110. plugins/enhanced_input/color_engine.py +165 -0
  111. plugins/enhanced_input/config.py +150 -0
  112. plugins/enhanced_input/cursor_manager.py +72 -0
  113. plugins/enhanced_input/geometry.py +81 -0
  114. plugins/enhanced_input/state.py +130 -0
  115. plugins/enhanced_input/text_processor.py +115 -0
  116. plugins/enhanced_input_plugin.py +385 -0
  117. plugins/fullscreen/__init__.py +9 -0
  118. plugins/fullscreen/example_plugin.py +327 -0
  119. plugins/fullscreen/matrix_plugin.py +132 -0
  120. plugins/hook_monitoring_plugin.py +1299 -0
  121. plugins/query_enhancer_plugin.py +350 -0
  122. plugins/save_conversation_plugin.py +502 -0
  123. plugins/system_commands_plugin.py +93 -0
  124. plugins/tmux_plugin.py +795 -0
  125. plugins/workflow_enforcement_plugin.py +629 -0
  126. system_prompt/default.md +1286 -0
  127. system_prompt/default_win.md +265 -0
  128. system_prompt/example_with_trender.md +47 -0
@@ -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