agent-team-mail-core 1.0.0

Daemon-free core library for local agent team mail workflows.
Documentation
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentMember {
    pub name: String,

    #[serde(default)]
    pub agent_id: String,

    #[serde(default)]
    pub agent_type: String,

    #[serde(default)]
    pub model: String,

    #[serde(default)]
    pub joined_at: Option<u64>,

    #[serde(default)]
    pub tmux_pane_id: String,

    #[serde(default)]
    pub cwd: String,

    #[serde(flatten)]
    pub extra: Map<String, Value>,
}

#[cfg(test)]
mod tests {
    use super::AgentMember;

    #[test]
    fn parse_name_only_record_defaults_optional_fields() {
        let member: AgentMember = serde_json::from_str(r#"{"name":"arch-ctm"}"#).expect("member");

        assert_eq!(member.name, "arch-ctm");
        assert!(member.agent_id.is_empty());
        assert!(member.agent_type.is_empty());
        assert!(member.model.is_empty());
        assert_eq!(member.joined_at, None);
        assert!(member.tmux_pane_id.is_empty());
        assert!(member.cwd.is_empty());
        assert!(member.extra.is_empty());
    }

    #[test]
    fn parse_full_claude_code_record_preserves_values_and_extra() {
        let raw = r#"{
            "agentId":"arch-ctm@atm-dev",
            "name":"arch-ctm",
            "agentType":"general-purpose",
            "model":"claude-sonnet-4-5",
            "joinedAt":1770765919076,
            "tmuxPaneId":"%1",
            "cwd":"/workspace",
            "color":"blue"
        }"#;

        let member: AgentMember = serde_json::from_str(raw).expect("member");
        assert_eq!(member.agent_id, "arch-ctm@atm-dev");
        assert_eq!(member.name, "arch-ctm");
        assert_eq!(member.agent_type, "general-purpose");
        assert_eq!(member.model, "claude-sonnet-4-5");
        assert_eq!(member.joined_at, Some(1770765919076));
        assert_eq!(member.tmux_pane_id, "%1");
        assert_eq!(member.cwd, "/workspace");
        assert_eq!(member.extra["color"], serde_json::json!("blue"));

        let encoded = serde_json::to_string(&member).expect("encode");
        let decoded: AgentMember = serde_json::from_str(&encoded).expect("decode");
        assert_eq!(decoded, member);
    }

    #[test]
    fn parse_name_and_agent_type_record_succeeds() {
        let member: AgentMember =
            serde_json::from_str(r#"{"name":"arch-ctm","agentType":"plan"}"#).expect("member");

        assert_eq!(member.name, "arch-ctm");
        assert_eq!(member.agent_type, "plan");
        assert!(member.agent_id.is_empty());
        assert!(member.model.is_empty());
        assert_eq!(member.joined_at, None);
        assert!(member.tmux_pane_id.is_empty());
        assert!(member.cwd.is_empty());
    }
}