1use super::string_utils::safe_truncate;
4use super::task::Task;
5
6#[derive(Debug, Clone, serde::Serialize)]
8pub struct ClarificationRequest {
9 pub reason: String,
10 pub message: String,
11 pub suggestions: Vec<String>,
12}
13
14#[derive(Debug, Clone)]
16pub enum ClarificationResponse {
17 Continue(Option<String>),
19 Stop,
21}
22
23pub trait EventSink: Send {
25 fn on_turn_start(&mut self) {}
27 fn on_text(&mut self, text: &str);
29 fn on_tool_call(&mut self, name: &str, arguments: &str);
31 fn on_tool_result(&mut self, name: &str, result: &str, is_error: bool);
33 fn on_command_started(&mut self, _command: &str) {}
35 fn on_command_output(&mut self, _stream: &str, _chunk: &str) {}
37 fn on_command_finished(&mut self, _success: bool, _exit_code: i32, _duration_ms: u64) {}
39 fn on_preview_started(&mut self, _path: &str, _port: u16) {}
41 fn on_preview_ready(&mut self, _url: &str, _port: u16) {}
43 fn on_preview_failed(&mut self, _message: &str) {}
45 fn on_preview_stopped(&mut self, _reason: &str) {}
47 fn on_swarm_started(&mut self, _description: &str) {}
49 fn on_swarm_progress(&mut self, _status: &str) {}
51 fn on_swarm_finished(&mut self, _summary: &str) {}
53 fn on_swarm_failed(&mut self, _message: &str) {}
55 fn on_confirmation_request(&mut self, prompt: &str) -> bool;
58 fn on_text_chunk(&mut self, _chunk: &str) {}
60 fn on_task_plan(&mut self, _tasks: &[Task]) {}
62 fn on_task_progress(&mut self, _task_id: u32, _completed: bool, _tasks: &[Task]) {}
65 fn on_clarification_request(
68 &mut self,
69 _request: &ClarificationRequest,
70 ) -> ClarificationResponse {
71 ClarificationResponse::Stop
72 }
73}
74
75pub struct SilentEventSink;
78
79impl EventSink for SilentEventSink {
80 fn on_text(&mut self, _text: &str) {}
81 fn on_tool_call(&mut self, _name: &str, _arguments: &str) {}
82 fn on_tool_result(&mut self, _name: &str, _result: &str, _is_error: bool) {}
83 fn on_confirmation_request(&mut self, _prompt: &str) -> bool {
84 true }
86}
87
88const SECTION_SEP: &str = "──────────────────────────────────────";
90
91pub struct TerminalEventSink {
93 pub verbose: bool,
94 streamed_text: bool,
95 execution_section_shown: bool,
97 result_section_shown: bool,
99}
100
101impl TerminalEventSink {
102 pub fn new(verbose: bool) -> Self {
103 Self {
104 verbose,
105 streamed_text: false,
106 execution_section_shown: false,
107 result_section_shown: false,
108 }
109 }
110
111 #[inline]
112 fn msg(&self, s: &str) {
113 eprintln!("{}", s);
114 }
115
116 #[inline]
117 fn msg_opt(&self, s: &str) {
118 if !s.is_empty() {
119 for line in s.lines() {
120 eprintln!("{}", line);
121 }
122 }
123 }
124
125 fn show_execution_section(&mut self) {
126 if !self.execution_section_shown {
127 self.execution_section_shown = true;
128 self.msg(&format!("─── 🔧 执行 ─── {}", SECTION_SEP));
129 }
130 }
131
132 fn show_result_section(&mut self) {
133 if !self.result_section_shown {
134 self.result_section_shown = true;
135 self.msg(&format!("─── 📄 结果 ─── {}", SECTION_SEP));
136 self.msg("");
137 }
138 }
139}
140
141impl EventSink for TerminalEventSink {
142 fn on_turn_start(&mut self) {
143 self.execution_section_shown = false;
144 self.result_section_shown = false;
145 }
146
147 fn on_text(&mut self, text: &str) {
148 if self.streamed_text {
149 self.streamed_text = false;
153 return;
154 }
155 if !text.trim().is_empty() {
158 self.show_result_section();
159 }
160 use std::io::Write;
161 print!("{}", text);
162 let _ = std::io::stdout().flush();
163 println!();
164 }
165
166 fn on_text_chunk(&mut self, chunk: &str) {
167 self.streamed_text = true;
168 if !chunk.trim().is_empty() {
170 self.show_result_section();
171 }
172 use std::io::Write;
173 print!("{}", chunk);
174 let _ = std::io::stdout().flush();
175 }
176
177 fn on_tool_call(&mut self, name: &str, arguments: &str) {
178 self.show_execution_section();
179 if self.verbose {
180 let args_display = if arguments.len() > 200 {
182 format!("{}…", safe_truncate(arguments, 200))
183 } else {
184 arguments.to_string()
185 };
186 self.msg(&format!("🔧 Tool: {} args={}", name, args_display));
187 } else {
188 self.msg(&format!("🔧 {}", name));
189 }
190 }
191
192 fn on_tool_result(&mut self, name: &str, result: &str, is_error: bool) {
193 let icon = if is_error { "❌" } else { "✅" };
194 if self.verbose {
195 let brief = if result.len() > 400 {
196 format!("{}…", safe_truncate(result, 400))
197 } else {
198 result.to_string()
199 };
200 self.msg(&format!(" {} {}: {}", icon, name, brief));
201 } else {
202 let first = result.lines().next().unwrap_or("(ok)");
203 let brief = if first.len() > 80 {
204 format!("{}…", safe_truncate(first, 80))
205 } else {
206 first.to_string()
207 };
208 self.msg(&format!(" {} {} {}", icon, name, brief));
209 }
210 }
211
212 fn on_command_started(&mut self, command: &str) {
213 self.show_execution_section();
214 let brief = if command.len() > 120 {
215 format!("{}…", safe_truncate(command, 120))
216 } else {
217 command.to_string()
218 };
219 self.msg(&format!(" ▶ command started: {}", brief));
220 }
221
222 fn on_command_output(&mut self, stream: &str, chunk: &str) {
223 if chunk.is_empty() {
224 return;
225 }
226 self.show_execution_section();
227 let prefix = if stream == "stderr" { " ! " } else { " │ " };
228 for line in chunk.lines() {
229 self.msg(&format!("{}{}", prefix, line));
230 }
231 }
232
233 fn on_command_finished(&mut self, success: bool, exit_code: i32, duration_ms: u64) {
234 self.show_execution_section();
235 let icon = if success { " ■" } else { " ✗" };
236 self.msg(&format!(
237 "{} command finished: exit {} ({} ms)",
238 icon, exit_code, duration_ms
239 ));
240 }
241
242 fn on_preview_started(&mut self, path: &str, port: u16) {
243 self.show_execution_section();
244 self.msg(&format!(" ▶ preview started: {} (port {})", path, port));
245 }
246
247 fn on_preview_ready(&mut self, url: &str, _port: u16) {
248 self.show_execution_section();
249 self.msg(&format!(" ■ preview ready: {}", url));
250 }
251
252 fn on_preview_failed(&mut self, message: &str) {
253 self.show_execution_section();
254 self.msg(&format!(" ✗ preview failed: {}", message));
255 }
256
257 fn on_preview_stopped(&mut self, reason: &str) {
258 self.show_execution_section();
259 self.msg(&format!(" ■ preview stopped: {}", reason));
260 }
261
262 fn on_swarm_started(&mut self, description: &str) {
263 self.show_execution_section();
264 let brief = if description.len() > 120 {
265 format!("{}…", safe_truncate(description, 120))
266 } else {
267 description.to_string()
268 };
269 self.msg(&format!(" ▶ swarm started: {}", brief));
270 }
271
272 fn on_swarm_progress(&mut self, status: &str) {
273 self.show_execution_section();
274 self.msg(&format!(" … swarm: {}", status));
275 }
276
277 fn on_swarm_finished(&mut self, summary: &str) {
278 self.show_execution_section();
279 let brief = if summary.len() > 160 {
280 format!("{}…", safe_truncate(summary, 160))
281 } else {
282 summary.to_string()
283 };
284 self.msg(&format!(" ■ swarm finished: {}", brief));
285 }
286
287 fn on_swarm_failed(&mut self, message: &str) {
288 self.show_execution_section();
289 let brief = if message.len() > 160 {
290 format!("{}…", safe_truncate(message, 160))
291 } else {
292 message.to_string()
293 };
294 self.msg(&format!(" ✗ swarm failed: {}", brief));
295 }
296
297 fn on_confirmation_request(&mut self, prompt: &str) -> bool {
298 use std::io::Write;
299 self.msg_opt(prompt);
300 eprint!("确认执行? [y/N] ");
301 let _ = std::io::stderr().flush();
302 let mut input = String::new();
303 if std::io::stdin().read_line(&mut input).is_ok() {
304 let trimmed = input.trim().to_lowercase();
305 trimmed == "y" || trimmed == "yes"
306 } else {
307 false
308 }
309 }
310
311 fn on_clarification_request(
312 &mut self,
313 request: &ClarificationRequest,
314 ) -> ClarificationResponse {
315 use std::io::Write;
316 self.msg(&format!("─── ⚠ 需要确认 ─── {}", SECTION_SEP));
317 self.msg(&format!("原因: {}", request.reason));
318 self.msg(&request.message);
319 self.msg("");
320 for (i, s) in request.suggestions.iter().enumerate() {
321 self.msg(&format!(" [{}] {}", i + 1, s));
322 }
323 self.msg(" [0] 停止");
324 eprint!("请选择 (或直接输入补充信息): ");
325 let _ = std::io::stderr().flush();
326 let mut input = String::new();
327 if std::io::stdin().read_line(&mut input).is_ok() {
328 let trimmed = input.trim();
329 if trimmed == "0" {
330 return ClarificationResponse::Stop;
331 }
332 if let Ok(idx) = trimmed.parse::<usize>() {
333 if idx >= 1 && idx <= request.suggestions.len() {
334 return ClarificationResponse::Continue(Some(
335 request.suggestions[idx - 1].clone(),
336 ));
337 }
338 }
339 if !trimmed.is_empty() {
340 return ClarificationResponse::Continue(Some(trimmed.to_string()));
341 }
342 }
343 ClarificationResponse::Stop
344 }
345
346 fn on_task_plan(&mut self, tasks: &[Task]) {
347 self.msg(&format!("─── 📋 计划 ─── {}", SECTION_SEP));
348 self.msg(&format!("Task plan ({} tasks):", tasks.len()));
349 for task in tasks {
350 let status = if task.completed { "✅" } else { "○" };
351 let hint = task
352 .tool_hint
353 .as_deref()
354 .map(|h| format!(" [{}]", h))
355 .unwrap_or_default();
356 self.msg(&format!(
357 " {}. {} {}{}",
358 task.id, status, task.description, hint
359 ));
360 }
361 }
362
363 fn on_task_progress(&mut self, task_id: u32, completed: bool, tasks: &[Task]) {
364 if completed {
365 self.msg(&format!(" ✅ Task {} completed", task_id));
366 }
367 if !tasks.is_empty() {
368 let completed_count = tasks.iter().filter(|t| t.completed).count();
369 self.msg(&format!(" 📋 进度 ({}/{}):", completed_count, tasks.len()));
370 for task in tasks {
371 let status = if task.completed {
372 "✅"
373 } else if task.id
374 == tasks
375 .iter()
376 .find(|t| !t.completed)
377 .map(|t| t.id)
378 .unwrap_or(0)
379 {
380 "▶"
381 } else {
382 "○"
383 };
384 let hint = task
385 .tool_hint
386 .as_deref()
387 .map(|h| format!(" [{}]", h))
388 .unwrap_or_default();
389 self.msg(&format!(
390 " {}. {} {}{}",
391 task.id, status, task.description, hint
392 ));
393 }
394 }
395 }
396}
397
398pub struct RunModeEventSink {
402 inner: TerminalEventSink,
403}
404
405impl RunModeEventSink {
406 pub fn new(verbose: bool) -> Self {
407 Self {
408 inner: TerminalEventSink::new(verbose),
409 }
410 }
411}
412
413impl EventSink for RunModeEventSink {
414 fn on_turn_start(&mut self) {
415 self.inner.on_turn_start();
416 }
417 fn on_text(&mut self, text: &str) {
418 self.inner.on_text(text);
419 }
420 fn on_text_chunk(&mut self, chunk: &str) {
421 self.inner.on_text_chunk(chunk);
422 }
423 fn on_tool_call(&mut self, name: &str, arguments: &str) {
424 self.inner.on_tool_call(name, arguments);
425 }
426 fn on_tool_result(&mut self, name: &str, result: &str, is_error: bool) {
427 self.inner.on_tool_result(name, result, is_error);
428 }
429 fn on_command_started(&mut self, command: &str) {
430 self.inner.on_command_started(command);
431 }
432 fn on_command_output(&mut self, stream: &str, chunk: &str) {
433 self.inner.on_command_output(stream, chunk);
434 }
435 fn on_command_finished(&mut self, success: bool, exit_code: i32, duration_ms: u64) {
436 self.inner
437 .on_command_finished(success, exit_code, duration_ms);
438 }
439 fn on_preview_started(&mut self, path: &str, port: u16) {
440 self.inner.on_preview_started(path, port);
441 }
442 fn on_preview_ready(&mut self, url: &str, port: u16) {
443 self.inner.on_preview_ready(url, port);
444 }
445 fn on_preview_failed(&mut self, message: &str) {
446 self.inner.on_preview_failed(message);
447 }
448 fn on_preview_stopped(&mut self, reason: &str) {
449 self.inner.on_preview_stopped(reason);
450 }
451 fn on_swarm_started(&mut self, description: &str) {
452 self.inner.on_swarm_started(description);
453 }
454 fn on_swarm_progress(&mut self, status: &str) {
455 self.inner.on_swarm_progress(status);
456 }
457 fn on_swarm_finished(&mut self, summary: &str) {
458 self.inner.on_swarm_finished(summary);
459 }
460 fn on_swarm_failed(&mut self, message: &str) {
461 self.inner.on_swarm_failed(message);
462 }
463 fn on_confirmation_request(&mut self, prompt: &str) -> bool {
464 if !prompt.is_empty() {
465 for line in prompt.lines() {
466 eprintln!("{}", line);
467 }
468 }
469 eprintln!(" [run mode: auto-approved]");
470 true
471 }
472 fn on_clarification_request(
473 &mut self,
474 request: &ClarificationRequest,
475 ) -> ClarificationResponse {
476 eprintln!(
477 " [run mode: auto-stop on clarification] reason={} msg={}",
478 request.reason, request.message
479 );
480 ClarificationResponse::Stop
481 }
482 fn on_task_plan(&mut self, tasks: &[Task]) {
483 self.inner.on_task_plan(tasks);
484 }
485 fn on_task_progress(&mut self, task_id: u32, completed: bool, tasks: &[Task]) {
486 self.inner.on_task_progress(task_id, completed, tasks);
487 }
488}