1use tokio::sync::mpsc::UnboundedSender;
11
12use crate::events::{EventQueue, format_event_for_agent};
13use crate::SharedMessage;
14
15pub use agent_core::core::rpc_protocol::EventPayload;
17
18pub const AUTO_TURN_CAP: u32 = 5;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum EventDisposition {
27 Steered,
29 Buffered,
31 Injected,
33}
34
35#[derive(Debug, Clone)]
38pub struct DrainedEvent {
39 pub event: crate::events::types::Event,
40 pub formatted: String,
41 pub disposition: EventDisposition,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum WakeAction {
47 RunTurn,
49 Forward,
52 Nothing,
54}
55
56pub fn drain_event_queue(
69 queue: &EventQueue,
70 messages: &mut Vec<SharedMessage>,
71 pending_events: &mut Vec<String>,
72 busy: bool,
73 steer_tx: Option<&UnboundedSender<String>>,
74) -> Vec<DrainedEvent> {
75 let mut drained = Vec::new();
76
77 while let Some(event) = queue.pop() {
78 let formatted = format_event_for_agent(&event);
79
80 let disposition = if busy {
81 let steered = steer_tx
83 .map(|tx| tx.send(formatted.clone()).is_ok())
84 .unwrap_or(false);
85 if steered {
86 EventDisposition::Steered
87 } else {
88 pending_events.push(formatted.clone());
89 EventDisposition::Buffered
90 }
91 } else {
92 messages.push(std::sync::Arc::new(serde_json::json!({
94 "role": "user",
95 "content": formatted
96 })));
97 EventDisposition::Injected
98 };
99
100 drained.push(DrainedEvent { event, formatted, disposition });
101 }
102
103 drained
104}
105
106pub fn wake_action(
114 drained: &[DrainedEvent],
115 messages: &[SharedMessage],
116 busy: bool,
117 auto_turn_enabled: bool,
118 consecutive_auto_turns: u32,
119) -> WakeAction {
120 if drained.is_empty() {
121 return WakeAction::Nothing;
122 }
123
124 let has_injected = drained.iter().any(|d| d.disposition == EventDisposition::Injected);
125
126 if has_injected
127 && !busy
128 && auto_turn_enabled
129 && consecutive_auto_turns < AUTO_TURN_CAP
130 && messages.last().map(|m| m["role"].as_str() == Some("user")).unwrap_or(false)
131 {
132 return WakeAction::RunTurn;
133 }
134
135 WakeAction::Forward
136}
137
138pub fn event_payload_from_drained(drained: &DrainedEvent) -> EventPayload {
147 let ev = &drained.event;
148 let severity = ev
149 .content
150 .severity
151 .as_ref()
152 .map(|s| s.as_str())
153 .unwrap_or("medium")
154 .to_string();
155 EventPayload {
156 id: ev.id.clone(),
157 source: ev.source.source_type.clone(),
158 severity,
159 content_type: ev.content.content_type.clone(),
160 text: ev.content.text.clone(),
161 timestamp: ev.timestamp.to_rfc3339(),
162 formatted: drained.formatted.clone(),
163 }
164}
165
166#[inline]
186pub fn claim_auto_turn(counter: &mut u32) -> bool {
187 if *counter < AUTO_TURN_CAP {
188 *counter += 1;
189 true
190 } else {
191 false
192 }
193}
194
195#[inline]
207pub fn idle_should_wait(children_running: bool, queue_len: usize) -> bool {
208 children_running || queue_len > 0
209}
210
211pub fn terminal_flush_seam(
226 allow_chain: bool,
227 auto_turn_pending: &mut bool,
228 pending_events: &mut Vec<String>,
229 api_messages: &mut Vec<crate::SharedMessage>,
230 events_auto_turn: bool,
231 consecutive_auto_turns: &mut u32,
232) -> Option<String> {
233 *auto_turn_pending = false;
234 let to_inject = std::mem::take(pending_events);
235 let had_buffered = !to_inject.is_empty();
236 for formatted in to_inject {
237 api_messages.push(std::sync::Arc::new(
238 serde_json::json!({"role": "user", "content": formatted})
239 ));
240 }
241 if allow_chain
242 && had_buffered
243 && events_auto_turn
244 && *consecutive_auto_turns < AUTO_TURN_CAP
245 && api_messages.last().map(|m| m["role"].as_str() == Some("user")).unwrap_or(false)
246 && claim_auto_turn(consecutive_auto_turns)
247 {
248 *auto_turn_pending = true;
249 return Some("auto:seam".to_string());
250 }
251 None
252}
253
254pub fn spawn_prompt_registration_check(
272 is_auto: bool,
273 auto_turn_pending: &mut bool,
274 in_flight_live: bool,
275) -> bool {
276 if !is_auto {
277 return !*auto_turn_pending && !in_flight_live;
280 }
281 if *auto_turn_pending && !in_flight_live {
282 return true;
283 }
284 *auto_turn_pending = false;
286 false
287}
288
289#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::events::{types::Severity, EventQueue};
295
296 fn make_queue_with(texts: &[(&str, Option<Severity>)]) -> EventQueue {
297 let q = EventQueue::new(64);
298 for (text, sev) in texts {
299 q.push(crate::events::types::Event::simple("test", text, sev.clone())).unwrap();
300 }
301 q
302 }
303
304 fn user_msg(text: &str) -> SharedMessage {
305 std::sync::Arc::new(serde_json::json!({"role": "user", "content": text}))
306 }
307
308 fn assistant_msg() -> SharedMessage {
309 std::sync::Arc::new(serde_json::json!({"role": "assistant", "content": "ok"}))
310 }
311
312 #[test]
315 fn idle_single_event_injected() {
316 let q = make_queue_with(&[("hello", Some(Severity::Medium))]);
317 let mut messages: Vec<SharedMessage> = Vec::new();
318 let mut pending: Vec<String> = Vec::new();
319
320 let drained = drain_event_queue(&q, &mut messages, &mut pending, false, None);
321
322 assert_eq!(drained.len(), 1);
323 assert_eq!(drained[0].disposition, EventDisposition::Injected);
324 assert!(drained[0].formatted.contains("hello"));
325 assert_eq!(messages.len(), 1);
326 assert_eq!(messages[0]["role"], "user");
327 assert!(messages[0]["content"].as_str().unwrap().contains("hello"));
328 assert!(pending.is_empty());
329 assert!(q.is_empty());
330 }
331
332 #[test]
333 fn idle_multiple_events_injected_in_order() {
334 let q = make_queue_with(&[
335 ("first", Some(Severity::Medium)),
336 ("second", Some(Severity::Medium)),
337 ("third", Some(Severity::Low)),
338 ]);
339 let mut messages: Vec<SharedMessage> = Vec::new();
340 let mut pending: Vec<String> = Vec::new();
341
342 let drained = drain_event_queue(&q, &mut messages, &mut pending, false, None);
343
344 assert_eq!(drained.len(), 3);
345 assert!(drained.iter().all(|d| d.disposition == EventDisposition::Injected));
346 assert!(drained[0].formatted.contains("first"));
348 assert!(drained[1].formatted.contains("second"));
349 assert!(drained[2].formatted.contains("third"));
350 assert_eq!(messages.len(), 3);
351 }
352
353 #[test]
354 fn busy_live_steer_tx_steers() {
355 let q = make_queue_with(&[("ev", Some(Severity::Medium))]);
356 let mut messages: Vec<SharedMessage> = Vec::new();
357 let mut pending: Vec<String> = Vec::new();
358 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
359
360 let drained = drain_event_queue(&q, &mut messages, &mut pending, true, Some(&tx));
361
362 assert_eq!(drained.len(), 1);
363 assert_eq!(drained[0].disposition, EventDisposition::Steered);
364 assert!(messages.is_empty());
365 assert!(pending.is_empty());
366 let sent = rx.try_recv().expect("message should have been steered");
368 assert!(sent.contains("ev"));
369 }
370
371 #[test]
372 fn busy_dead_steer_tx_buffers() {
373 let q = make_queue_with(&[("ev", Some(Severity::Medium))]);
374 let mut messages: Vec<SharedMessage> = Vec::new();
375 let mut pending: Vec<String> = Vec::new();
376 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<String>();
378 drop(rx);
379
380 let drained = drain_event_queue(&q, &mut messages, &mut pending, true, Some(&tx));
381
382 assert_eq!(drained.len(), 1);
383 assert_eq!(drained[0].disposition, EventDisposition::Buffered);
384 assert!(messages.is_empty());
385 assert_eq!(pending.len(), 1);
386 assert!(pending[0].contains("ev"));
387 }
388
389 #[test]
390 fn busy_no_steer_tx_buffers() {
391 let q = make_queue_with(&[("ev", Some(Severity::Medium))]);
392 let mut messages: Vec<SharedMessage> = Vec::new();
393 let mut pending: Vec<String> = Vec::new();
394
395 let drained = drain_event_queue(&q, &mut messages, &mut pending, true, None);
396
397 assert_eq!(drained.len(), 1);
398 assert_eq!(drained[0].disposition, EventDisposition::Buffered);
399 assert!(pending.len() == 1);
400 }
401
402 #[test]
403 fn empty_queue_returns_nothing() {
404 let q = EventQueue::new(10);
405 let mut messages: Vec<SharedMessage> = Vec::new();
406 let mut pending: Vec<String> = Vec::new();
407
408 let drained = drain_event_queue(&q, &mut messages, &mut pending, false, None);
409
410 assert!(drained.is_empty());
411 assert!(messages.is_empty());
412 assert!(pending.is_empty());
413 }
414
415 #[test]
416 fn canonical_format_contains_xml_event_tags() {
417 let q = make_queue_with(&[("ping", Some(Severity::High))]);
418 let mut messages: Vec<SharedMessage> = Vec::new();
419 let mut pending: Vec<String> = Vec::new();
420
421 let drained = drain_event_queue(&q, &mut messages, &mut pending, false, None);
422
423 assert!(drained[0].formatted.starts_with("<event "));
424 assert!(drained[0].formatted.ends_with("</event>"));
425 assert!(drained[0].formatted.contains("severity=\"high\""));
426 }
427
428 #[test]
429 fn priority_ordering_preserved_critical_first() {
430 let q = EventQueue::new(10);
432 q.push(crate::events::types::Event::simple("test", "medium", Some(Severity::Medium))).unwrap();
433 q.push(crate::events::types::Event::simple("test", "critical", Some(Severity::Critical))).unwrap();
434
435 let mut messages: Vec<SharedMessage> = Vec::new();
436 let mut pending: Vec<String> = Vec::new();
437
438 let drained = drain_event_queue(&q, &mut messages, &mut pending, false, None);
439
440 assert_eq!(drained.len(), 2);
441 assert!(drained[0].formatted.contains("critical"));
442 assert!(drained[1].formatted.contains("medium"));
443 }
444
445 fn injected_event() -> DrainedEvent {
448 let event = crate::events::types::Event::simple("test", "x", None);
449 DrainedEvent {
450 formatted: format_event_for_agent(&event),
451 event,
452 disposition: EventDisposition::Injected,
453 }
454 }
455
456 fn steered_event() -> DrainedEvent {
457 let event = crate::events::types::Event::simple("test", "x", None);
458 DrainedEvent {
459 formatted: format_event_for_agent(&event),
460 event,
461 disposition: EventDisposition::Steered,
462 }
463 }
464
465 fn buffered_event() -> DrainedEvent {
466 let event = crate::events::types::Event::simple("test", "x", None);
467 DrainedEvent {
468 formatted: format_event_for_agent(&event),
469 event,
470 disposition: EventDisposition::Buffered,
471 }
472 }
473
474 #[test]
475 fn wake_nothing_when_drained_empty() {
476 let result = wake_action(&[], &[], false, true, 0);
477 assert_eq!(result, WakeAction::Nothing);
478 }
479
480 #[test]
481 fn wake_run_turn_when_injected_idle_enabled_under_cap() {
482 let drained = vec![injected_event()];
483 let messages = vec![user_msg("hello")];
484 let result = wake_action(&drained, &messages, false, true, 0);
485 assert_eq!(result, WakeAction::RunTurn);
486 }
487
488 #[test]
489 fn wake_forward_when_busy() {
490 let drained = vec![injected_event()];
491 let messages = vec![user_msg("hello")];
492 let result = wake_action(&drained, &messages, true, true, 0);
493 assert_eq!(result, WakeAction::Forward);
494 }
495
496 #[test]
497 fn wake_forward_when_auto_turn_disabled() {
498 let drained = vec![injected_event()];
499 let messages = vec![user_msg("hello")];
500 let result = wake_action(&drained, &messages, false, false, 0);
501 assert_eq!(result, WakeAction::Forward);
502 }
503
504 #[test]
505 fn wake_forward_when_last_message_not_user() {
506 let drained = vec![injected_event()];
507 let messages = vec![assistant_msg()];
508 let result = wake_action(&drained, &messages, false, true, 0);
509 assert_eq!(result, WakeAction::Forward);
510 }
511
512 #[test]
513 fn wake_forward_when_messages_empty() {
514 let drained = vec![injected_event()];
515 let result = wake_action(&drained, &[], false, true, 0);
516 assert_eq!(result, WakeAction::Forward);
517 }
518
519 #[test]
520 fn wake_forward_when_cap_reached() {
521 let drained = vec![injected_event()];
522 let messages = vec![user_msg("hello")];
523 let result = wake_action(&drained, &messages, false, true, AUTO_TURN_CAP);
525 assert_eq!(result, WakeAction::Forward);
526 }
527
528 #[test]
529 fn wake_run_turn_when_one_below_cap() {
530 let drained = vec![injected_event()];
531 let messages = vec![user_msg("hello")];
532 let result = wake_action(&drained, &messages, false, true, AUTO_TURN_CAP - 1);
533 assert_eq!(result, WakeAction::RunTurn);
534 }
535
536 #[test]
537 fn wake_forward_when_only_steered_events() {
538 let drained = vec![steered_event()];
540 let messages = vec![user_msg("hello")];
541 let result = wake_action(&drained, &messages, false, true, 0);
542 assert_eq!(result, WakeAction::Forward);
543 }
544
545 #[test]
546 fn wake_forward_when_only_buffered_events() {
547 let drained = vec![buffered_event()];
548 let messages = vec![user_msg("hello")];
549 let result = wake_action(&drained, &messages, false, true, 0);
550 assert_eq!(result, WakeAction::Forward);
551 }
552
553 #[test]
554 fn wake_run_turn_with_mixed_injected_and_steered() {
555 let drained = vec![steered_event(), injected_event()];
557 let messages = vec![user_msg("hello")];
558 let result = wake_action(&drained, &messages, false, true, 0);
559 assert_eq!(result, WakeAction::RunTurn);
560 }
561
562 #[test]
565 fn idle_should_wait_false_when_no_children_no_queue() {
566 assert!(!idle_should_wait(false, 0));
567 }
568
569 #[test]
570 fn idle_should_wait_true_when_children_running() {
571 assert!(idle_should_wait(true, 0));
572 }
573
574 #[test]
575 fn idle_should_wait_true_when_queue_nonempty() {
576 assert!(idle_should_wait(false, 1));
577 }
578
579 #[test]
580 fn idle_should_wait_true_when_both_children_and_queue() {
581 assert!(idle_should_wait(true, 3));
582 }
583
584 #[test]
585 fn idle_should_wait_true_when_large_queue() {
586 assert!(idle_should_wait(false, 999));
587 }
588
589 #[test]
592 fn claim_auto_turn_first_five_allowed() {
593 let mut counter: u32 = 0;
594 for _ in 0..AUTO_TURN_CAP {
595 assert!(claim_auto_turn(&mut counter), "turn within cap must be allowed");
596 }
597 assert_eq!(counter, AUTO_TURN_CAP, "counter must equal cap after 5 claims");
598 }
599
600 #[test]
601 fn claim_auto_turn_sixth_denied() {
602 let mut counter: u32 = AUTO_TURN_CAP;
603 assert!(!claim_auto_turn(&mut counter), "turn at cap must be denied");
604 assert_eq!(counter, AUTO_TURN_CAP, "counter must remain unchanged when denied");
605 }
606
607 #[test]
608 fn claim_auto_turn_remains_denied_until_reset() {
609 let mut counter: u32 = AUTO_TURN_CAP;
610 assert!(!claim_auto_turn(&mut counter));
611 assert!(!claim_auto_turn(&mut counter));
612 assert!(!claim_auto_turn(&mut counter));
613 assert_eq!(counter, AUTO_TURN_CAP, "counter must not change while denied");
614 }
615
616 #[test]
617 fn claim_auto_turn_reset_re_enables() {
618 let mut counter: u32 = AUTO_TURN_CAP;
619 assert!(!claim_auto_turn(&mut counter));
620 counter = 0;
622 assert!(claim_auto_turn(&mut counter), "after reset first turn must be allowed");
623 assert_eq!(counter, 1);
624 }
625
626 #[test]
627 fn claim_auto_turn_exact_boundary_sequence() {
628 let mut c: u32 = 0;
630 for i in 1..=AUTO_TURN_CAP {
631 assert!(claim_auto_turn(&mut c), "turn {i} must be allowed");
632 assert_eq!(c, i);
633 }
634 assert!(!claim_auto_turn(&mut c));
636 assert_eq!(c, AUTO_TURN_CAP);
637 }
638}