use tokio::sync::mpsc;
use mofa_kernel::agent::secretary::{SecretaryBehavior, SecretaryContext, UserConnection};
#[derive(Debug, Clone)]
pub struct SecretaryCoreConfig {
pub poll_interval_ms: u64,
pub send_welcome: bool,
pub enable_periodic_check: bool,
pub periodic_check_interval_ms: u64,
pub max_consecutive_errors: u32,
}
impl Default for SecretaryCoreConfig {
fn default() -> Self {
Self {
poll_interval_ms: 100,
send_welcome: true,
enable_periodic_check: true,
periodic_check_interval_ms: 1000,
max_consecutive_errors: 10,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoreState {
Initializing,
Running,
Paused,
Stopped,
}
#[derive(Clone)]
pub struct SecretaryHandle {
running: std::sync::Arc<std::sync::atomic::AtomicBool>,
paused: std::sync::Arc<std::sync::atomic::AtomicBool>,
stop_tx: mpsc::Sender<()>,
}
impl SecretaryHandle {
pub fn new(stop_tx: mpsc::Sender<()>) -> Self {
Self {
running: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
paused: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
stop_tx,
}
}
pub fn running_flag(&self) -> std::sync::Arc<std::sync::atomic::AtomicBool> {
self.running.clone()
}
pub fn paused_flag(&self) -> std::sync::Arc<std::sync::atomic::AtomicBool> {
self.paused.clone()
}
pub fn is_running(&self) -> bool {
self.running.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn is_paused(&self) -> bool {
self.paused.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn set_running(&self, running: bool) {
self.running
.store(running, std::sync::atomic::Ordering::SeqCst);
}
pub fn pause(&self) {
self.paused.store(true, std::sync::atomic::Ordering::SeqCst);
}
pub fn resume(&self) {
self.paused
.store(false, std::sync::atomic::Ordering::SeqCst);
}
pub async fn stop(&self) {
self.running
.store(false, std::sync::atomic::Ordering::SeqCst);
let _ = self.stop_tx.send(()).await;
}
}
pub struct SecretaryCore<B>
where
B: SecretaryBehavior,
{
behavior: B,
config: SecretaryCoreConfig,
}
impl<B> SecretaryCore<B>
where
B: SecretaryBehavior + 'static,
{
pub fn new(behavior: B) -> Self {
Self {
behavior,
config: SecretaryCoreConfig::default(),
}
}
pub fn with_config(behavior: B, config: SecretaryCoreConfig) -> Self {
Self { behavior, config }
}
pub fn config_mut(&mut self) -> &mut SecretaryCoreConfig {
&mut self.config
}
pub fn behavior(&self) -> &B {
&self.behavior
}
pub async fn start<C>(
self,
connection: C,
) -> (SecretaryHandle, tokio::task::JoinHandle<anyhow::Result<()>>)
where
C: UserConnection<Input = B::Input, Output = B::Output> + 'static,
{
let (stop_tx, stop_rx) = mpsc::channel(1);
let handle = SecretaryHandle::new(stop_tx);
let handle_clone = handle.clone();
let join_handle =
tokio::spawn(
async move { self.run_event_loop(connection, handle_clone, stop_rx).await },
);
(handle, join_handle)
}
pub async fn run<C>(self, connection: C) -> anyhow::Result<()>
where
C: UserConnection<Input = B::Input, Output = B::Output> + 'static,
{
let (stop_tx, stop_rx) = mpsc::channel(1);
let handle = SecretaryHandle::new(stop_tx);
self.run_event_loop(connection, handle, stop_rx).await
}
async fn run_event_loop<C>(
self,
connection: C,
handle: SecretaryHandle,
mut stop_rx: mpsc::Receiver<()>,
) -> anyhow::Result<()>
where
C: UserConnection<Input = B::Input, Output = B::Output>,
{
handle.set_running(true);
let mut ctx = SecretaryContext::new(self.behavior.initial_state());
if self.config.send_welcome
&& let Some(welcome) = self.behavior.welcome_message()
&& let Err(e) = connection.send(welcome).await
{
tracing::warn!("Failed to send welcome message: {}", e);
}
let mut consecutive_errors = 0u32;
let mut last_periodic_check = std::time::Instant::now();
loop {
tokio::select! {
_ = stop_rx.recv() => {
tracing::info!("Received stop signal");
break;
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(self.config.poll_interval_ms)) => {
}
}
if !connection.is_connected() {
tracing::info!("Connection closed");
break;
}
if handle.is_paused() {
continue;
}
match connection.try_receive().await {
Ok(Some(input)) => {
consecutive_errors = 0;
match self.behavior.handle_input(input, &mut ctx).await {
Ok(outputs) => {
for output in outputs {
if let Err(e) = connection.send(output).await {
tracing::error!("Failed to send output: {}", e);
}
}
}
Err(e) => {
tracing::error!("Error handling input: {}", e);
if let Some(error_output) = self.behavior.handle_error(&e) {
let _ = connection.send(error_output).await;
}
}
}
}
Ok(None) => {
if self.config.enable_periodic_check {
let elapsed = last_periodic_check.elapsed().as_millis() as u64;
if elapsed >= self.config.periodic_check_interval_ms {
last_periodic_check = std::time::Instant::now();
match self.behavior.periodic_check(&mut ctx).await {
Ok(outputs) => {
for output in outputs {
if let Err(e) = connection.send(output).await {
tracing::warn!("Failed to send periodic output: {}", e);
}
}
}
Err(e) => {
tracing::warn!("Periodic check error: {}", e);
}
}
}
}
}
Err(e) => {
tracing::error!("Error receiving input: {}", e);
consecutive_errors += 1;
if consecutive_errors >= self.config.max_consecutive_errors {
tracing::error!(
"Too many consecutive errors ({}), stopping",
consecutive_errors
);
break;
}
}
}
}
handle.set_running(false);
if let Err(e) = self.behavior.on_disconnect(&mut ctx).await {
tracing::warn!("Error in on_disconnect: {}", e);
}
Ok(())
}
}
pub struct SecretaryCoreBuilder<B>
where
B: SecretaryBehavior,
{
behavior: B,
config: SecretaryCoreConfig,
}
impl<B> SecretaryCoreBuilder<B>
where
B: SecretaryBehavior + 'static,
{
pub fn new(behavior: B) -> Self {
Self {
behavior,
config: SecretaryCoreConfig::default(),
}
}
pub fn with_poll_interval(mut self, ms: u64) -> Self {
self.config.poll_interval_ms = ms;
self
}
pub fn with_welcome(mut self, send: bool) -> Self {
self.config.send_welcome = send;
self
}
pub fn with_periodic_check(mut self, enabled: bool) -> Self {
self.config.enable_periodic_check = enabled;
self
}
pub fn with_periodic_check_interval(mut self, ms: u64) -> Self {
self.config.periodic_check_interval_ms = ms;
self
}
pub fn with_max_consecutive_errors(mut self, max: u32) -> Self {
self.config.max_consecutive_errors = max;
self
}
pub fn build(self) -> SecretaryCore<B> {
SecretaryCore::with_config(self.behavior, self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = SecretaryCoreConfig::default();
assert_eq!(config.poll_interval_ms, 100);
assert!(config.send_welcome);
assert!(config.enable_periodic_check);
}
#[test]
fn test_handle() {
let (tx, _rx) = mpsc::channel(1);
let handle = SecretaryHandle::new(tx);
assert!(!handle.is_running());
assert!(!handle.is_paused());
handle.pause();
assert!(handle.is_paused());
handle.resume();
assert!(!handle.is_paused());
}
}