1use crate::output::{render_output, MultiFormatDisplay, OutputFormat};
4use crate::types::OperationResult;
5use anyhow::Result;
6use clap::Subcommand;
7use comfy_table::{presets::UTF8_FULL, Cell, Color, ContentArrangement, Table};
8use serde::Serialize;
9
10#[derive(Subcommand)]
11pub enum DebugCommands {
12 #[command(alias = "connect")]
14 Attach {
15 agent_id: String,
17 #[arg(short, long, default_value = "9229")]
19 port: u16,
20 },
21 #[command(aliases = ["track", "follow"])]
23 Trace {
24 agent_id: String,
26 #[arg(short, long, default_value = "10")]
28 duration: u64,
29 #[arg(short = 'f', long = "file")]
31 output_file: Option<String>,
32 },
33 #[command(aliases = ["snapshot", "export"])]
35 Dump {
36 agent_id: String,
38 #[arg(short, long)]
40 memory: bool,
41 #[arg(long, default_value = "json")]
43 dump_format: String,
44 },
45 #[command(aliases = ["prof", "perf"])]
47 Profile {
48 agent_id: String,
50 #[arg(long)]
52 cpu: bool,
53 #[arg(long)]
55 mem: bool,
56 },
57}
58
59#[derive(Debug, Serialize)]
60struct DebugSession {
61 agent_id: String,
62 session_id: String,
63 debugger_url: String,
64 status: String,
65}
66
67impl MultiFormatDisplay for DebugSession {
68 fn to_table(&self) -> Table {
69 let mut table = Table::new();
70 table
71 .load_preset(UTF8_FULL)
72 .set_content_arrangement(ContentArrangement::Dynamic);
73
74 table.add_row(vec![
75 Cell::new("Agent ID").fg(Color::Cyan),
76 Cell::new(&self.agent_id),
77 ]);
78 table.add_row(vec![
79 Cell::new("Session ID").fg(Color::Cyan),
80 Cell::new(&self.session_id),
81 ]);
82 table.add_row(vec![
83 Cell::new("Debugger URL").fg(Color::Cyan),
84 Cell::new(&self.debugger_url),
85 ]);
86 table.add_row(vec![
87 Cell::new("Status").fg(Color::Cyan),
88 Cell::new(&self.status).fg(Color::Green),
89 ]);
90
91 table
92 }
93
94 fn to_quiet(&self) -> String {
95 self.debugger_url.clone()
96 }
97}
98
99#[derive(Debug, Serialize)]
100struct TraceResult {
101 agent_id: String,
102 duration_secs: u64,
103 events_captured: u64,
104 output_file: String,
105 file_size_kb: u64,
106}
107
108impl MultiFormatDisplay for TraceResult {
109 fn to_table(&self) -> Table {
110 let mut table = Table::new();
111 table
112 .load_preset(UTF8_FULL)
113 .set_content_arrangement(ContentArrangement::Dynamic);
114
115 table.add_row(vec![
116 Cell::new("Agent ID").fg(Color::Cyan),
117 Cell::new(&self.agent_id),
118 ]);
119 table.add_row(vec![
120 Cell::new("Duration").fg(Color::Cyan),
121 Cell::new(format!("{}s", self.duration_secs)),
122 ]);
123 table.add_row(vec![
124 Cell::new("Events Captured").fg(Color::Cyan),
125 Cell::new(self.events_captured.to_string()),
126 ]);
127 table.add_row(vec![
128 Cell::new("Output File").fg(Color::Cyan),
129 Cell::new(&self.output_file),
130 ]);
131 table.add_row(vec![
132 Cell::new("File Size").fg(Color::Cyan),
133 Cell::new(format!("{} KB", self.file_size_kb)),
134 ]);
135
136 table
137 }
138
139 fn to_quiet(&self) -> String {
140 self.output_file.clone()
141 }
142}
143
144#[derive(Debug, Serialize)]
145struct AgentDump {
146 agent_id: String,
147 timestamp: String,
148 state: String,
149 memory_mb: f64,
150 stack_depth: usize,
151 local_variables: usize,
152}
153
154impl MultiFormatDisplay for AgentDump {
155 fn to_table(&self) -> Table {
156 let mut table = Table::new();
157 table
158 .load_preset(UTF8_FULL)
159 .set_content_arrangement(ContentArrangement::Dynamic);
160
161 table.add_row(vec![
162 Cell::new("Agent ID").fg(Color::Cyan),
163 Cell::new(&self.agent_id),
164 ]);
165 table.add_row(vec![
166 Cell::new("Timestamp").fg(Color::Cyan),
167 Cell::new(&self.timestamp),
168 ]);
169 table.add_row(vec![
170 Cell::new("State").fg(Color::Cyan),
171 Cell::new(&self.state).fg(Color::Green),
172 ]);
173 table.add_row(vec![
174 Cell::new("Memory").fg(Color::Cyan),
175 Cell::new(format!("{:.2} MB", self.memory_mb)),
176 ]);
177 table.add_row(vec![
178 Cell::new("Stack Depth").fg(Color::Cyan),
179 Cell::new(self.stack_depth.to_string()),
180 ]);
181 table.add_row(vec![
182 Cell::new("Local Variables").fg(Color::Cyan),
183 Cell::new(self.local_variables.to_string()),
184 ]);
185
186 table
187 }
188}
189
190pub async fn handle_debug_command(action: DebugCommands, format: OutputFormat) -> Result<()> {
191 match action {
192 DebugCommands::Attach { agent_id, port } => {
193 let session = mock_debug_session(&agent_id, port);
194 println!("{}", render_output(&session, format)?);
195 }
196 DebugCommands::Trace {
197 agent_id,
198 duration,
199 output_file,
200 } => {
201 let output_path = output_file.unwrap_or_else(|| {
202 format!("trace-{}-{}.json", agent_id, chrono::Utc::now().timestamp())
203 });
204
205 let trace = mock_trace_result(&agent_id, duration, &output_path);
206 println!("{}", render_output(&trace, format)?);
207 }
208 DebugCommands::Dump {
209 agent_id,
210 memory,
211 dump_format,
212 } => {
213 let dump = mock_agent_dump(&agent_id);
214
215 if memory {
216 let result = OperationResult {
217 success: true,
218 message: format!(
219 "Agent {} state dumped to agent-{}.{} (with memory snapshot)",
220 agent_id, agent_id, dump_format
221 ),
222 id: Some(agent_id),
223 };
224 println!("{}", render_output(&result, format)?);
225 } else {
226 println!("{}", render_output(&dump, format)?);
227 }
228 }
229 DebugCommands::Profile { agent_id, cpu, mem } => {
230 let profile_types = match (cpu, mem) {
231 (true, true) => "CPU and memory".to_string(),
232 (true, false) => "CPU".to_string(),
233 (false, true) => "memory".to_string(),
234 (false, false) => "CPU and memory".to_string(), };
236
237 let result = OperationResult {
238 success: true,
239 message: format!(
240 "Profiling agent {} ({} profile available at /tmp/profile-{}.pb.gz)",
241 agent_id, profile_types, agent_id
242 ),
243 id: Some(agent_id),
244 };
245 println!("{}", render_output(&result, format)?);
246 }
247 }
248 Ok(())
249}
250
251fn mock_debug_session(agent_id: &str, port: u16) -> DebugSession {
252 DebugSession {
253 agent_id: agent_id.to_string(),
254 session_id: format!("debug-session-{}", uuid::Uuid::new_v4()),
255 debugger_url: format!("ws://localhost:{}/debugger/{}", port, agent_id),
256 status: "Attached".to_string(),
257 }
258}
259
260fn mock_trace_result(agent_id: &str, duration: u64, output_file: &str) -> TraceResult {
261 TraceResult {
262 agent_id: agent_id.to_string(),
263 duration_secs: duration,
264 events_captured: duration * 1250, output_file: output_file.to_string(),
266 file_size_kb: duration * 85, }
268}
269
270fn mock_agent_dump(agent_id: &str) -> AgentDump {
271 AgentDump {
272 agent_id: agent_id.to_string(),
273 timestamp: chrono::Utc::now().to_rfc3339(),
274 state: "Running".to_string(),
275 memory_mb: 12.5,
276 stack_depth: 8,
277 local_variables: 24,
278 }
279}