use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde_json::{json, Value};
use crate::SessionLocator;
const NAMES: &[&str] = &[
"SUPERCODE_AGENT_ID",
"SUPERCODE_AGENT_ORG",
"SUPERCODE_AGENT_CONTEXT",
"SUPERCODE_AGENT_MAIN",
"SUPERCODE_AGENT_ASSIGNMENTS",
];
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"))
})
}
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)
}
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(),
)
}
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)
}
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)
}