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}
67
68#[derive(Debug)]
77pub struct ChildMailbox {
78 pub task_rx: mpsc::Receiver<MailboxTask>,
80}
81
82struct MailboxEntry {
87 task_tx: mpsc::Sender<MailboxTask>,
89 results: Vec<MailboxResult>,
91 pending: Vec<String>,
93}
94
95pub struct MailboxHub {
108 entries: Mutex<HashMap<AgentPath, MailboxEntry>>,
109 seq_tx: watch::Sender<u64>,
110 seq_rx: watch::Receiver<u64>,
111}
112
113impl MailboxHub {
114 pub fn new() -> Self {
116 let (seq_tx, seq_rx) = watch::channel(0);
117 Self {
118 entries: Mutex::new(HashMap::new()),
119 seq_tx,
120 seq_rx,
121 }
122 }
123
124 pub fn register(&self, agent_path: &AgentPath) -> Option<ChildMailbox> {
129 let mut entries = self.entries.lock().unwrap();
130 if entries.contains_key(agent_path) {
131 return None;
132 }
133 let (task_tx, task_rx) = mpsc::channel(32);
134 entries.insert(
135 agent_path.clone(),
136 MailboxEntry {
137 task_tx,
138 results: Vec::new(),
139 pending: Vec::new(),
140 },
141 );
142 Some(ChildMailbox { task_rx })
143 }
144
145 pub fn unregister(&self, agent_path: &AgentPath) -> bool {
150 let mut entries = self.entries.lock().unwrap();
151 if entries.get(agent_path).is_some() {
152 let current = *self.seq_rx.borrow();
154 let _ = self.seq_tx.send(current.wrapping_add(1));
155 entries.remove(agent_path);
156 true
157 } else {
158 false
159 }
160 }
161
162 pub fn send_message(&self, agent_path: &AgentPath, message: String) -> bool {
167 let mut entries = self.entries.lock().unwrap();
168 match entries.get_mut(agent_path) {
169 Some(entry) => {
170 entry.pending.push(message);
171 true
172 }
173 None => false,
174 }
175 }
176
177 pub fn send_task(&self, agent_path: &AgentPath, task: String, interrupt: bool) -> bool {
183 let mut entries = self.entries.lock().unwrap();
184 match entries.get_mut(agent_path) {
185 Some(entry) => {
186 let pending = std::mem::take(&mut entry.pending);
187 let mailbox_task = MailboxTask {
188 task,
189 interrupt,
190 pending_messages: pending,
191 };
192 entry.task_tx.try_send(mailbox_task).is_ok()
193 }
194 None => false,
195 }
196 }
197
198 pub fn has_pending(&self, agent_path: &AgentPath) -> bool {
200 let entries = self.entries.lock().unwrap();
201 entries
202 .get(agent_path)
203 .map(|e| !e.pending.is_empty())
204 .unwrap_or(false)
205 }
206
207 pub fn post_result(&self, result: MailboxResult) {
211 let mut entries = self.entries.lock().unwrap();
212 if let Some(entry) = entries.get_mut(&result.agent_path) {
213 entry.results.push(result);
214 let current = *self.seq_rx.borrow();
216 let _ = self.seq_tx.send(current.wrapping_add(1));
217 }
218 }
219
220 pub fn subscribe_seq(&self) -> watch::Receiver<u64> {
224 self.seq_rx.clone()
225 }
226
227 pub fn try_recv_result(&self, agent_path: &AgentPath) -> Option<MailboxResult> {
231 let mut entries = self.entries.lock().unwrap();
232 entries.get_mut(agent_path).and_then(|e| {
233 if e.results.is_empty() {
234 None
235 } else {
236 Some(e.results.remove(0))
237 }
238 })
239 }
240
241 pub fn try_recv_any(&self) -> Option<MailboxResult> {
245 let mut entries = self.entries.lock().unwrap();
246 for entry in entries.values_mut() {
247 if !entry.results.is_empty() {
248 return Some(entry.results.remove(0));
249 }
250 }
251 None
252 }
253
254 pub fn has_results(&self, agent_path: &AgentPath) -> bool {
256 let entries = self.entries.lock().unwrap();
257 entries
258 .get(agent_path)
259 .map(|e| !e.results.is_empty())
260 .unwrap_or(false)
261 }
262
263 pub fn total_pending_results(&self) -> usize {
265 let entries = self.entries.lock().unwrap();
266 entries.values().map(|e| e.results.len()).sum()
267 }
268
269 pub fn contains(&self, agent_path: &AgentPath) -> bool {
271 let entries = self.entries.lock().unwrap();
272 entries.contains_key(agent_path)
273 }
274
275 pub fn len(&self) -> usize {
277 let entries = self.entries.lock().unwrap();
278 entries.len()
279 }
280
281 pub fn is_empty(&self) -> bool {
283 self.len() == 0
284 }
285
286 pub fn agent_paths(&self) -> Vec<AgentPath> {
288 let entries = self.entries.lock().unwrap();
289 entries.keys().cloned().collect()
290 }
291}
292
293impl Default for MailboxHub {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299#[cfg(test)]
304mod tests {
305 use std::sync::Arc;
306
307 use super::*;
308
309 fn test_path(name: &str) -> AgentPath {
310 AgentPath::root().join(name)
311 }
312
313 #[test]
314 fn register_and_unregister() {
315 let hub = MailboxHub::new();
316 let path = test_path("test-agent");
317
318 assert!(!hub.contains(&path));
319 assert_eq!(hub.len(), 0);
320
321 let child = hub.register(&path);
322 assert!(child.is_some());
323 assert!(hub.contains(&path));
324 assert_eq!(hub.len(), 1);
325
326 assert!(hub.register(&path).is_none());
328
329 assert!(hub.unregister(&path));
330 assert!(!hub.contains(&path));
331 assert_eq!(hub.len(), 0);
332
333 assert!(!hub.unregister(&path));
335 }
336
337 #[test]
338 fn send_message_and_task() {
339 let hub = MailboxHub::new();
340 let path = test_path("worker");
341
342 let mut child = hub.register(&path).unwrap();
343
344 assert!(hub.send_message(&path, "hello".into()));
346 assert!(hub.send_message(&path, "world".into()));
347 assert!(hub.has_pending(&path));
348
349 assert!(!hub.send_message(&test_path("ghost"), "nope".into()));
351
352 assert!(hub.send_task(&path, "do work".into(), true));
354 assert!(!hub.has_pending(&path));
355
356 let received = child.task_rx.try_recv().unwrap();
358 assert_eq!(received.task, "do work");
359 assert!(received.interrupt);
360 assert_eq!(received.pending_messages, vec!["hello", "world"]);
361 }
362
363 #[test]
364 fn post_and_receive_result() {
365 let hub = MailboxHub::new();
366 let path = test_path("worker");
367
368 hub.register(&path);
369
370 hub.post_result(MailboxResult {
371 agent_path: path.clone(),
372 status: MailboxStatus::Ok,
373 result: Some("done!".into()),
374 });
375
376 assert!(hub.has_results(&path));
377
378 let received = hub.try_recv_result(&path);
379 assert!(received.is_some());
380 let r = received.unwrap();
381 assert_eq!(r.agent_path, path);
382 assert_eq!(r.status, MailboxStatus::Ok);
383 assert_eq!(r.result.unwrap(), "done!");
384
385 assert!(!hub.has_results(&path));
386 }
387
388 #[test]
389 fn try_recv_any_returns_all() {
390 let hub = MailboxHub::new();
391 let a = test_path("a");
392 let b = test_path("b");
393
394 hub.register(&a);
395 hub.register(&b);
396
397 hub.post_result(MailboxResult {
398 agent_path: a.clone(),
399 status: MailboxStatus::Ok,
400 result: Some("first".into()),
401 });
402 hub.post_result(MailboxResult {
403 agent_path: b.clone(),
404 status: MailboxStatus::Error,
405 result: Some("second".into()),
406 });
407
408 let r1 = hub.try_recv_any().unwrap();
410 let r2 = hub.try_recv_any().unwrap();
411 assert!(hub.try_recv_any().is_none());
412
413 let mut paths = vec![r1.agent_path.to_string(), r2.agent_path.to_string()];
414 paths.sort();
415 assert_eq!(paths, vec!["root/a", "root/b"]);
416 }
417
418 #[test]
419 fn sequence_number_changes_on_post() {
420 let hub = MailboxHub::new();
421 let path = test_path("worker");
422 hub.register(&path);
423
424 let seq = hub.subscribe_seq();
425 let initial = *seq.borrow();
426
427 hub.post_result(MailboxResult {
428 agent_path: path.clone(),
429 status: MailboxStatus::Ok,
430 result: None,
431 });
432
433 assert!(seq.has_changed().unwrap());
434 assert_ne!(*seq.borrow(), initial);
435 }
436
437 #[test]
438 fn sequence_number_changes_on_unregister() {
439 let hub = MailboxHub::new();
440 let path = test_path("worker");
441 hub.register(&path);
442
443 let seq = hub.subscribe_seq();
444 let initial = *seq.borrow();
445
446 hub.unregister(&path);
447
448 assert!(seq.has_changed().unwrap());
449 assert_ne!(*seq.borrow(), initial);
450 }
451
452 #[test]
453 fn agent_paths() {
454 let hub = MailboxHub::new();
455 hub.register(&test_path("a"));
456 hub.register(&test_path("b"));
457
458 let mut paths = hub.agent_paths();
459 paths.sort();
460 assert_eq!(paths.len(), 2);
461 }
462
463 #[test]
464 fn total_pending_results() {
465 let hub = MailboxHub::new();
466 let a = test_path("a");
467 hub.register(&a);
468
469 assert_eq!(hub.total_pending_results(), 0);
470
471 hub.post_result(MailboxResult {
472 agent_path: a.clone(),
473 status: MailboxStatus::Ok,
474 result: None,
475 });
476 assert_eq!(hub.total_pending_results(), 1);
477
478 hub.post_result(MailboxResult {
479 agent_path: a.clone(),
480 status: MailboxStatus::Ok,
481 result: None,
482 });
483 assert_eq!(hub.total_pending_results(), 2);
484
485 hub.try_recv_any();
486 assert_eq!(hub.total_pending_results(), 1);
487 }
488
489 #[tokio::test]
490 async fn wait_for_result_pattern() {
491 let hub = Arc::new(MailboxHub::new());
492 let path = test_path("worker");
493 hub.register(&path);
494
495 let hub_clone = hub.clone();
496 let path_clone = path.clone();
497
498 tokio::spawn(async move {
500 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
501 hub_clone.post_result(MailboxResult {
502 agent_path: path_clone,
503 status: MailboxStatus::Ok,
504 result: Some("async result".into()),
505 });
506 });
507
508 let mut seq = hub.subscribe_seq();
510 loop {
511 match hub.try_recv_any() {
512 Some(r) => {
513 assert_eq!(r.status, MailboxStatus::Ok);
514 assert_eq!(r.result.unwrap(), "async result");
515 break;
516 }
517 None => {
518 let _ = seq.changed().await;
519 }
520 }
521 }
522 }
523}