1use std::collections::HashMap;
25use std::sync::Mutex;
26
27use tokio::sync::{mpsc, watch};
28
29use super::path::AgentPath;
30
31#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct MailboxTask {
38 pub task: String,
40 pub interrupt: bool,
42 pub pending_messages: Vec<String>,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum MailboxStatus {
49 Ok,
51 Error,
53 Closed,
55}
56
57#[derive(Clone, Debug)]
59pub struct MailboxResult {
60 pub agent_path: AgentPath,
62 pub status: MailboxStatus,
64 pub result: Option<String>,
66 pub denied_tools: Vec<String>,
68}
69
70#[derive(Debug)]
79pub struct ChildMailbox {
80 pub task_rx: mpsc::Receiver<MailboxTask>,
82}
83
84struct MailboxEntry {
89 task_tx: mpsc::Sender<MailboxTask>,
91 results: Vec<MailboxResult>,
93 pending: Vec<String>,
95}
96
97pub struct MailboxHub {
110 entries: Mutex<HashMap<AgentPath, MailboxEntry>>,
111 seq_tx: watch::Sender<u64>,
112 seq_rx: watch::Receiver<u64>,
113}
114
115impl MailboxHub {
116 pub fn new() -> Self {
118 let (seq_tx, seq_rx) = watch::channel(0);
119 Self {
120 entries: Mutex::new(HashMap::new()),
121 seq_tx,
122 seq_rx,
123 }
124 }
125
126 pub fn register(&self, agent_path: &AgentPath) -> Option<ChildMailbox> {
131 let mut entries = self.entries.lock().unwrap();
132 if entries.contains_key(agent_path) {
133 return None;
134 }
135 let (task_tx, task_rx) = mpsc::channel(32);
136 entries.insert(
137 agent_path.clone(),
138 MailboxEntry {
139 task_tx,
140 results: Vec::new(),
141 pending: Vec::new(),
142 },
143 );
144 Some(ChildMailbox { task_rx })
145 }
146
147 pub fn unregister(&self, agent_path: &AgentPath) -> bool {
152 let mut entries = self.entries.lock().unwrap();
153 if entries.get(agent_path).is_some() {
154 let current = *self.seq_rx.borrow();
156 let _ = self.seq_tx.send(current.wrapping_add(1));
157 entries.remove(agent_path);
158 true
159 } else {
160 false
161 }
162 }
163
164 pub fn send_message(&self, agent_path: &AgentPath, message: String) -> bool {
169 let mut entries = self.entries.lock().unwrap();
170 match entries.get_mut(agent_path) {
171 Some(entry) => {
172 entry.pending.push(message);
173 true
174 }
175 None => false,
176 }
177 }
178
179 pub fn send_task(&self, agent_path: &AgentPath, task: String, interrupt: bool) -> bool {
185 let mut entries = self.entries.lock().unwrap();
186 match entries.get_mut(agent_path) {
187 Some(entry) => {
188 let pending = std::mem::take(&mut entry.pending);
189 let mailbox_task = MailboxTask {
190 task,
191 interrupt,
192 pending_messages: pending,
193 };
194 entry.task_tx.try_send(mailbox_task).is_ok()
195 }
196 None => false,
197 }
198 }
199
200 pub fn has_pending(&self, agent_path: &AgentPath) -> bool {
202 let entries = self.entries.lock().unwrap();
203 entries
204 .get(agent_path)
205 .map(|e| !e.pending.is_empty())
206 .unwrap_or(false)
207 }
208
209 pub fn post_result(&self, result: MailboxResult) {
213 let mut entries = self.entries.lock().unwrap();
214 if let Some(entry) = entries.get_mut(&result.agent_path) {
215 entry.results.push(result);
216 let current = *self.seq_rx.borrow();
218 let _ = self.seq_tx.send(current.wrapping_add(1));
219 }
220 }
221
222 pub fn subscribe_seq(&self) -> watch::Receiver<u64> {
226 self.seq_rx.clone()
227 }
228
229 pub fn try_recv_result(&self, agent_path: &AgentPath) -> Option<MailboxResult> {
233 let mut entries = self.entries.lock().unwrap();
234 entries.get_mut(agent_path).and_then(|e| {
235 if e.results.is_empty() {
236 None
237 } else {
238 Some(e.results.remove(0))
239 }
240 })
241 }
242
243 pub fn try_recv_any(&self) -> Option<MailboxResult> {
247 let mut entries = self.entries.lock().unwrap();
248 for entry in entries.values_mut() {
249 if !entry.results.is_empty() {
250 return Some(entry.results.remove(0));
251 }
252 }
253 None
254 }
255
256 pub fn has_results(&self, agent_path: &AgentPath) -> bool {
258 let entries = self.entries.lock().unwrap();
259 entries
260 .get(agent_path)
261 .map(|e| !e.results.is_empty())
262 .unwrap_or(false)
263 }
264
265 pub fn total_pending_results(&self) -> usize {
267 let entries = self.entries.lock().unwrap();
268 entries.values().map(|e| e.results.len()).sum()
269 }
270
271 pub fn contains(&self, agent_path: &AgentPath) -> bool {
273 let entries = self.entries.lock().unwrap();
274 entries.contains_key(agent_path)
275 }
276
277 pub fn len(&self) -> usize {
279 let entries = self.entries.lock().unwrap();
280 entries.len()
281 }
282
283 pub fn is_empty(&self) -> bool {
285 self.len() == 0
286 }
287
288 pub fn agent_paths(&self) -> Vec<AgentPath> {
290 let entries = self.entries.lock().unwrap();
291 entries.keys().cloned().collect()
292 }
293}
294
295impl Default for MailboxHub {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301#[cfg(test)]
306mod tests {
307 use std::sync::Arc;
308
309 use super::*;
310
311 fn test_path(name: &str) -> AgentPath {
312 AgentPath::root().join(name)
313 }
314
315 #[test]
316 fn register_and_unregister() {
317 let hub = MailboxHub::new();
318 let path = test_path("test-agent");
319
320 assert!(!hub.contains(&path));
321 assert_eq!(hub.len(), 0);
322
323 let child = hub.register(&path);
324 assert!(child.is_some());
325 assert!(hub.contains(&path));
326 assert_eq!(hub.len(), 1);
327
328 assert!(hub.register(&path).is_none());
330
331 assert!(hub.unregister(&path));
332 assert!(!hub.contains(&path));
333 assert_eq!(hub.len(), 0);
334
335 assert!(!hub.unregister(&path));
337 }
338
339 #[test]
340 fn send_message_and_task() {
341 let hub = MailboxHub::new();
342 let path = test_path("worker");
343
344 let mut child = hub.register(&path).unwrap();
345
346 assert!(hub.send_message(&path, "hello".into()));
348 assert!(hub.send_message(&path, "world".into()));
349 assert!(hub.has_pending(&path));
350
351 assert!(!hub.send_message(&test_path("ghost"), "nope".into()));
353
354 assert!(hub.send_task(&path, "do work".into(), true));
356 assert!(!hub.has_pending(&path));
357
358 let received = child.task_rx.try_recv().unwrap();
360 assert_eq!(received.task, "do work");
361 assert!(received.interrupt);
362 assert_eq!(received.pending_messages, vec!["hello", "world"]);
363 }
364
365 #[test]
366 fn post_and_receive_result() {
367 let hub = MailboxHub::new();
368 let path = test_path("worker");
369
370 hub.register(&path);
371
372 hub.post_result(MailboxResult {
373 agent_path: path.clone(),
374 status: MailboxStatus::Ok,
375 result: Some("done!".into()),
376 denied_tools: vec![],
377 });
378
379 assert!(hub.has_results(&path));
380
381 let received = hub.try_recv_result(&path);
382 assert!(received.is_some());
383 let r = received.unwrap();
384 assert_eq!(r.agent_path, path);
385 assert_eq!(r.status, MailboxStatus::Ok);
386 assert_eq!(r.result.unwrap(), "done!");
387
388 assert!(!hub.has_results(&path));
389 }
390
391 #[test]
392 fn try_recv_any_returns_all() {
393 let hub = MailboxHub::new();
394 let a = test_path("a");
395 let b = test_path("b");
396
397 hub.register(&a);
398 hub.register(&b);
399
400 hub.post_result(MailboxResult {
401 agent_path: a.clone(),
402 status: MailboxStatus::Ok,
403 result: Some("first".into()),
404 denied_tools: vec![],
405 });
406 hub.post_result(MailboxResult {
407 agent_path: b.clone(),
408 status: MailboxStatus::Error,
409 result: Some("second".into()),
410 denied_tools: vec![],
411 });
412
413 let r1 = hub.try_recv_any().unwrap();
415 let r2 = hub.try_recv_any().unwrap();
416 assert!(hub.try_recv_any().is_none());
417
418 let mut paths = vec![r1.agent_path.to_string(), r2.agent_path.to_string()];
419 paths.sort();
420 assert_eq!(paths, vec!["root/a", "root/b"]);
421 }
422
423 #[test]
424 fn sequence_number_changes_on_post() {
425 let hub = MailboxHub::new();
426 let path = test_path("worker");
427 hub.register(&path);
428
429 let seq = hub.subscribe_seq();
430 let initial = *seq.borrow();
431
432 hub.post_result(MailboxResult {
433 agent_path: path.clone(),
434 status: MailboxStatus::Ok,
435 result: None,
436 denied_tools: vec![],
437 });
438
439 assert!(seq.has_changed().unwrap());
440 assert_ne!(*seq.borrow(), initial);
441 }
442
443 #[test]
444 fn sequence_number_changes_on_unregister() {
445 let hub = MailboxHub::new();
446 let path = test_path("worker");
447 hub.register(&path);
448
449 let seq = hub.subscribe_seq();
450 let initial = *seq.borrow();
451
452 hub.unregister(&path);
453
454 assert!(seq.has_changed().unwrap());
455 assert_ne!(*seq.borrow(), initial);
456 }
457
458 #[test]
459 fn agent_paths() {
460 let hub = MailboxHub::new();
461 hub.register(&test_path("a"));
462 hub.register(&test_path("b"));
463
464 let mut paths = hub.agent_paths();
465 paths.sort();
466 assert_eq!(paths.len(), 2);
467 }
468
469 #[test]
470 fn total_pending_results() {
471 let hub = MailboxHub::new();
472 let a = test_path("a");
473 hub.register(&a);
474
475 assert_eq!(hub.total_pending_results(), 0);
476
477 hub.post_result(MailboxResult {
478 agent_path: a.clone(),
479 status: MailboxStatus::Ok,
480 result: None,
481 denied_tools: vec![],
482 });
483 assert_eq!(hub.total_pending_results(), 1);
484
485 hub.post_result(MailboxResult {
486 agent_path: a.clone(),
487 status: MailboxStatus::Ok,
488 result: None,
489 denied_tools: vec![],
490 });
491 assert_eq!(hub.total_pending_results(), 2);
492
493 hub.try_recv_any();
494 assert_eq!(hub.total_pending_results(), 1);
495 }
496
497 #[tokio::test]
498 async fn wait_for_result_pattern() {
499 let hub = Arc::new(MailboxHub::new());
500 let path = test_path("worker");
501 hub.register(&path);
502
503 let hub_clone = hub.clone();
504 let path_clone = path.clone();
505
506 tokio::spawn(async move {
508 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
509 hub_clone.post_result(MailboxResult {
510 agent_path: path_clone,
511 status: MailboxStatus::Ok,
512 result: Some("async result".into()),
513 denied_tools: vec![],
514 });
515 });
516
517 let mut seq = hub.subscribe_seq();
519 loop {
520 match hub.try_recv_any() {
521 Some(r) => {
522 assert_eq!(r.status, MailboxStatus::Ok);
523 assert_eq!(r.result.unwrap(), "async result");
524 break;
525 }
526 None => {
527 let _ = seq.changed().await;
528 }
529 }
530 }
531 }
532}