1#![allow(dead_code)]
3
4use std::sync::{Arc, Mutex};
5
6use tokio::time::{Duration, interval};
7
8const ALWAYS_EMITTED_HOOK_EVENTS: [&str; 2] = ["SessionStart", "Setup"];
10
11const MAX_PENDING_EVENTS: usize = 100;
12
13#[derive(Debug, Clone)]
15pub struct HookStartedEvent {
16 pub hook_id: String,
17 pub hook_name: String,
18 pub hook_event: String,
19}
20
21#[derive(Debug, Clone)]
23pub struct HookProgressEvent {
24 pub hook_id: String,
25 pub hook_name: String,
26 pub hook_event: String,
27 pub stdout: String,
28 pub stderr: String,
29 pub output: String,
30}
31
32#[derive(Debug, Clone)]
34pub struct HookResponseEvent {
35 pub hook_id: String,
36 pub hook_name: String,
37 pub hook_event: String,
38 pub output: String,
39 pub stdout: String,
40 pub stderr: String,
41 pub exit_code: Option<i32>,
42 pub outcome: HookOutcome,
43}
44
45#[derive(Debug, Clone)]
47pub enum HookExecutionEvent {
48 Started(HookStartedEvent),
49 Progress(HookProgressEvent),
50 Response(HookResponseEvent),
51}
52
53#[derive(Debug, Clone)]
55pub enum HookOutcome {
56 Success,
57 Error,
58 Cancelled,
59}
60
61pub type HookEventHandler = Box<dyn Fn(HookExecutionEvent) + Send + Sync>;
63
64pub struct ProgressOutput {
66 pub stdout: String,
67 pub stderr: String,
68 pub output: String,
69}
70
71pub struct StartHookProgressParams {
73 pub hook_id: String,
74 pub hook_name: String,
75 pub hook_event: String,
76 pub get_output: Arc<
77 dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ProgressOutput> + Send>>
78 + Send
79 + Sync,
80 >,
81 pub interval_ms: Option<u64>,
82}
83
84struct HookEventState {
86 pending_events: Vec<HookExecutionEvent>,
87 event_handler: Option<HookEventHandler>,
88 all_hook_events_enabled: bool,
89}
90
91lazy_static::lazy_static! {
92 static ref HOOK_EVENT_STATE: Arc<Mutex<HookEventState>> = Arc::new(Mutex::new(
93 HookEventState {
94 pending_events: Vec::new(),
95 event_handler: None,
96 all_hook_events_enabled: false,
97 }
98 ));
99}
100
101pub fn register_hook_event_handler(handler: Option<HookEventHandler>) {
103 let mut state = HOOK_EVENT_STATE.lock().unwrap();
104
105 let events: Vec<HookExecutionEvent> = state.pending_events.drain(..).collect();
107 state.event_handler = handler;
108
109 if let Some(ref handler) = state.event_handler {
111 for event in events {
112 handler(event);
113 }
114 }
115}
116
117fn emit(event: HookExecutionEvent) {
119 let mut state = HOOK_EVENT_STATE.lock().unwrap();
120 if let Some(ref handler) = state.event_handler {
121 handler(event);
122 } else {
123 state.pending_events.push(event);
124 if state.pending_events.len() > MAX_PENDING_EVENTS {
125 state.pending_events.remove(0);
126 }
127 }
128}
129
130fn should_emit(hook_event: &str) -> bool {
132 if ALWAYS_EMITTED_HOOK_EVENTS.contains(&hook_event) {
133 return true;
134 }
135 let state = HOOK_EVENT_STATE.lock().unwrap();
136 state.all_hook_events_enabled
137}
138
139pub fn emit_hook_started(hook_id: &str, hook_name: &str, hook_event: &str) {
141 if !should_emit(hook_event) {
142 return;
143 }
144
145 emit(HookExecutionEvent::Started(HookStartedEvent {
146 hook_id: hook_id.to_string(),
147 hook_name: hook_name.to_string(),
148 hook_event: hook_event.to_string(),
149 }));
150}
151
152pub fn emit_hook_progress(
154 hook_id: &str,
155 hook_name: &str,
156 hook_event: &str,
157 stdout: &str,
158 stderr: &str,
159 output: &str,
160) {
161 if !should_emit(hook_event) {
162 return;
163 }
164
165 emit(HookExecutionEvent::Progress(HookProgressEvent {
166 hook_id: hook_id.to_string(),
167 hook_name: hook_name.to_string(),
168 hook_event: hook_event.to_string(),
169 stdout: stdout.to_string(),
170 stderr: stderr.to_string(),
171 output: output.to_string(),
172 }));
173}
174
175pub fn start_hook_progress_interval(
178 params: StartHookProgressParams,
179) -> Arc<dyn Fn() + Send + Sync> {
180 if !should_emit(¶ms.hook_event) {
181 return Arc::new(|| {});
182 }
183
184 let interval_ms = params.interval_ms.unwrap_or(1000);
185 let hook_id = params.hook_id.clone();
186 let hook_name = params.hook_name.clone();
187 let hook_event = params.hook_event.clone();
188 let get_output = params.get_output.clone();
189
190 let stopped = Arc::new(Mutex::new(false));
191 let stopped_clone = stopped.clone();
192
193 let handle = tokio::spawn(async move {
195 let mut last_emitted_output = String::new();
196 let mut interval = interval(Duration::from_millis(interval_ms));
197
198 loop {
199 interval.tick().await;
200
201 if *stopped_clone.lock().unwrap() {
203 break;
204 }
205
206 let output = get_output().await;
207 if output.output == last_emitted_output {
208 continue;
209 }
210 last_emitted_output = output.output.clone();
211
212 emit_hook_progress(
213 &hook_id,
214 &hook_name,
215 &hook_event,
216 &output.stdout,
217 &output.stderr,
218 &output.output,
219 );
220 }
221 });
222
223 Arc::new(move || {
225 let mut stopped = stopped.lock().unwrap();
226 *stopped = true;
227 handle.abort();
228 })
229}
230
231pub struct EmitHookResponseParams {
233 pub hook_id: String,
234 pub hook_name: String,
235 pub hook_event: String,
236 pub output: String,
237 pub stdout: String,
238 pub stderr: String,
239 pub exit_code: Option<i32>,
240 pub outcome: HookOutcome,
241}
242
243pub fn emit_hook_response(data: EmitHookResponseParams) {
245 let output_to_log =
247 if !data.stdout.is_empty() || !data.stderr.is_empty() || !data.output.is_empty() {
248 if !data.stdout.is_empty() {
249 Some(&data.stdout)
250 } else if !data.stderr.is_empty() {
251 Some(&data.stderr)
252 } else {
253 Some(&data.output)
254 }
255 } else {
256 None
257 };
258
259 if let Some(output) = output_to_log {
260 log_for_debugging(&format!(
261 "Hook {} ({}) {:?}:\n{}",
262 data.hook_name, data.hook_event, data.outcome, output
263 ));
264 }
265
266 if !should_emit(&data.hook_event) {
267 return;
268 }
269
270 emit(HookExecutionEvent::Response(HookResponseEvent {
271 hook_id: data.hook_id,
272 hook_name: data.hook_name,
273 hook_event: data.hook_event,
274 output: data.output,
275 stdout: data.stdout,
276 stderr: data.stderr,
277 exit_code: data.exit_code,
278 outcome: data.outcome,
279 }));
280}
281
282pub fn set_all_hook_events_enabled(enabled: bool) {
284 let mut state = HOOK_EVENT_STATE.lock().unwrap();
285 state.all_hook_events_enabled = enabled;
286}
287
288pub fn clear_hook_event_state() {
290 let mut state = HOOK_EVENT_STATE.lock().unwrap();
291 state.event_handler = None;
292 state.pending_events.clear();
293 state.all_hook_events_enabled = false;
294}
295
296fn log_for_debugging(msg: &str) {
298 log::debug!("{}", msg);
299}