daat-locus 0.1.1

A long-running local agent runtime with memory, workflows, apps, and sleep-time self-improvement.
use serde::{Deserialize, Serialize};

pub mod glyph {
    pub const APP_ATTENTION: &str = "";
    pub const BROWSER: &str = "";
    pub const ERROR: &str = "!";
    pub const EXEC: &str = "";
    pub const MEMORY: &str = "";
    pub const PATCH: &str = "";
    pub const PLAN: &str = "";
    pub const REPLY: &str = "";
    pub const TELEGRAM: &str = "";
    pub const WORKFLOW: &str = "";
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum ToolUiEvent {
    Exec(ToolUiData),
    Terminal(TerminalUiData),
    Browser(BrowserUiData),
    Patch(PatchUiData),
    Telegram(TelegramUiData),
    Reply(ReplyUiData),
    AppAttention(AppAttentionUiData),
    Plan(PlanUiData),
    CreateWorkflow(CreateWorkflowUiData),
    ActivateWorkflow(ActivateWorkflowUiData),
    DeepRecall(DeepRecallUiData),
    #[serde(alias = "Finish", alias = "Work")]
    App(ToolUiData),
    Error(ToolUiData),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum ToolCallUiEvent {
    Exec(ToolUiData),
    Terminal(TerminalUiData),
    Browser(BrowserUiData),
    Patch(PatchUiData),
    Telegram(TelegramUiData),
    Plan(ToolUiData),
    CreateWorkflow(ToolUiData),
    ActivateWorkflow(ToolUiData),
    DeepRecall(ToolUiData),
    #[serde(alias = "Finish", alias = "Work")]
    App(ToolUiData),
    Error(ToolUiData),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ToolUiData {
    pub title: String,
    #[serde(default)]
    pub body_lines: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TerminalUiData {
    pub action: TerminalUiAction,
    pub title: String,
    #[serde(default)]
    pub body_lines: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BrowserUiData {
    pub action: BrowserUiAction,
    pub title: String,
    #[serde(default)]
    pub body_lines: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub line_count: Option<usize>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ref_count: Option<usize>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TerminalUiAction {
    Execute,
    Continue,
    Poll,
    Terminate,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserUiAction {
    OpenPage,
    Snapshot,
    Wait,
    Click,
    Fill,
    Back,
    Forward,
    Reload,
    ClosePage,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchUiData {
    pub summary_line: String,
    #[serde(default)]
    pub files: Vec<PatchFileUiData>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchFileUiData {
    pub path: String,
    pub operation: PatchFileOperation,
    pub added_lines: usize,
    pub removed_lines: usize,
    #[serde(default)]
    pub diff_lines: Vec<PatchDiffLineUiData>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PatchFileOperation {
    Add,
    Delete,
    Update,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchDiffLineUiData {
    pub kind: PatchDiffLineKind,
    pub old_lineno: Option<usize>,
    pub new_lineno: Option<usize>,
    pub text: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PatchDiffLineKind {
    Context,
    Delete,
    Add,
    HunkBreak,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TelegramUiData {
    pub action: TelegramUiAction,
    pub title: String,
    #[serde(default)]
    pub detail_lines: Vec<String>,
    #[serde(default)]
    pub message_lines: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReplyUiData {
    pub disposition: ReplyDisposition,
    #[serde(default)]
    pub subject: ReplySubject,
    #[serde(default)]
    pub message_lines: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AppAttentionUiData {
    pub action: AppAttentionUiAction,
    pub app: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlanUiData {
    pub steps: Vec<PlanStepUiData>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlanStepUiData {
    pub status: PlanStepUiStatus,
    pub text: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PlanStepUiStatus {
    Pending,
    InProgress,
    Completed,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateWorkflowUiData {
    pub workflow_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ActivateWorkflowUiData {
    pub workflow_id: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeepRecallUiData {
    pub memory_count: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReplyDisposition {
    Resolved,
    Dismissed,
    Failed,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReplySubject {
    #[default]
    Message,
    Notice,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AppAttentionUiAction {
    Focus,
    PutAway,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TelegramUiAction {
    ListChats,
    #[serde(alias = "ReadChat")]
    ReadHistory,
    SelectChat,
    SendMessage,
    ResolveChat,
}

impl ToolUiEvent {
    pub fn patch(summary_line: impl Into<String>, files: Vec<PatchFileUiData>) -> Self {
        Self::Patch(PatchUiData {
            summary_line: summary_line.into(),
            files,
        })
    }

    pub fn terminal(
        action: TerminalUiAction,
        title: impl Into<String>,
        body_lines: Vec<String>,
    ) -> Self {
        Self::Terminal(TerminalUiData {
            action,
            title: title.into(),
            body_lines,
        })
    }

    pub fn plan(steps: Vec<PlanStepUiData>) -> Self {
        Self::Plan(PlanUiData { steps })
    }

    pub fn create_workflow(workflow_id: impl Into<String>) -> Self {
        Self::CreateWorkflow(CreateWorkflowUiData {
            workflow_id: workflow_id.into(),
        })
    }

    pub fn activate_workflow(workflow_id: impl Into<String>) -> Self {
        Self::ActivateWorkflow(ActivateWorkflowUiData {
            workflow_id: workflow_id.into(),
        })
    }

    pub fn deep_recall(memory_count: usize) -> Self {
        Self::DeepRecall(DeepRecallUiData { memory_count })
    }

    pub fn reply(disposition: ReplyDisposition, message_lines: Vec<String>) -> Self {
        Self::Reply(ReplyUiData {
            disposition,
            subject: ReplySubject::Message,
            message_lines,
        })
    }

    pub fn notice_reply(disposition: ReplyDisposition, message_lines: Vec<String>) -> Self {
        Self::Reply(ReplyUiData {
            disposition,
            subject: ReplySubject::Notice,
            message_lines,
        })
    }

    pub fn focus_app(app: impl Into<String>) -> Self {
        Self::AppAttention(AppAttentionUiData {
            action: AppAttentionUiAction::Focus,
            app: Some(app.into()),
        })
    }

    pub fn put_away_app() -> Self {
        Self::AppAttention(AppAttentionUiData {
            action: AppAttentionUiAction::PutAway,
            app: None,
        })
    }

    pub fn app(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::App(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn error(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::Error(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }
}

impl ToolCallUiEvent {
    pub fn patch(summary_line: impl Into<String>, files: Vec<PatchFileUiData>) -> Self {
        Self::Patch(PatchUiData {
            summary_line: summary_line.into(),
            files,
        })
    }

    pub fn terminal(
        action: TerminalUiAction,
        title: impl Into<String>,
        body_lines: Vec<String>,
    ) -> Self {
        Self::Terminal(TerminalUiData {
            action,
            title: title.into(),
            body_lines,
        })
    }

    pub fn plan(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::Plan(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn create_workflow(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::CreateWorkflow(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn activate_workflow(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::ActivateWorkflow(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn deep_recall(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::DeepRecall(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn app(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::App(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }

    pub fn error(title: impl Into<String>, body_lines: Vec<String>) -> Self {
        Self::Error(ToolUiData {
            title: title.into(),
            body_lines,
        })
    }
}

pub fn compact_body_lines(text: &str, max_lines: usize) -> Vec<String> {
    text.lines()
        .map(str::trim)
        .filter(|line| !line.is_empty())
        .take(max_lines)
        .map(ToString::to_string)
        .collect()
}