use serde_json::Value;
use std::{
collections::HashMap,
env, fs,
io::{Read as _, Seek as _},
path::Path,
process::Command,
};
pub(crate) fn verified() -> Option<String> {
if let Some(pane) = crate::invocation::current().pane.as_deref() {
return named(pane);
}
let native = inherited();
if native.is_some() {
return native;
}
crate::current_session()
.ok()
.flatten()
.and_then(|session| for_session(&session, crate::invocation::current().pane.as_deref()))
}
pub(crate) fn named(pane: &str) -> Option<String> {
crate::current_session()
.ok()
.flatten()
.and_then(|session| for_session(&session, Some(pane)))
.or_else(|| crate::workflow::trusts_recorded_pane(pane).then(|| pane.to_owned()))
}
pub(crate) fn live_owner_pane(pane: &str) -> Option<String> {
live_kind(pane)
.as_deref()
.is_some_and(owner_inbox_kind)
.then(|| pane.to_owned())
}
fn inherited() -> Option<String> {
if env::var("HERDR_ENV").as_deref() != Ok("1") {
return None;
}
let pane = env::var("HERDR_PANE_ID")
.ok()
.filter(|pane| !pane.trim().is_empty())?;
#[cfg(debug_assertions)]
if env::var("BRGR_TEST_TRUST_HERDR_PANE").as_deref() == Ok("1") {
return Some(pane);
}
runs_inside(&pane).then_some(pane)
}
pub(crate) fn for_session(session: &str, explicit: Option<&str>) -> Option<String> {
if let Some(pane) = traced(session) {
return explicit
.is_none_or(|expected| expected == pane)
.then_some(pane);
}
if let Some(pane) = explicit
&& inherited().as_deref() == Some(pane)
&& live_kind(pane).as_deref().is_some_and(owner_inbox_kind)
{
return Some(pane.to_owned());
}
bridge_receipt(session, explicit)
}
pub(crate) fn owner_inbox_kind(kind: &str) -> bool {
matches!(kind, "codex" | "claude" | "cursor")
}
pub(crate) fn inherited_agent_kind() -> Option<String> {
live_kind(&inherited()?)
}
const PENDING: &str = "session:";
pub(crate) fn pending_marker(session: &str) -> String {
format!("{PENDING}{session}")
}
pub(crate) fn pending_session(caller: &str) -> Option<&str> {
caller.strip_prefix(PENDING)
}
pub(crate) fn wait_for_session(session: &str, limit: std::time::Duration) -> Option<String> {
let started = std::time::Instant::now();
loop {
if let Some(pane) = traced(session) {
return Some(pane);
}
if started.elapsed() >= limit {
return None;
}
std::thread::sleep(std::time::Duration::from_millis(700));
}
}
const TRACE_PREFIX: usize = 13;
fn traced(session: &str) -> Option<String> {
if crate::invocation::is_hook() {
return None;
}
let prefix: String = session.chars().take(TRACE_PREFIX).collect();
if prefix.chars().count() < TRACE_PREFIX {
return None;
}
let agents = herdr_json(&["agent", "list"])?;
let mut found = Vec::new();
for item in agents.pointer("/result/agents")?.as_array()? {
if item.get("agent").and_then(Value::as_str) != Some("codex") {
continue;
}
let pane = item.get("pane_id").and_then(Value::as_str)?;
let screen = herdr_text(&[
"pane",
"read",
pane,
"--source",
"recent-unwrapped",
"--lines",
"1500",
])?;
if shows_brgr_call(&screen, &prefix) {
found.push(pane.to_owned());
}
}
match found.as_slice() {
[only] => Some(only.clone()),
_ => None,
}
}
pub(crate) const VERIFIED_CODEX: &str = "codex-cli 0.166";
fn shows_brgr_call(screen: &str, prefix: &str) -> bool {
screen.lines().any(|line| {
let line = line
.trim_start()
.trim_start_matches(['•', '└', '│'])
.trim_start();
["Ran ", "Running ", "Run "]
.iter()
.find_map(|verb| line.strip_prefix(verb))
.is_some_and(|command| command.contains(prefix) && invokes_brgr(command))
})
}
fn invokes_brgr(command: &str) -> bool {
command.split(['&', ';', '|']).any(|segment| {
segment
.split_whitespace()
.find(|word| !word.contains('='))
.is_some_and(|program| {
let program = program.trim_matches(['"', '\'']);
program == "brgr"
|| program.ends_with("/brgr")
|| program == "$BRGR_BIN"
|| program == "${BRGR_BIN}"
})
})
}
pub(crate) fn codex_trace_status() -> Value {
let installed = std::process::Command::new("codex")
.arg("--version")
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|text| text.trim().to_owned())
.filter(|text| !text.is_empty());
let verified = installed.as_deref().is_some_and(|version| {
version == VERIFIED_CODEX || version.starts_with(&format!("{VERIFIED_CODEX}."))
});
serde_json::json!({
"codex": installed,
"verified_with": VERIFIED_CODEX,
"verified": verified,
})
}
fn live_kind(pane: &str) -> Option<String> {
herdr_json(&["agent", "get", pane]).and_then(|live| {
let agent = live.pointer("/result/agent")?;
(agent.get("pane_id").and_then(Value::as_str) == Some(pane))
.then(|| {
agent
.get("agent")
.and_then(Value::as_str)
.map(str::to_owned)
})
.flatten()
})
}
fn bridge_receipt(session: &str, explicit: Option<&str>) -> Option<String> {
let cwd = env::current_dir().ok()?;
let mut roots = vec![cwd.clone()];
if let Some(primary) = crate::workspace::primary_checkout(&cwd) {
roots.push(primary);
}
for root in roots {
let Ok(entries) = fs::read_dir(root.join(".agent-progress/bridges")) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_none_or(|v| v != "json") {
continue;
}
let Ok(metadata) = fs::symlink_metadata(&path) else {
continue;
};
if !metadata.is_file() || metadata.len() > 65_536 {
continue;
}
let Some(record) = fs::read(&path)
.ok()
.and_then(|bytes| serde_json::from_slice::<Value>(&bytes).ok())
else {
continue;
};
if record.get("native_session").and_then(Value::as_str) != Some(session)
|| record.get("session").and_then(Value::as_str) != Some(session)
{
continue;
}
let Some(pane) = record.get("pane").and_then(Value::as_str) else {
continue;
};
if explicit.is_some_and(|expected| expected != pane) {
continue;
}
let Some(pid) = record.get("pid").and_then(Value::as_u64) else {
continue;
};
let Some(process) = herdr_json(&["pane", "process-info", "--pane", pane]) else {
continue;
};
if contains_pid(&process, pid) {
return Some(pane.to_owned());
}
}
}
None
}
fn contains_pid(value: &Value, pid: u64) -> bool {
match value {
Value::Object(fields) => fields.iter().any(|(key, value)| {
(matches!(key.as_str(), "pid" | "foreground_process_group")
&& value.as_u64() == Some(pid))
|| contains_pid(value, pid)
}),
Value::Array(values) => values.iter().any(|v| contains_pid(v, pid)),
_ => false,
}
}
fn herdr_output(args: &[&str]) -> Option<Vec<u8>> {
let binary = binary()?;
let mut command = Command::new(binary);
if let Ok(session) = env::var("HERDR_SESSION") {
command.args(["--session", &session]);
}
let mut out = tempfile::tempfile().ok()?;
let err = tempfile::tempfile().ok()?;
let mut child = command
.args(args)
.stdin(std::process::Stdio::null())
.stdout(out.try_clone().ok()?)
.stderr(err)
.spawn()
.ok()?;
let started = std::time::Instant::now();
let status = loop {
if let Some(status) = child.try_wait().ok()? {
break status;
}
let budget = if crate::invocation::is_hook() {
200
} else {
2000
};
if started.elapsed() > std::time::Duration::from_millis(budget) {
let _ = child.kill();
let _ = child.wait();
return None;
}
std::thread::sleep(std::time::Duration::from_millis(5));
};
if !status.success() || out.metadata().ok()?.len() > 1024 * 1024 {
return None;
}
out.rewind().ok()?;
let mut bytes = Vec::new();
out.take(1024 * 1024 + 1).read_to_end(&mut bytes).ok()?;
Some(bytes)
}
fn herdr_json(args: &[&str]) -> Option<Value> {
serde_json::from_slice(&herdr_output(args)?).ok()
}
fn herdr_text(args: &[&str]) -> Option<String> {
Some(String::from_utf8_lossy(&herdr_output(args)?).into_owned())
}
pub(crate) fn binary() -> Option<std::ffi::OsString> {
#[cfg(test)]
if let Some(path) = tests::HERDR.with(|slot| slot.borrow().clone()) {
return Some(path.into_os_string());
}
if let Some(path) = env::var_os("HERDR_BIN_PATH") {
return Some(path);
}
let path = env::var_os("PATH")?;
env::split_paths(&path)
.map(|dir| dir.join("herdr"))
.find(|p| p.is_file())
.and_then(|p| p.canonicalize().ok())
.map(std::path::PathBuf::into_os_string)
}
fn runs_inside(pane: &str) -> bool {
let Some(table) = process_table() else {
return false;
};
let mut pid = std::process::id();
for _ in 0..64 {
let Some((parent, command)) = table.get(&pid) else {
return false;
};
if Path::new(command)
.file_name()
.is_some_and(|name| name == "herdr")
{
return true;
}
if pane_in_environment(pid).is_some_and(|claimed| claimed != pane) {
return false;
}
if *parent <= 1 {
return false;
}
pid = *parent;
}
false
}
fn process_table() -> Option<HashMap<u32, (u32, String)>> {
let output = Command::new("/bin/ps")
.args(["-A", "-o", "pid=,ppid=,comm="])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
Some(
text.lines()
.filter_map(|line| {
let mut fields = line.split_whitespace();
let pid = fields.next()?.parse().ok()?;
let parent = fields.next()?.parse().ok()?;
let command = fields.collect::<Vec<_>>().join(" ");
Some((pid, (parent, command)))
})
.collect(),
)
}
fn pane_in_environment(pid: u32) -> Option<String> {
#[cfg(target_os = "linux")]
{
const MAX_ENVIRONMENT_SIZE: u64 = 1024 * 1024;
let file = fs::File::open(format!("/proc/{pid}/environ")).ok()?;
let mut bytes = Vec::new();
file.take(MAX_ENVIRONMENT_SIZE + 1)
.read_to_end(&mut bytes)
.ok()?;
if bytes.len() as u64 > MAX_ENVIRONMENT_SIZE {
return None;
}
bytes.split(|byte| *byte == 0).find_map(|entry| {
let value = entry.strip_prefix(b"HERDR_PANE_ID=")?;
std::str::from_utf8(value).ok().map(str::to_owned)
})
}
#[cfg(not(target_os = "linux"))]
{
let output = Command::new("/bin/ps")
.args(["-E", "-ww", "-o", "command=", "-p", &pid.to_string()])
.output()
.ok()?;
let text = String::from_utf8_lossy(&output.stdout);
let (_, rest) = text.rsplit_once(" HERDR_PANE_ID=")?;
rest.split_whitespace().next().map(str::to_owned)
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
#[cfg(target_os = "linux")]
use std::process::Stdio;
const SESSION: &str = "01a0fb33-447c-7ba0-9f37-ba04f33ce809";
#[test]
fn a_ran_line_with_the_session_is_the_trace() {
let ran = format!("• Ran brgr --as {SESSION} run \"Run the sh…");
assert!(shows_brgr_call(&ran, "01a0fb33-447c"));
assert!(shows_brgr_call(
"• Ran brgr --as 01a0fb33-447c…",
"01a0fb33-447c"
));
assert!(shows_brgr_call(
&format!("• Running brgr --as {SESSION} status"),
"01a0fb33-447c"
));
}
#[test]
fn a_path_variable_or_cd_prefixed_call_is_a_trace() {
for line in [
format!("• Ran /Users/me/.local/bin/brgr --as {SESSION} status"),
format!("• Ran $BRGR_BIN --as {SESSION} result"),
format!("• Ran cd /repo && brgr --as {SESSION} run \"x\""),
format!("• Ran BRGR_HOME=/tmp/h brgr --as {SESSION} status"),
format!(" └ Run brgr --as {SESSION} wait"),
] {
assert!(shows_brgr_call(&line, "01a0fb33-447c"), "{line}");
}
}
#[test]
fn brgr_as_an_argument_is_not_a_trace() {
assert!(!shows_brgr_call(
&format!("• Ran grep brgr notes.txt {SESSION}"),
"01a0fb33-447c"
));
assert!(!shows_brgr_call(
&format!("• Ran echo brgr --as {SESSION}"),
"01a0fb33-447c"
));
}
#[test]
fn the_bare_id_or_another_command_is_not_a_trace() {
assert!(!shows_brgr_call(
&format!("the session is {SESSION}"),
"01a0fb33-447c"
));
assert!(!shows_brgr_call(
&format!("• Ran echo {SESSION}"),
"01a0fb33-447c"
));
assert!(!shows_brgr_call(
"• Ran brgr --as 11111111-2222 run",
"01a0fb33-447c"
));
assert!(!shows_brgr_call(
&format!("› please run brgr --as {SESSION} status"),
"01a0fb33-447c"
));
}
fn fake_herdr(screens: &[(&str, &str, &str)]) -> (tempfile::TempDir, std::path::PathBuf) {
let temp = tempfile::tempdir().unwrap();
let mut agents = Vec::new();
for (pane, kind, screen) in screens {
fs::write(temp.path().join(pane.replace(':', "_")), screen).unwrap();
agents.push(format!(r#"{{"pane_id":"{pane}","agent":"{kind}"}}"#));
}
fs::write(
temp.path().join("list.json"),
format!(r#"{{"result":{{"agents":[{}]}}}}"#, agents.join(",")),
)
.unwrap();
let binary = temp.path().join("herdr");
crate::fast_script::write_fast_script(
&binary,
"#!/bin/sh\nd=$(/usr/bin/dirname \"$0\")\nif [ \"$1\" = --session ]; then shift 2; fi\ncase \"$1 $2\" in\n 'agent list') /bin/cat \"$d/list.json\";;\n 'pane read') f=$(printf %s \"$3\" | /usr/bin/sed 's/:/_/'); /bin/cat \"$d/$f\";;\n *) exit 2;;\nesac\n",
);
(temp, binary)
}
fn trace_with(screens: &[(&str, &str, &str)]) -> Option<String> {
let (_temp, binary) = fake_herdr(screens);
with_herdr(&binary, || traced(SESSION))
}
thread_local! {
pub(crate) static HERDR: std::cell::RefCell<Option<std::path::PathBuf>> =
const { std::cell::RefCell::new(None) };
}
fn with_herdr<T>(binary: &Path, run: impl FnOnce() -> T) -> T {
HERDR.with(|slot| *slot.borrow_mut() = Some(binary.to_path_buf()));
let out = run();
HERDR.with(|slot| *slot.borrow_mut() = None);
out
}
#[test]
fn a_session_flag_does_not_hide_the_fixture_panes() {
let (_temp, binary) = fake_herdr(&[("w1:p2", "codex", "idle")]);
let list = Command::new(&binary)
.args(["--session", "ambient-session", "agent", "list"])
.output()
.unwrap();
assert!(
list.status.success(),
"{}",
String::from_utf8_lossy(&list.stderr)
);
let agents: Value = serde_json::from_slice(&list.stdout).unwrap();
assert_eq!(
agents
.pointer("/result/agents/0/pane_id")
.and_then(Value::as_str),
Some("w1:p2")
);
let screen = Command::new(&binary)
.args(["--session", "ambient-session", "pane", "read", "w1:p2"])
.output()
.unwrap();
assert!(screen.status.success());
assert_eq!(String::from_utf8_lossy(&screen.stdout), "idle");
}
#[test]
fn exactly_one_codex_pane_showing_the_call_is_the_callers() {
let ran = format!("• Ran brgr --as {SESSION} run \"x\"");
let found = trace_with(&[
("w1:p1", "codex", "• Ran echo hello"),
("w1:p2", "codex", &ran),
("w1:p3", "codex", "idle"),
]);
assert_eq!(found.as_deref(), Some("w1:p2"));
}
#[test]
fn no_pane_or_two_panes_prove_nothing() {
let ran = format!("• Ran brgr --as {SESSION} run \"x\"");
assert_eq!(trace_with(&[("w1:p1", "codex", "nothing")]), None);
assert_eq!(
trace_with(&[("w1:p1", "codex", &ran), ("w1:p2", "codex", &ran)]),
None
);
}
#[test]
fn a_poisoned_herdr_session_record_does_not_move_the_session() {
let ran = format!("• Ran brgr --as {SESSION} run \"x\"");
let (temp, binary) = fake_herdr(&[
("w6G:p1", "codex", "other work"),
("w5G:p30", "codex", &ran),
]);
fs::write(
temp.path().join("list.json"),
format!(
r#"{{"result":{{"agents":[{{"pane_id":"w6G:p1","agent":"codex","agent_session":{{"value":"{SESSION}"}}}},{{"pane_id":"w5G:p30","agent":"codex"}}]}}}}"#
),
)
.unwrap();
let found = with_herdr(&binary, || for_session(SESSION, None));
assert_eq!(found.as_deref(), Some("w5G:p30"));
let named = with_herdr(&binary, || for_session(SESSION, Some("w6G:p1")));
assert_eq!(named, None);
}
#[test]
fn a_pane_that_is_not_codex_never_counts() {
let ran = format!("• Ran brgr --as {SESSION} run \"x\"");
let found = trace_with(&[("w1:p1", "claude", &ran), ("w1:p2", "codex", &ran)]);
assert_eq!(found.as_deref(), Some("w1:p2"));
}
#[test]
fn an_unrelated_pane_claim_is_not_trusted() {
assert!(!runs_inside("w999:p999-not-a-real-pane"));
}
#[test]
fn the_process_table_includes_this_process() {
let table = process_table().unwrap();
assert!(table.contains_key(&std::process::id()));
}
#[cfg(target_os = "linux")]
fn waiting_child(pane: Option<&str>) -> std::process::Child {
let mut command = Command::new("/bin/sleep");
command.arg("600").env_clear().env("MARK", "1");
if let Some(pane) = pane {
command.env("HERDR_PANE_ID", pane);
}
let child = command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
let environ = format!("/proc/{}/environ", child.id());
let started = std::time::Instant::now();
while !fs::read(&environ).is_ok_and(|bytes| !bytes.is_empty()) {
assert!(
started.elapsed() < std::time::Duration::from_mins(1),
"the child's environment never appeared"
);
std::thread::sleep(std::time::Duration::from_millis(1));
}
child
}
#[cfg(target_os = "linux")]
#[test]
fn pane_environment_is_read_from_another_process() {
let mut with_pane = waiting_child(Some("w-test:p42"));
let with_value = pane_in_environment(with_pane.id());
with_pane.kill().unwrap();
with_pane.wait().unwrap();
assert_eq!(with_value.as_deref(), Some("w-test:p42"));
let mut without_pane = waiting_child(None);
let without_value = pane_in_environment(without_pane.id());
without_pane.kill().unwrap();
without_pane.wait().unwrap();
assert_eq!(without_value, None);
}
}