supercode-harness 0.5.2

The optional native Volter Harness agent and tool harness
Documentation
//! One send for every sender: `supercode message send`, the native agent's
//! `send_message`, and a request arriving through another machine's mail
//! door. It resolves the receiver, routes to another machine through Teams or
//! to the router's door here, guards against loops and repeats, and words
//! the outcome for the sending agent.

use std::path::PathBuf;
use std::time::Duration;

use crate::mail_route::{Caller, LiveSessions, Unresolved};
use crate::mailbox::{local_machine_name, mail_root, Envelope, MailAddress, MailKind, ReplyVia};
use crate::HarnessHomes;

/// Exit code of a refused send.
pub const EXIT_REFUSED: i32 = 2;
/// Exit code of an unknown or stale receiver.
pub const EXIT_UNKNOWN: i32 = 3;
/// Exit code of a message stored with no door to show it.
pub const EXIT_STORED: i32 = 4;
/// Exit code of a failed send.
pub const EXIT_FAILED: i32 = 5;

/// What a delivery came to: the exit code, and the text the sending agent reads.
pub struct Outcome {
    /// Exit code for scripts: 0 sent, [`EXIT_REFUSED`], [`EXIT_UNKNOWN`],
    /// [`EXIT_STORED`] or [`EXIT_FAILED`].
    pub code: i32,
    /// What the sending agent reads.
    pub text: String,
}

impl Outcome {
    /// An outcome with this code and text.
    pub fn new(code: i32, text: impl Into<String>) -> Self {
        Self {
            code,
            text: text.into(),
        }
    }
}

/// Options of one send.
#[derive(Debug, Clone, Default)]
pub struct SendOptions {
    /// Id of the message this one answers.
    pub in_reply_to: Option<String>,
    /// Also send one notice when the receiver's next turn ends.
    pub notify_when_idle: bool,
    /// Only enqueue; never start a turn in an idle receiver.
    pub queue: bool,
    /// Idempotency key: repeating a send with it does not send twice.
    pub idempotency_key: Option<String>,
}

/// Send `body` from `caller` to `to` (an address, `name@machine`, or a name
/// unique on this machine): through Teams when `to` is on another machine,
/// else through the router's door here. The one send every sender uses.
pub async fn send(
    homes: &HarnessHomes,
    caller: &Caller,
    to: &str,
    body: &str,
    options: SendOptions,
) -> std::io::Result<Outcome> {
    let SendOptions {
        in_reply_to,
        notify_when_idle,
        queue,
        idempotency_key,
    } = options;
    if let Some(machine) = remote_machine(to) {
        let request = serde_json::json!({
            "op": "send",
            "from": caller.address.to_string(),
            "from_name": caller.name,
            "to": to,
            "body": body,
            "in_reply_to": in_reply_to,
            "notify_when_idle": notify_when_idle,
            "queue": queue,
            "id": idempotency_key,
        });
        return Ok(remote_send(&machine, &request));
    }
    deliver(
        homes,
        caller,
        to,
        body,
        in_reply_to,
        notify_when_idle,
        queue,
        idempotency_key,
    )
    .await
}

/// The machine `to` names when it is not this one.
fn remote_machine(to: &str) -> Option<String> {
    let local = local_machine_name();
    let machine = match MailAddress::parse(to) {
        Ok(address) => address.machine,
        Err(_) => to.rsplit_once('@')?.1.to_string(),
    };
    (machine != local).then_some(machine)
}

/// Hand a request to another machine's mail door through Teams.
fn remote_send(machine: &str, request: &serde_json::Value) -> Outcome {
    match crate::mailbox::teams_mail(machine, request) {
        Ok(answer) => Outcome::new(
            answer["code"]
                .as_i64()
                .map_or(EXIT_FAILED, |code| code as i32),
            answer["text"]
                .as_str()
                .or_else(|| answer["detail"].as_str())
                .unwrap_or("the other machine answered nothing readable")
                .to_string(),
        ),
        Err(detail) if detail.contains("no mail grant") => Outcome::new(
            EXIT_REFUSED,
            format!(
                "Not sent: machine {machine} does not accept mail from you (no mail grant). Only \
                 an operator can grant it; tell your user. Don't retry."
            ),
        ),
        Err(detail) => Outcome::new(
            EXIT_FAILED,
            format!("Not sent to machine {machine}: {detail}"),
        ),
    }
}

/// Deliver from `caller` to `to` on this machine, through the one router
/// every sender uses (`crate::mail_route`).
#[allow(clippy::too_many_arguments)]
async fn deliver(
    homes: &HarnessHomes,
    caller: &Caller,
    to: &str,
    body: &str,
    in_reply_to: Option<String>,
    notify_when_idle: bool,
    queue: bool,
    idempotency_key: Option<String>,
) -> std::io::Result<Outcome> {
    use crate::mail_route::{deliver as route, door_for, Delivered, Refused};
    // An operator (a board, a script) is not a listed session; its address is
    // taken as given.
    let (address, name) = match MailAddress::parse(to) {
        Ok(address) if address.harness == "operator" => {
            let name = format!("{}@{}", address.session_id, address.machine);
            (address, name)
        }
        _ => match LiveSessions::read(homes).resolve(to) {
            Ok(session) => (session.address.clone(), session.name.clone()),
            Err(Unresolved::Stale(message) | Unresolved::Unknown(message)) => {
                return Ok(Outcome::new(EXIT_UNKNOWN, message));
            }
        },
    };
    if address == caller.address {
        return Ok(Outcome::new(
            EXIT_REFUSED,
            "Not sent: that address is your own session.",
        ));
    }
    let door = match door_for(homes, &address) {
        Ok(door) => door,
        Err(_) => {
            return Ok(Outcome::new(
                EXIT_UNKNOWN,
                format!(
                    "{name} is no longer running. Nothing was sent. Run supercode message list for \
                     the live sessions."
                ),
            ))
        }
    };
    let message_id = match &idempotency_key {
        Some(key) => format!(
            "m-{}",
            &blake3::hash(format!("{}\0{key}", caller.address).as_bytes()).to_hex()[..24]
        ),
        None => crate::mailbox::new_message_id()?,
    };
    let sent_marker = sent_marker(&caller.address, &message_id);
    if let Ok(previous) = std::fs::read_to_string(&sent_marker) {
        return Ok(Outcome::new(
            0,
            format!(
                "Already sent with --id {}: {previous} Nothing was sent again.",
                idempotency_key.as_deref().unwrap_or_default()
            ),
        ));
    }
    if recent_sends(&caller.address, &address) >= LOOP_LIMIT {
        return Ok(Outcome::new(
            EXIT_REFUSED,
            format!(
                "Not sent: this would be message {} from you to {name} in {} minutes. If you two \
                 are trading acknowledgements or status, stop; tell your user if the exchange must \
                 go on.",
                LOOP_LIMIT + 1,
                LOOP_WINDOW.as_secs() / 60
            ),
        ));
    }
    let mut envelope = Envelope::new(
        caller.address.clone(),
        caller.name.clone(),
        MailKind::Peer,
        ReplyVia::Command,
        body,
    )?;
    envelope.id = message_id.clone();
    envelope.in_reply_to = in_reply_to;
    let tier = door.name();
    let idle_note = if notify_when_idle {
        " Subscribed: one idle notice reaches you when its next turn ends."
    } else {
        ""
    };
    let (code, what) = match route(&envelope, &address, &door, !queue, notify_when_idle).await {
        Err(detail) => {
            return Ok(Outcome::new(
                EXIT_FAILED,
                format!("Not sent to {name}: {detail}"),
            ))
        }
        Ok(Err(Refused::CannotQueueNative)) => {
            return Ok(Outcome::new(
                EXIT_REFUSED,
                format!(
                    "Not sent: {name} is idle, and a Claude session always starts a turn when a \
                     message arrives, so --queue cannot hold it. Send without --queue to wake it."
                ),
            ))
        }
        Ok(Err(Refused::TooLong(bytes))) => {
            return Ok(Outcome::new(
                EXIT_REFUSED,
                format!(
                    "Not sent: the message is {bytes} bytes; the limit is {}. Write it to a file \
                     and send its path instead.",
                    crate::mail_route::MAX_RELAYED_BYTES
                ),
            ))
        }
        Ok(Ok(Delivered::Steered)) => (0, "steered into its running turn"),
        Ok(Ok(Delivered::Started)) => (0, "it was idle, so the message started a turn"),
        Ok(Ok(Delivered::Native { busy: true })) => (0, "it will read it at its next tool call"),
        Ok(Ok(Delivered::Native { busy: false })) => {
            (0, "it was idle, so the message starts its next turn")
        }
        Ok(Ok(Delivered::Hooked)) => (
            0,
            "its hook points it at the message at its next tool call, or when its turn ends; an \
             idle session sees it on its next turn",
        ),
        Ok(Ok(Delivered::Queued)) => (
            0,
            "it is idle and --queue leaves it so; the message waits in its mailbox",
        ),
        Ok(Ok(Delivered::Operator)) => (0, "filed in its mailbox"),
        Ok(Ok(Delivered::Stored)) => (
            EXIT_STORED,
            "Stored (not failed): it has no delivery door, so it sees this only if it runs \
             supercode message inbox (a Codex session gets one with: supercode message setup \
             codex). Don't resend, and don't wait for a reply",
        ),
    };
    record_send(&caller.address, &address, &message_id);
    let text = if code == 0 {
        format!(
            "sent to {name} ({}, {tier}): {what}. Message id {message_id}.{idle_note} Don't poll; \
             carry on.",
            address.harness
        )
    } else {
        format!(
            "{name} ({}): {what}. Message id {message_id}.{idle_note}",
            address.harness
        )
    };
    if code == 0 && idempotency_key.is_some() {
        if let Some(parent) = sent_marker.parent() {
            std::fs::create_dir_all(parent).ok();
        }
        std::fs::write(&sent_marker, &text).ok();
    }
    Ok(Outcome::new(code, text))
}

/// Messages to one peer within [`LOOP_WINDOW`] beyond which a send is
/// refused: two agents trading acknowledgements never stop on their own.
const LOOP_LIMIT: usize = 8;
const LOOP_WINDOW: Duration = Duration::from_secs(10 * 60);

fn send_log(sender: &MailAddress) -> PathBuf {
    let hash = blake3::hash(sender.to_string().as_bytes()).to_hex();
    mail_root().join("sent").join(&hash[..24]).join("log.jsonl")
}

/// Sends from `sender` to `to` within the loop window.
fn recent_sends(sender: &MailAddress, to: &MailAddress) -> usize {
    let now = now_ms();
    let window = LOOP_WINDOW.as_millis() as u64;
    std::fs::read_to_string(send_log(sender))
        .unwrap_or_default()
        .lines()
        .filter_map(|line| serde_json::from_str::<serde_json::Value>(line).ok())
        .filter(|entry| entry["to"].as_str() == Some(to.to_string().as_str()))
        .filter(|entry| {
            entry["at_ms"]
                .as_u64()
                .is_some_and(|at| now.saturating_sub(at) < window)
        })
        .count()
}

fn record_send(sender: &MailAddress, to: &MailAddress, message_id: &str) {
    let log = send_log(sender);
    if let Some(parent) = log.parent() {
        std::fs::create_dir_all(parent).ok();
    }
    if let Ok(mut file) = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&log)
    {
        use std::io::Write as _;
        let entry = serde_json::json!({"to": to.to_string(), "at_ms": now_ms(), "id": message_id});
        writeln!(file, "{entry}").ok();
    }
}

fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|elapsed| elapsed.as_millis() as u64)
        .unwrap_or_default()
}

/// Marker recording that `message_id` was sent by `sender`, for `--id`.
fn sent_marker(sender: &MailAddress, message_id: &str) -> PathBuf {
    let hash = blake3::hash(sender.to_string().as_bytes()).to_hex();
    mail_root().join("sent").join(&hash[..24]).join(message_id)
}