Skip to main content

hh_cli/cli/tui/
event.rs

1use std::sync::Arc;
2use std::sync::Mutex;
3
4use serde_json::Value;
5use tokio::sync::mpsc;
6use tokio::sync::oneshot;
7
8use crate::core::agent::AgentEvents;
9
10#[derive(Debug, Clone)]
11pub struct SubagentEventItem {
12    pub task_id: String,
13    pub name: String,
14    pub agent_name: String,
15    pub status: String,
16    pub prompt: String,
17    pub depth: usize,
18    pub parent_task_id: Option<String>,
19    pub started_at: u64,
20    pub finished_at: Option<u64>,
21    pub summary: Option<String>,
22    pub error: Option<String>,
23}
24
25type QuestionResponder =
26    Arc<Mutex<Option<oneshot::Sender<anyhow::Result<crate::core::QuestionAnswers>>>>>;
27
28#[derive(Debug)]
29pub struct ScopedTuiEvent {
30    pub session_epoch: u64,
31    pub run_epoch: u64,
32    pub event: TuiEvent,
33}
34
35#[derive(Debug)]
36pub enum TuiEvent {
37    Thinking(String),
38    ToolStart {
39        name: String,
40        args: Value,
41    },
42    ToolEnd {
43        name: String,
44        result: crate::tool::ToolResult,
45    },
46    TodoItemsChanged(Vec<crate::core::TodoItem>),
47    AssistantDelta(String),
48    ContextUsage(usize),
49    AssistantDone,
50    SessionTitle(String),
51    CompactionStart,
52    CompactionDone(String),
53    QuestionPrompt {
54        questions: Vec<crate::core::QuestionPrompt>,
55        responder: QuestionResponder,
56    },
57    SubagentsChanged(Vec<SubagentEventItem>),
58    Error(String),
59    Key(crossterm::event::KeyEvent),
60    Tick,
61}
62
63/// Sends agent events to a channel for the TUI to consume
64#[derive(Clone)]
65pub struct TuiEventSender {
66    tx: Arc<mpsc::UnboundedSender<ScopedTuiEvent>>,
67    session_epoch: u64,
68    run_epoch: u64,
69}
70
71impl TuiEventSender {
72    pub fn new(tx: mpsc::UnboundedSender<ScopedTuiEvent>) -> Self {
73        Self {
74            tx: Arc::new(tx),
75            session_epoch: 0,
76            run_epoch: 0,
77        }
78    }
79
80    pub fn scoped(&self, session_epoch: u64, run_epoch: u64) -> Self {
81        Self {
82            tx: Arc::clone(&self.tx),
83            session_epoch,
84            run_epoch,
85        }
86    }
87
88    pub fn send(&self, event: TuiEvent) {
89        let _ = self.tx.send(ScopedTuiEvent {
90            session_epoch: self.session_epoch,
91            run_epoch: self.run_epoch,
92            event,
93        });
94    }
95}
96
97impl AgentEvents for TuiEventSender {
98    fn on_thinking(&self, text: &str) {
99        self.send(TuiEvent::Thinking(text.to_string()));
100    }
101
102    fn on_tool_start(&self, name: &str, args: &Value) {
103        self.send(TuiEvent::ToolStart {
104            name: name.to_string(),
105            args: args.clone(),
106        });
107    }
108
109    fn on_tool_end(&self, name: &str, result: &crate::tool::ToolResult) {
110        self.send(TuiEvent::ToolEnd {
111            name: name.to_string(),
112            result: result.clone(),
113        });
114    }
115
116    fn on_todo_items_changed(&self, items: &[crate::core::TodoItem]) {
117        self.send(TuiEvent::TodoItemsChanged(items.to_vec()));
118    }
119
120    fn on_assistant_delta(&self, delta: &str) {
121        self.send(TuiEvent::AssistantDelta(delta.to_string()));
122    }
123
124    fn on_context_usage(&self, tokens: usize) {
125        self.send(TuiEvent::ContextUsage(tokens));
126    }
127
128    fn on_assistant_done(&self) {
129        self.send(TuiEvent::AssistantDone);
130    }
131}