haematite-cli 0.21.0

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
use super::assistant_test_support::*;
use serde_json::{Value, json};
use std::{path::PathBuf, sync::Arc};

// These are the launch inputs consumed by the source-included transport.
// Keeping the transport private to its seat does not require another wire implementation.
mod config {
    use super::PathBuf;
    pub struct Seat {
        pub door_socket: PathBuf,
        pub store: String,
        pub harness: Harness,
    }
    pub struct Harness {
        pub acp_frame_bytes: usize,
        pub write_queue: usize,
        pub content_bytes: usize,
        pub id: String,
    }
}
mod tools {
    pub struct Identity {
        pub agent: String,
        pub pass: String,
    }
}
mod handshake {
    use super::Value;
    pub struct Session {
        pub resumable: bool,
        pub version: Version,
        pub info: Value,
    }
    #[derive(Clone, Copy)]
    pub enum Version {
        Two,
    }
    impl Version {
        pub const fn number(self) -> u16 {
            match self {
                Self::Two => 2,
            }
        }
    }
}
mod door_link {
    include!("../assistant/door_link.rs");
}

#[tokio::test(flavor = "current_thread")]
async fn real_seat_link_and_real_door_agree_hello_welcome_op_ack_item() -> Result {
    let f = Fixture::new(json!({}))?;
    let seat = config::Seat {
        door_socket: f.files.path().join("assistant.sock"),
        store: "wards".into(),
        harness: config::Harness {
            acp_frame_bytes: 262_144,
            write_queue: 16,
            content_bytes: 65_536,
            id: "codex".into(),
        },
    };
    let identity = tools::Identity {
        agent: "agent-assistant".into(),
        pass: agent_token("agent-pass")?,
    };
    let session = handshake::Session {
        resumable: true,
        version: handshake::Version::Two,
        info: json!({"version":"fixture"}),
    };
    let mut link = door_link::Link::open(&seat, &identity, &session).await?;
    assert_eq!(link.person, PERSON);
    let mut events = Stream::open(f.answer("GET", "/events", &json!({})))?;
    events.event()?;
    let request = f.request("POST", "", &json!({"text":"real transport"}));
    let (door, site) = (Arc::clone(&f.door), Arc::clone(&f.site));
    let posted = std::thread::spawn(move || {
        written(super::route::answer(
            &door,
            &site,
            &request,
            crate::serve::pass::now(),
        ))
    });
    let op = link
        .input
        .recv()
        .await
        .ok_or_else(|| anyhow::anyhow!("no op"))??
        .ok_or_else(|| anyhow::anyhow!("closed"))?;
    assert_eq!(op["op"]["text"], "real transport");
    assert_eq!(op["op"]["person"], PERSON);
    link.output.send(json!({"ack":{"id":op["op"]["id"]}}))?;
    link.output.send(json!({"item":item(1)}))?;
    link.output.flush().await?;
    let response = posted.join().map_err(|_| anyhow::anyhow!("post panic"))??;
    assert!(response.starts_with("HTTP/1.1 202 "), "{response}");
    let item = events.event()?;
    assert!(
        item.contains("id: conversation:1") && item.contains("item 1"),
        "{item}"
    );
    events.close()?;
    Ok(())
}