supercode-harness 0.5.148

The optional native Volter Harness agent and tool harness
Documentation
//! The agent a launch named in a session's environment, kept in the harness home the session is stored in.
//!
//! A launcher that starts a session for an agent puts the agent's id and reader context in the
//! session's environment (`SUPERCODE_AGENT_*`). A managed runtime records those variables in the
//! session's harness home (`<home>/supercode/launches/<session id>.json`) before the session's
//! transcript is visible, so whoever discovers the session reads its agent from the session's own
//! home ([`read`], the `harness.v1.sessions.launch_agent` door), however short its process lived.
//! The machine daemon binds the session to that agent when its connector discovers it.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use serde_json::{json, Value};

use crate::SessionLocator;

/// The launch variables recorded: who the session is, never a credential (a context is a name in
/// the machine's own contexts file).
const NAMES: &[&str] = &[
    "SUPERCODE_AGENT_ID",
    "SUPERCODE_AGENT_ORG",
    "SUPERCODE_AGENT_CONTEXT",
    "SUPERCODE_AGENT_MAIN",
    "SUPERCODE_AGENT_ASSIGNMENTS",
];

/// The record of `session_id` in the harness home `home`; none for an id that is not a plain name.
fn record_path(home: &Path, session_id: &str) -> Option<PathBuf> {
    (!session_id.is_empty()
        && session_id
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'))
    .then(|| {
        home.join("supercode")
            .join("launches")
            .join(format!("{session_id}.json"))
    })
}

/// The harness home a session's transcript is stored in: a Codex rollout lies under
/// `<CODEX_HOME>/sessions/`, a Claude Code transcript under `<CLAUDE_CONFIG_DIR>/projects/`.
fn home_of(locator: &SessionLocator) -> Option<PathBuf> {
    let crate::StorageLocator::File { path } = &locator.storage else {
        return None;
    };
    let store = match locator.harness.as_str() {
        crate::HarnessId::CODEX => "sessions",
        crate::HarnessId::CLAUDE_CODE => "projects",
        _ => return None,
    };
    path.ancestors()
        .find(|dir| dir.file_name().and_then(|name| name.to_str()) == Some(store))
        .and_then(Path::parent)
        .map(Path::to_path_buf)
}

/// The agent variables of a launch environment, or `None` when it names no agent.
pub fn agent_environment(env: &BTreeMap<String, String>) -> Option<BTreeMap<String, String>> {
    env.get("SUPERCODE_AGENT_ID").filter(|id| !id.is_empty())?;
    Some(
        NAMES
            .iter()
            .filter_map(|name| {
                env.get(*name)
                    .map(|value| (name.to_string(), value.clone()))
            })
            .collect(),
    )
}

/// Record `env`'s agent variables for `session_id` in the harness home `home`, written whole and
/// renamed into place. A launch that names no agent records nothing.
pub fn record(
    home: &Path,
    session_id: &str,
    env: &BTreeMap<String, String>,
) -> std::io::Result<()> {
    let Some(agent) = agent_environment(env) else {
        return Ok(());
    };
    let Some(path) = record_path(home, session_id) else {
        return Ok(());
    };
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    let temporary = path.with_extension(format!("{}.tmp", std::process::id()));
    let body = serde_json::to_vec(&json!({"version": 1, "environment": agent}))?;
    {
        use std::io::Write;
        let mut options = std::fs::OpenOptions::new();
        options.write(true).create(true).truncate(true);
        #[cfg(unix)]
        {
            use std::os::unix::fs::OpenOptionsExt;
            options.mode(0o600);
        }
        let mut file = options.open(&temporary)?;
        file.write_all(&body)?;
        file.sync_all()?;
    }
    std::fs::rename(&temporary, &path)
}

/// The agent variables recorded for the session at `locator`, or null when its launch named none.
pub fn read(locator: &SessionLocator) -> Value {
    home_of(locator)
        .and_then(|home| record_path(&home, &locator.session_id))
        .and_then(|path| std::fs::read(path).ok())
        .and_then(|bytes| serde_json::from_slice::<Value>(&bytes).ok())
        .and_then(|record| record.get("environment").cloned())
        .filter(Value::is_object)
        .unwrap_or(Value::Null)
}