aidaemon 0.11.3

A personal AI agent that runs as a background daemon, accessible via Telegram, Slack, or Discord, with tool use, MCP integration, and persistent memory
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use super::tools::ToolSemanticScope;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OpenRequestStatus {
    Open,
    InProgress,
    PartiallyAnswered,
    Answered,
    Blocked,
    Superseded,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QuestionKind {
    Clarification,
    Approval,
    Choice,
    Confirmation,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AssistantTurnKind {
    ClarificationQuestion,
    PartialProgress,
    SubstantiveAnswer,
    Blocked,
    Refusal,
    SystemNotice,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UserTurnKind {
    NewRequest,
    Followup,
    ClarificationAnswer,
    Courtesy,
    Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ActiveTaskStatus {
    Running,
    Completed,
    Failed,
    Cancelled,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenRequest {
    pub user_message_id: String,
    pub text: String,
    pub status: OpenRequestStatus,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub task_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub project_scope: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub semantic_scope: Option<ToolSemanticScope>,
    pub opened_at: DateTime<Utc>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resolved_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenQuestion {
    pub assistant_message_id: String,
    pub text: String,
    pub kind: QuestionKind,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub related_user_message_id: Option<String>,
    pub awaiting_user_reply: bool,
    pub asked_at: DateTime<Utc>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssistantTurnSummary {
    pub message_id: String,
    pub kind: AssistantTurnKind,
    pub left_request_open: bool,
    pub text: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserTurnSummary {
    pub message_id: String,
    pub kind: UserTurnKind,
    pub text: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActiveTaskRef {
    pub task_id: String,
    pub status: ActiveTaskStatus,
    pub started_at: DateTime<Utc>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DialogueState {
    pub session_id: String,
    pub schema_version: u32,
    pub revision: i64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_request: Option<OpenRequest>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_question: Option<OpenQuestion>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last_assistant_turn: Option<AssistantTurnSummary>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last_user_turn: Option<UserTurnSummary>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub active_task: Option<ActiveTaskRef>,
    pub updated_at: DateTime<Utc>,
}

impl DialogueState {
    pub const SCHEMA_VERSION: u32 = 1;

    pub fn new(session_id: impl Into<String>) -> Self {
        Self {
            session_id: session_id.into(),
            schema_version: Self::SCHEMA_VERSION,
            revision: 1,
            open_request: None,
            open_question: None,
            last_assistant_turn: None,
            last_user_turn: None,
            active_task: None,
            updated_at: Utc::now(),
        }
    }

    pub fn touch(&mut self) {
        self.revision = self.revision.saturating_add(1);
        self.updated_at = Utc::now();
    }
}