use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, RwLock};
use tokio::sync::Notify;
use crate::session::context::SessionId;
#[derive(Debug, Clone)]
pub enum InboxSource {
Schedule { id: String },
BackgroundAgent { name: String },
Skill { name: String },
Inject,
Webhook { hook: String },
}
#[derive(Debug, Clone)]
pub struct InboxMessage {
pub source: InboxSource,
pub content: String,
}
struct InboxQueue {
messages: VecDeque<InboxMessage>,
notify: Arc<Notify>,
}
static INBOX: RwLock<Option<HashMap<SessionId, InboxQueue>>> = RwLock::new(None);
pub fn init_inbox_for_session() {
let session_id = match crate::session::context::current_session_id() {
Some(id) => id,
None => return,
};
let mut guard = INBOX.write().unwrap();
let registry = guard.get_or_insert_with(HashMap::new);
registry.insert(
session_id,
InboxQueue {
messages: VecDeque::new(),
notify: Arc::new(Notify::new()),
},
);
}
pub fn clear_inbox_for_session(session_id: &SessionId) {
if let Ok(mut guard) = INBOX.write() {
if let Some(registry) = guard.as_mut() {
registry.remove(session_id);
}
}
}
pub fn push_inbox_message(msg: InboxMessage) {
let session_id = match crate::session::context::current_session_id() {
Some(id) => id,
None => return,
};
let mut guard = INBOX.write().unwrap();
if let Some(registry) = guard.as_mut() {
if let Some(q) = registry.get_mut(&session_id) {
q.messages.push_back(msg);
q.notify.notify_one();
}
}
}
pub fn push_inbox_message_for_session(session_id: &str, msg: InboxMessage) {
let mut guard = INBOX.write().unwrap();
if let Some(registry) = guard.as_mut() {
if let Some(q) = registry.get_mut(session_id) {
q.messages.push_back(msg);
q.notify.notify_one();
}
}
}
pub fn try_pop_inbox_message() -> Option<InboxMessage> {
let session_id = crate::session::context::current_session_id()?;
let mut guard = INBOX.write().unwrap();
let registry = guard.as_mut()?;
let queue = registry.get_mut(&session_id)?;
queue.messages.pop_front()
}
pub fn has_inbox_messages() -> bool {
let session_id = match crate::session::context::current_session_id() {
Some(id) => id,
None => return false,
};
let guard = INBOX.read().unwrap();
guard
.as_ref()
.and_then(|r| r.get(&session_id))
.map(|q| !q.messages.is_empty())
.unwrap_or(false)
}
pub fn peek_inbox_preview(session_id: &str) -> Option<String> {
let guard = INBOX.read().unwrap();
let msg = guard
.as_ref()
.and_then(|r| r.get(session_id))?
.messages
.front()?;
let source = match &msg.source {
InboxSource::Schedule { .. } => "scheduled message",
InboxSource::BackgroundAgent { name } => {
return Some(format!("background agent '{name}'"));
}
InboxSource::Skill { name } => {
return Some(format!("skill '{name}'"));
}
InboxSource::Inject => "external inject",
InboxSource::Webhook { hook } => {
return Some(format!("webhook '{hook}'"));
}
};
let preview: String = msg
.content
.lines()
.next()
.unwrap_or("")
.chars()
.take(80)
.collect();
let ellipsis = if preview.len() < msg.content.len() {
"…"
} else {
""
};
Some(format!("{source}: {preview}{ellipsis}"))
}
pub fn get_inbox_notify() -> Option<Arc<Notify>> {
let session_id = crate::session::context::current_session_id()?;
let guard = INBOX.read().unwrap();
guard
.as_ref()
.and_then(|r| r.get(&session_id))
.map(|q| Arc::clone(&q.notify))
}