pub mod assets;
mod claude;
mod codex;
mod grok;
mod omp;
pub mod summary;
use std::{
ffi::OsString,
fs::File,
io::{BufRead, BufReader, Read},
path::{Path, PathBuf},
time::{Duration, SystemTime},
};
pub use claude::Claude;
pub use codex::Codex;
pub use grok::Grok;
#[cfg(test)]
pub(crate) use grok::encode_cwd;
pub use omp::Omp;
pub const CAPTURE_ENV: &str = "FLEETCOM_CAPTURE_FILE";
pub const NOTIFY_CHAIN_ENV: &str = "FLEETCOM_NOTIFY_CHAIN";
const CORRELATE_WINDOW: Duration = Duration::from_secs(30);
pub trait Harness: Sync {
fn home_env_var(&self) -> &'static str;
fn home_dot_dir(&self) -> &'static str;
fn resolve_home(&self, env: &dyn Fn(&str) -> Option<PathBuf>) -> Option<PathBuf> {
env(self.home_env_var()).or_else(|| Some(env("HOME")?.join(self.home_dot_dir())))
}
fn home_root(&self, home: Option<&Path>) -> Option<PathBuf> {
match home {
Some(p) => Some(p.to_path_buf()),
None => Some(dirs::home_dir()?.join(self.home_dot_dir())),
}
}
fn shape(&self) -> (&'static str, &'static str);
fn detect(&self, cmd: &str) -> Option<Invocation> {
let (program, selector) = self.shape();
detect_shape(cmd, program, selector)
}
fn instrument(
&self,
inv: &Invocation,
capture: &CapturePaths,
home: Option<&Path>,
) -> SpawnPlan;
fn parse_capture(&self, _payload: &str) -> Option<String> {
None
}
fn scrape_exit(&self, text: &str) -> Option<String>;
fn live_session_id(
&self,
_pid: u32,
_cwd: &Path,
_spawned: SystemTime,
_home: Option<&Path>,
) -> Option<String> {
None
}
fn live_blocked_status(
&self,
_pid: u32,
_cwd: &Path,
_spawned: SystemTime,
_home: Option<&Path>,
) -> Option<(String, &'static str)> {
None
}
fn correlate_fs(&self, cwd: &Path, spawned: SystemTime, home: Option<&Path>) -> Option<String>;
fn resume_command(&self, cmd: &str, id: &str) -> String {
let (program, selector) = self.shape();
resume_shape(cmd, program, selector, id)
}
}
struct Agent {
harness: &'static dyn Harness,
summary: &'static dyn crate::preview::SummaryAdapter,
}
static AGENTS: &[Agent] = &[
Agent {
harness: &Claude,
summary: &summary::ClaudeSummary,
},
Agent {
harness: &Codex,
summary: &summary::CodexSummary,
},
Agent {
harness: &Grok,
summary: &summary::GrokSummary,
},
Agent {
harness: &Omp,
summary: &summary::OmpSummary,
},
];
pub fn detect(cmd: &str) -> Option<(&'static dyn Harness, Invocation)> {
AGENTS
.iter()
.find_map(|a| a.harness.detect(cmd).map(|inv| (a.harness, inv)))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Invocation {
Bare,
Resume(String),
}
impl Invocation {
pub fn known_id(self) -> Option<String> {
match self {
Self::Bare => None,
Self::Resume(id) => Some(id),
}
}
}
#[derive(Debug, Clone)]
pub struct CapturePaths {
pub capture_file: PathBuf,
pub claude_settings: PathBuf,
pub codex_notify: PathBuf,
pub omp_capture: PathBuf,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SpawnPlan {
pub args_suffix: String,
pub env: Vec<(OsString, OsString)>,
pub injected_id: Option<String>,
}
const PROGRAM_WORD_REFUSALS: &[char] = &[
'|', ';', '&', '<', '>', '$', '#', '`', '(', ')', '\\', '\'', '"', '=', '\n', '\r', '*', '?',
'[', ']', '{', '}',
];
fn detect_shape(cmd: &str, program: &str, selector: &str) -> Option<Invocation> {
let mut words = cmd.split([' ', '\t']).filter(|w| !w.is_empty());
let first = words.next()?;
if first.contains(PROGRAM_WORD_REFUSALS) || Path::new(first).file_name()?.to_str()? != program {
return None;
}
let Some(sel) = words.next() else {
return Some(Invocation::Bare);
};
let id = unquote(words.next()?);
(sel == selector && is_uuid(id) && words.next().is_none())
.then(|| Invocation::Resume(id.to_string()))
}
fn unquote(token: &str) -> &str {
token
.strip_prefix('\'')
.and_then(|t| t.strip_suffix('\''))
.unwrap_or(token)
}
fn resume_shape(cmd: &str, program: &str, selector: &str, id: &str) -> String {
if !is_uuid(id) || detect_shape(cmd, program, selector).is_none() {
return cmd.to_string();
}
let first = cmd
.split([' ', '\t'])
.find(|w| !w.is_empty())
.expect("detect_shape accepted a program word");
format!("{first} {selector} {}", shell_quote(id))
}
pub fn is_uuid(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 36
&& b.iter().enumerate().all(|(i, &c)| match i {
8 | 13 | 18 | 23 => c == b'-',
_ => matches!(c, b'0'..=b'9' | b'a'..=b'f'),
})
}
fn capture_id(v: &jzon::JsonValue, key: &str) -> Option<String> {
let id = v[key].as_str()?;
is_uuid(id).then(|| id.to_string())
}
fn leading_uuid(s: &str) -> Option<&str> {
let head = s.get(..36).filter(|h| is_uuid(h))?;
match s.as_bytes().get(36) {
Some(&c) if c.is_ascii_alphanumeric() || c == b'-' || c == b'_' => None,
_ => Some(head),
}
}
fn last_hint(text: &str, hints: &[&str]) -> Option<String> {
let mut last: Option<(usize, String)> = None;
for hint in hints {
for (i, _) in text.match_indices(hint) {
if let Some(id) = leading_uuid(&text[i + hint.len()..])
&& last.as_ref().is_none_or(|(j, _)| i > *j)
{
last = Some((i, id.to_string()));
}
}
}
last.map(|(_, id)| id)
}
fn uuid_v4() -> Option<String> {
use std::fmt::Write;
let mut bytes = [0u8; 16];
File::open("/dev/urandom")
.ok()?
.read_exact(&mut bytes)
.ok()?;
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
let mut out = String::with_capacity(36);
for (i, b) in bytes.iter().enumerate() {
if matches!(i, 4 | 6 | 8 | 10) {
out.push('-');
}
let _ = write!(out, "{b:02x}");
}
Some(out)
}
fn pin_plan(inv: &Invocation) -> SpawnPlan {
let mut plan = SpawnPlan::default();
if *inv == Invocation::Bare
&& let Some(id) = uuid_v4()
{
plan.args_suffix = format!(" --session-id {}", shell_quote(&id));
plan.injected_id = Some(id);
}
plan
}
fn within_window(a: SystemTime, b: SystemTime) -> bool {
match a.duration_since(b) {
Ok(d) => d <= CORRELATE_WINDOW,
Err(e) => e.duration() <= CORRELATE_WINDOW,
}
}
fn within_window_ms(a: u128, b: u128) -> bool {
a.abs_diff(b) <= CORRELATE_WINDOW.as_millis()
}
fn v7_millis(id: &str) -> Option<u64> {
if id.as_bytes()[14] != b'7' {
return None;
}
u64::from_str_radix(&format!("{}{}", &id[..8], &id[9..13]), 16).ok()
}
fn unix_millis(t: SystemTime) -> Option<u128> {
Some(t.duration_since(SystemTime::UNIX_EPOCH).ok()?.as_millis())
}
fn same_cwd(recorded: &Path, cwd: &Path, canon: Option<&Path>) -> bool {
recorded == cwd || canon.is_some_and(|c| recorded == c)
}
fn sole_id(candidates: Vec<String>) -> Option<String> {
match candidates.as_slice() {
[only] if is_uuid(only) => Some(only.clone()),
_ => None,
}
}
fn push_unique<T: PartialEq>(v: &mut Vec<T>, x: T) {
if !v.contains(&x) {
v.push(x);
}
}
fn jsonl_head(path: &Path, n: usize) -> Option<Vec<Option<jzon::JsonValue>>> {
let file = File::open(path).ok()?;
let mut reader = BufReader::new(file.take(64 * 1024));
let mut records = Vec::with_capacity(n);
let mut line = String::new();
for _ in 0..n {
line.clear();
match reader.read_line(&mut line) {
Ok(len) if len > 0 => {}
_ => return None,
}
records.push(jzon::parse(&line).ok());
}
Some(records)
}
fn shell_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', "'\\''"))
}
#[cfg(test)]
pub(crate) mod fixtures {
use std::path::PathBuf;
use super::{CapturePaths, Harness};
use crate::testutil::corpus_emulator;
pub(crate) const ID: &str = "c8c4a5cc-0b32-4ba0-a6b4-6ed08c218e0d";
pub(crate) const OTHER: &str = "11111111-2222-4333-8444-555555555555";
pub(super) fn paths() -> CapturePaths {
CapturePaths {
capture_file: PathBuf::from("/tmp/cap/session.json"),
claude_settings: PathBuf::from("/tmp/Application Support/fleetcom.json"),
codex_notify: PathBuf::from("/tmp/Application Support/notify.sh"),
omp_capture: PathBuf::from("/tmp/Application Support/omp-capture.js"),
}
}
pub(super) fn assert_all_opaque(h: &dyn Harness, id: &str, cmds: &[String]) {
for cmd in cmds {
assert_eq!(h.detect(cmd), None, "{cmd:?} must be opaque");
let resumed = h.resume_command(cmd, id);
assert_eq!(resumed, *cmd, "an opaque command must never be rewritten");
}
}
pub(super) fn assert_corpus_scrape(h: &dyn Harness, bytes: &[u8], expected: &str) {
let mut emu = corpus_emulator();
emu.process(bytes);
let text = emu.text_with_history();
assert_eq!(h.scrape_exit(&text).as_deref(), Some(expected));
}
}
#[cfg(test)]
mod tests {
use super::{
fixtures::{ID, OTHER},
*,
};
const BIN: &str = "/usr/local/bin";
#[test]
fn every_harness_detects_the_two_authored_shapes() {
for a in AGENTS {
let h = a.harness;
let (prog, sel) = h.shape();
assert_eq!(h.detect(prog), Some(Invocation::Bare), "{prog}");
assert_eq!(
h.detect(&format!("{BIN}/{prog}")),
Some(Invocation::Bare),
"{prog}"
);
for cmd in [
format!("{prog} {sel} {ID}"),
format!("{prog} {sel} '{ID}'"),
format!("{BIN}/{prog} {sel} '{ID}'"),
] {
assert_eq!(h.detect(&cmd), Some(Invocation::Resume(ID.into())), "{cmd}");
}
}
}
#[test]
fn every_harness_regenerates_the_canonical_resume_form() {
for a in AGENTS {
let h = a.harness;
let (prog, sel) = h.shape();
let canonical = format!("{prog} {sel} '{ID}'");
assert_eq!(h.resume_command(prog, ID), canonical, "{prog}");
assert_eq!(
h.resume_command(&format!("{BIN}/{prog}"), ID),
format!("{BIN}/{prog} {sel} '{ID}'")
);
assert_eq!(
h.resume_command(&format!("{prog} {sel} '{OTHER}'"), ID),
canonical
);
assert_eq!(
h.resume_command(&format!("{prog} {sel} {OTHER}"), ID),
canonical
);
for bad in ["evil'", "not-an-id"] {
assert_eq!(h.resume_command(prog, bad), prog, "{prog} {bad:?}");
}
}
}
#[test]
fn every_harness_keeps_shared_shell_syntax_opaque() {
for a in AGENTS {
let h = a.harness;
let (prog, sel) = h.shape();
let opaque = [
format!("{prog} 'fix the tests'"),
format!("{prog} {sel}"),
format!("{prog} {sel} not-a-uuid"),
format!("{prog} {sel} $ID"),
format!("{prog} {sel}={ID}"),
format!("{prog} {sel} '{ID}' 'and do x'"),
format!("{prog} {sel} {ID}ff"),
format!("{prog} | tee log"),
format!("{prog}; ls"),
format!("FOO=bar {prog}"),
String::new(),
];
for cmd in opaque {
assert_eq!(h.detect(&cmd), None, "{cmd:?} must be opaque");
assert_eq!(
h.resume_command(&cmd, ID),
cmd,
"an opaque command must never be rewritten"
);
}
for other in AGENTS.iter().map(|o| o.harness.shape().0) {
if other != prog {
assert_eq!(h.detect(other), None, "{other:?} is not {prog}");
}
}
}
}
#[test]
fn is_uuid_accepts_only_the_strict_shape() {
assert!(is_uuid(ID));
assert!(is_uuid("00000000-0000-0000-0000-000000000000"));
assert!(!is_uuid("C8C4A5CC-0B32-4BA0-A6B4-6ED08C218E0D"));
assert!(!is_uuid("c8c4a5cc-0b32-4ba0-a6b4-6ed08c218e0"));
assert!(!is_uuid("c8c4a5cc-0b32-4ba0-a6b4-6ed08c218e0dd"));
assert!(!is_uuid("c8c4a5cc0b32-4ba0-a6b4-6ed08c218e0d0"));
assert!(!is_uuid("c8c4a5cc-0b32-4ba0-a6b4-6ed08c218g0d"));
assert!(!is_uuid("my session name"));
assert!(!is_uuid("/tmp/evil; rm -rf ~"));
assert!(!is_uuid(""));
}
#[test]
fn leading_uuid_requires_a_token_boundary() {
assert_eq!(leading_uuid(ID), Some(ID));
assert_eq!(leading_uuid(&format!("{ID} tail")), Some(ID));
assert_eq!(leading_uuid(&format!("{ID})")), Some(ID));
assert_eq!(leading_uuid(&format!("{ID}f")), None);
assert_eq!(leading_uuid(&format!("{ID}-x")), None);
assert_eq!(leading_uuid(&format!("{ID}_x")), None);
assert_eq!(leading_uuid("short"), None);
}
#[test]
fn uuid_v4_is_strict_versioned_and_random() {
let a = uuid_v4().expect("/dev/urandom must be readable");
let b = uuid_v4().unwrap();
assert!(is_uuid(&a));
assert_eq!(a.as_bytes()[14], b'4', "version nibble");
assert!(
matches!(a.as_bytes()[19], b'8' | b'9' | b'a' | b'b'),
"variant bits"
);
assert_ne!(a, b);
}
#[test]
fn detect_shape_strips_exactly_one_quote_pair() {
assert_eq!(
detect_shape(&format!("claude --resume '{ID}'"), "claude", "--resume"),
Some(Invocation::Resume(ID.into()))
);
for token in [format!("'{ID}"), format!("{ID}'"), format!("''{ID}''")] {
assert_eq!(
detect_shape(&format!("claude --resume {token}"), "claude", "--resume"),
None,
"{token:?}"
);
}
}
#[test]
fn detect_shape_matches_basenames_and_refuses_shell_syntax_in_them() {
assert_eq!(
detect_shape("/usr/local/bin/claude", "claude", "--resume"),
Some(Invocation::Bare)
);
for cmd in [
"$HOME/bin/claude",
"a=b/claude",
"'/bin/claude'",
"/tmp/x;y/claude",
"/tmp/x`y`/claude",
"claude\nls",
] {
assert_eq!(detect_shape(cmd, "claude", "--resume"), None, "{cmd:?}");
}
}
#[test]
fn detect_shape_refuses_expanding_metacharacters_but_accepts_tilde() {
assert_eq!(
detect_shape("~/bin/claude", "claude", "--resume"),
Some(Invocation::Bare)
);
for (cmd, prog, sel) in [
("tools/*/claude", "claude", "--resume"),
("/opt/{stable,beta}/codex", "codex", "resume"),
("a?b/claude", "claude", "--resume"),
("[a]/grok", "grok", "--resume"),
] {
assert_eq!(detect_shape(cmd, prog, sel), None, "{cmd:?}");
}
}
#[test]
fn shell_quote_survives_spaces_and_single_quotes() {
let path = "/Users/x/Application Support/it's here/settings.json";
let quoted = shell_quote(path);
assert_eq!(
quoted,
"'/Users/x/Application Support/it'\\''s here/settings.json'"
);
let out = std::process::Command::new("sh")
.arg("-c")
.arg(format!("printf '%s' {quoted}"))
.output()
.expect("sh must run");
assert_eq!(String::from_utf8(out.stdout).unwrap(), path);
}
#[test]
fn within_window_is_symmetric_and_bounded() {
let t = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000);
assert!(within_window(t, t + Duration::from_secs(30)));
assert!(within_window(t + Duration::from_secs(30), t));
assert!(!within_window(t, t + Duration::from_secs(31)));
assert!(within_window_ms(5_000, 35_000));
assert!(!within_window_ms(5_000, 35_001));
}
#[test]
fn home_env_vars_name_each_tools_override() {
const OVERRIDES: [(&str, &str); 4] = [
("CLAUDE_CONFIG_DIR", ".claude"),
("CODEX_HOME", ".codex"),
("GROK_HOME", ".grok"),
("PI_CODING_AGENT_SESSION_DIR", ".omp/agent/sessions"),
];
assert_eq!(
AGENTS.len(),
OVERRIDES.len(),
"a new harness needs its (env var, dot dir) row added here"
);
for (a, (env_var, dot_dir)) in AGENTS.iter().zip(OVERRIDES) {
let program = a.harness.shape().0;
assert_eq!(a.harness.home_env_var(), env_var, "{program}");
assert_eq!(a.harness.home_dot_dir(), dot_dir, "{program}");
}
}
#[test]
fn registry_detect_routes_to_the_matching_harness() {
assert_eq!(AGENTS.len(), 4, "route the new harness's command here");
let (h, inv) = detect("claude").unwrap();
assert_eq!(h.home_dot_dir(), ".claude");
assert_eq!(inv, Invocation::Bare);
let (h, inv) = detect(&format!("codex resume {ID}")).unwrap();
assert_eq!(h.home_dot_dir(), ".codex");
assert_eq!(inv, Invocation::Resume(ID.into()));
let (h, inv) = detect("grok").unwrap();
assert_eq!(h.home_dot_dir(), ".grok");
assert_eq!(inv, Invocation::Bare);
let (h, inv) = detect("omp").unwrap();
assert_eq!(h.home_dot_dir(), ".omp/agent/sessions");
assert_eq!(inv, Invocation::Bare);
assert!(detect("vim").is_none());
assert!(detect("").is_none());
}
}