agent_tui_app/app/
error.rs1use std::io;
4
5use crate::app::commands::OutputFormat;
6use crate::common::error_codes;
7use crate::common::error_codes::ErrorCategory;
8use serde::Serialize;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
12pub enum AttachError {
13 #[error("Terminal error: {0}")]
14 Terminal(#[from] io::Error),
15
16 #[error("PTY write failed: {0}")]
17 PtyWrite(String),
18
19 #[error("PTY read failed: {0}")]
20 PtyRead(String),
21
22 #[error("Event read failed")]
23 EventRead,
24}
25
26#[derive(Debug, Error)]
27#[error("{message}")]
28pub(crate) struct CliError {
29 pub(crate) exit_code: i32,
30 pub(crate) format: OutputFormat,
31 pub(crate) message: String,
32 pub(crate) json: Option<String>,
33}
34
35impl CliError {
36 pub fn new(
37 format: OutputFormat,
38 message: impl Into<String>,
39 json: Option<String>,
40 exit_code: i32,
41 ) -> Self {
42 Self {
43 exit_code,
44 format,
45 message: message.into(),
46 json,
47 }
48 }
49}
50
51#[derive(Debug, Error)]
52#[error("Daemon is not running")]
53pub(crate) struct DaemonNotRunningError;
54
55impl AttachError {
56 pub fn code(&self) -> i32 {
57 match self {
58 AttachError::Terminal(_) => error_codes::PTY_ERROR,
59 AttachError::PtyWrite(_) => error_codes::PTY_ERROR,
60 AttachError::PtyRead(_) => error_codes::PTY_ERROR,
61 AttachError::EventRead => error_codes::PTY_ERROR,
62 }
63 }
64
65 pub fn category(&self) -> ErrorCategory {
66 ErrorCategory::External
67 }
68
69 pub fn context(&self) -> AttachErrorContext {
70 match self {
71 AttachError::Terminal(e) => AttachErrorContext {
72 operation: "terminal",
73 reason: e.to_string(),
74 },
75 AttachError::PtyWrite(reason) => AttachErrorContext {
76 operation: "pty_write",
77 reason: reason.clone(),
78 },
79 AttachError::PtyRead(reason) => AttachErrorContext {
80 operation: "pty_read",
81 reason: reason.clone(),
82 },
83 AttachError::EventRead => AttachErrorContext {
84 operation: "event_read",
85 reason: "Failed to read terminal events".to_string(),
86 },
87 }
88 }
89
90 pub fn suggestion(&self) -> String {
91 match self {
92 AttachError::Terminal(_) => {
93 "Terminal mode error. Try restarting your terminal.".to_string()
94 }
95 AttachError::PtyWrite(_) => {
96 "Failed to send input to session. The session may have ended. Run 'sessions' to check status."
97 .to_string()
98 }
99 AttachError::PtyRead(_) => {
100 "Failed to read from session. The session may have ended. Run 'sessions' to check status."
101 .to_string()
102 }
103 AttachError::EventRead => {
104 "Failed to read terminal events. Try restarting your terminal.".to_string()
105 }
106 }
107 }
108
109 pub fn is_retryable(&self) -> bool {
110 matches!(self, AttachError::PtyWrite(_) | AttachError::PtyRead(_))
111 }
112
113 pub fn exit_code(&self) -> i32 {
114 match self.category() {
115 ErrorCategory::InvalidInput => 64,
116 ErrorCategory::NotFound => 69,
117 ErrorCategory::Busy => 73,
118 ErrorCategory::External => 74,
119 ErrorCategory::Internal => 74,
120 ErrorCategory::Timeout => 75,
121 }
122 }
123
124 pub fn to_payload(&self) -> AttachErrorPayload {
125 AttachErrorPayload {
126 code: self.code(),
127 message: self.to_string(),
128 category: self.category().as_str().to_string(),
129 retryable: self.is_retryable(),
130 context: self.context(),
131 suggestion: self.suggestion(),
132 }
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
137pub struct AttachErrorContext {
138 pub operation: &'static str,
139 pub reason: String,
140}
141
142#[derive(Debug, Clone, Serialize)]
143pub struct AttachErrorPayload {
144 pub code: i32,
145 pub message: String,
146 pub category: String,
147 pub retryable: bool,
148 pub context: AttachErrorContext,
149 pub suggestion: String,
150}
151
152#[cfg(test)]
153#[path = "error_tests.rs"]
154mod tests;