Skip to main content

winx_code_agent/
errors.rs

1use std::path::PathBuf;
2use thiserror::Error;
3
4/// Errors that can occur in the Winx application
5#[derive(Error, Debug)]
6pub enum WinxError {
7    /// Error when initializing the shell
8    #[error("Failed to initialize shell: {0}")]
9    ShellInitializationError(String),
10
11    /// Error when operating on a workspace path
12    #[error("Workspace path error: {0}")]
13    WorkspacePathError(String),
14
15    /// Error when locking the bash state
16    #[error("Failed to lock the bash state: {0}")]
17    BashStateLockError(String),
18
19    /// Error when the bash state is not initialized
20    #[error("Bash state not initialized. Please call Initialize first with type=\"first_call\" and a valid workspace path.")]
21    BashStateNotInitialized,
22
23    /// Error when a command fails to execute
24    #[error("Command execution failed: {0}")]
25    CommandExecutionError(String),
26
27    /// Error when parsing arguments
28    #[error("Failed to parse arguments: {0}")]
29    ArgumentParseError(String),
30
31    /// Error when trying to access a file or directory
32    #[error("File access error for {path}: {message}")]
33    FileAccessError { path: PathBuf, message: String },
34
35    /// Security error - path traversal or symlink escape attempt
36    #[error("Security violation: {message}")]
37    PathSecurityError { path: PathBuf, message: String },
38
39    /// Error when a command is not allowed in the current mode
40    #[error("Command not allowed: {0}")]
41    CommandNotAllowed(String),
42
43    /// Error when chat IDs don't match
44    #[error("Thread ID mismatch: {0}")]
45    ThreadIdMismatch(String),
46
47    /// Error when deserializing data
48    #[error("Deserialization error: {0}")]
49    DeserializationError(String),
50
51    /// Error when serializing data
52    #[error("Serialization error: {0}")]
53    SerializationError(String),
54
55    /// Error in the search/replace format
56    #[error("Search/replace syntax error: {0}")]
57    SearchReplaceSyntaxError(String),
58
59    /// Error when search block is not found in content
60    #[error("Search block not found in content: {0}")]
61    SearchBlockNotFound(String),
62
63    /// Error when search block matches multiple locations (WCGW-style)
64    #[error("Search block matched multiple times")]
65    SearchBlockAmbiguous { block_content: String, match_count: usize, suggestions: Vec<String> },
66
67    /// Error when search blocks have conflicting matches
68    #[error("Multiple search blocks have conflicting matches")]
69    SearchBlockConflict { conflicting_blocks: Vec<String>, first_differing_block: Option<String> },
70
71    /// Enhanced search/replace syntax error with detailed context
72    #[error("Search/replace syntax error: {message}")]
73    SearchReplaceSyntaxErrorDetailed {
74        message: String,
75        line_number: Option<usize>,
76        block_type: Option<String>,
77        suggestions: Vec<String>,
78    },
79
80    /// Error when JSON parsing fails
81    #[error("Invalid JSON: {0}")]
82    JsonParseError(String),
83
84    /// Error when a file is too large for operation
85    #[error("File {path} is too large: {size} bytes (max {max_size})")]
86    FileTooLarge { path: PathBuf, size: u64, max_size: u64 },
87
88    /// Error when writing to a file
89    #[error("Failed to write file {path}: {message}")]
90    FileWriteError { path: PathBuf, message: String },
91
92    /// Error loading data
93    #[error("Failed to load data: {0}")]
94    DataLoadingError(String),
95
96    /// Parameter validation error
97    #[error("Invalid parameter: {field} - {message}")]
98    ParameterValidationError { field: String, message: String },
99
100    /// Required parameter missing error
101    #[error("Required parameter missing: {field} - {message}")]
102    MissingParameterError { field: String, message: String },
103
104    /// Null or undefined value error
105    #[error("Null or undefined value where object expected: {field}")]
106    NullValueError { field: String },
107
108    /// Recovery suggestion error with potential solutions
109    #[error("{message} - {suggestion}")]
110    RecoverableSuggestionError { message: String, suggestion: String },
111
112    /// Context save error
113    #[error("Context save error: {0}")]
114    ContextSaveError(String),
115
116    /// Command timeout error
117    #[error("Command timed out after {timeout_seconds}s: {command}")]
118    CommandTimeout { command: String, timeout_seconds: u64 },
119
120    /// Interactive command detected error
121    #[error(
122        "Interactive command detected: {command}. Use appropriate flags or consider alternatives."
123    )]
124    InteractiveCommandDetected { command: String },
125
126    /// Command already running error
127    #[error("A command is already running: '{current_command}' (for {duration_seconds:.1}s). Use status_check, send_text, or interrupt.")]
128    CommandAlreadyRunning { current_command: String, duration_seconds: f64 },
129
130    /// Process cleanup error
131    #[error("Failed to cleanup process: {message}")]
132    ProcessCleanupError { message: String },
133
134    /// Buffer overflow error
135    #[error("Command output exceeded maximum size: {size} bytes (max {max_size})")]
136    BufferOverflow { size: usize, max_size: usize },
137
138    /// Session recovery error
139    #[error("Failed to recover bash session: {message}")]
140    SessionRecoveryError { message: String },
141
142    /// Resource allocation error
143    #[error("Resource allocation failed: {message}")]
144    ResourceAllocationError { message: String },
145
146    /// IO error
147    #[error("IO error: {0}")]
148    IoError(#[from] std::io::Error),
149
150    /// Configuration error
151    #[error("Configuration error: {0}")]
152    ConfigurationError(String),
153
154    /// Parse error for responses
155    #[error("Parse error: {0}")]
156    ParseError(String),
157
158    /// Invalid input error
159    #[error("Invalid input: {0}")]
160    InvalidInput(String),
161
162    /// File error for file operations
163    #[error("File error: {0}")]
164    FileError(String),
165}
166
167/// Type alias for Result with `WinxError`
168pub type Result<T> = std::result::Result<T, WinxError>;
169
170/// Conversion from `anyhow::Error` to `WinxError`
171impl From<anyhow::Error> for WinxError {
172    fn from(error: anyhow::Error) -> Self {
173        WinxError::CommandExecutionError(format!("{error}"))
174    }
175}
176
177/// Advanced error recovery and suggestion options
178pub struct ErrorRecovery;
179
180impl ErrorRecovery {
181    pub fn suggest(error: WinxError, _suggestion: &str) -> WinxError {
182        error
183    }
184
185    pub fn param_error(field: &str, message: &str) -> WinxError {
186        WinxError::ParameterValidationError {
187            field: field.to_string(),
188            message: message.to_string(),
189        }
190    }
191
192    pub fn missing_param(field: &str, message: &str) -> WinxError {
193        WinxError::MissingParameterError { field: field.to_string(), message: message.to_string() }
194    }
195
196    pub fn null_value(field: &str) -> WinxError {
197        WinxError::NullValueError { field: field.to_string() }
198    }
199}
200
201/// Enable cloning for `WinxError`
202impl Clone for WinxError {
203    fn clone(&self) -> Self {
204        match self {
205            Self::ShellInitializationError(msg) => Self::ShellInitializationError(msg.clone()),
206            Self::WorkspacePathError(msg) => Self::WorkspacePathError(msg.clone()),
207            Self::BashStateLockError(msg) => Self::BashStateLockError(msg.clone()),
208            Self::BashStateNotInitialized => Self::BashStateNotInitialized,
209            Self::CommandExecutionError(msg) => Self::CommandExecutionError(msg.clone()),
210            Self::CommandNotAllowed(msg) => Self::CommandNotAllowed(msg.clone()),
211            Self::ThreadIdMismatch(msg) => Self::ThreadIdMismatch(msg.clone()),
212            Self::ArgumentParseError(msg) => Self::ArgumentParseError(msg.clone()),
213            Self::FileAccessError { path, message } => {
214                Self::FileAccessError { path: path.clone(), message: message.clone() }
215            }
216            Self::DeserializationError(msg) => Self::DeserializationError(msg.clone()),
217            Self::SerializationError(msg) => Self::SerializationError(msg.clone()),
218            Self::SearchReplaceSyntaxError(msg) => Self::SearchReplaceSyntaxError(msg.clone()),
219            Self::SearchBlockNotFound(msg) => Self::SearchBlockNotFound(msg.clone()),
220            Self::SearchBlockAmbiguous { block_content, match_count, suggestions } => {
221                Self::SearchBlockAmbiguous {
222                    block_content: block_content.clone(),
223                    match_count: *match_count,
224                    suggestions: suggestions.clone(),
225                }
226            }
227            Self::SearchBlockConflict { conflicting_blocks, first_differing_block } => {
228                Self::SearchBlockConflict {
229                    conflicting_blocks: conflicting_blocks.clone(),
230                    first_differing_block: first_differing_block.clone(),
231                }
232            }
233            Self::SearchReplaceSyntaxErrorDetailed {
234                message,
235                line_number,
236                block_type,
237                suggestions,
238            } => Self::SearchReplaceSyntaxErrorDetailed {
239                message: message.clone(),
240                line_number: *line_number,
241                block_type: block_type.clone(),
242                suggestions: suggestions.clone(),
243            },
244            Self::JsonParseError(msg) => Self::JsonParseError(msg.clone()),
245            Self::FileTooLarge { path, size, max_size } => {
246                Self::FileTooLarge { path: path.clone(), size: *size, max_size: *max_size }
247            }
248            Self::FileWriteError { path, message } => {
249                Self::FileWriteError { path: path.clone(), message: message.clone() }
250            }
251            Self::DataLoadingError(msg) => Self::DataLoadingError(msg.clone()),
252            Self::ParameterValidationError { field, message } => {
253                Self::ParameterValidationError { field: field.clone(), message: message.clone() }
254            }
255            Self::MissingParameterError { field, message } => {
256                Self::MissingParameterError { field: field.clone(), message: message.clone() }
257            }
258            Self::NullValueError { field } => Self::NullValueError { field: field.clone() },
259            Self::RecoverableSuggestionError { message, suggestion } => {
260                Self::RecoverableSuggestionError {
261                    message: message.clone(),
262                    suggestion: suggestion.clone(),
263                }
264            }
265            Self::ContextSaveError(msg) => Self::ContextSaveError(msg.clone()),
266            Self::CommandTimeout { command, timeout_seconds } => {
267                Self::CommandTimeout { command: command.clone(), timeout_seconds: *timeout_seconds }
268            }
269            Self::InteractiveCommandDetected { command } => {
270                Self::InteractiveCommandDetected { command: command.clone() }
271            }
272            Self::CommandAlreadyRunning { current_command, duration_seconds } => {
273                Self::CommandAlreadyRunning {
274                    current_command: current_command.clone(),
275                    duration_seconds: *duration_seconds,
276                }
277            }
278            Self::ProcessCleanupError { message } => {
279                Self::ProcessCleanupError { message: message.clone() }
280            }
281            Self::BufferOverflow { size, max_size } => {
282                Self::BufferOverflow { size: *size, max_size: *max_size }
283            }
284            Self::SessionRecoveryError { message } => {
285                Self::SessionRecoveryError { message: message.clone() }
286            }
287            Self::ResourceAllocationError { message } => {
288                Self::ResourceAllocationError { message: message.clone() }
289            }
290            Self::IoError(err) => Self::IoError(std::io::Error::new(err.kind(), err.to_string())),
291            Self::ConfigurationError(msg) => Self::ConfigurationError(msg.clone()),
292            Self::ParseError(msg) => Self::ParseError(msg.clone()),
293            Self::InvalidInput(msg) => Self::InvalidInput(msg.clone()),
294            Self::FileError(msg) => Self::FileError(msg.clone()),
295            Self::PathSecurityError { path, message } => {
296                Self::PathSecurityError { path: path.clone(), message: message.clone() }
297            }
298        }
299    }
300}