use super::*;
#[must_use]
pub fn sidecar_dir_for_session(session_jsonl: &Path) -> Option<PathBuf> {
let stem = session_jsonl.file_stem()?.to_str()?;
let parent = session_jsonl.parent()?;
let dir = parent.join(stem);
dir.is_dir().then_some(dir)
}
pub fn discover_subagents(session_jsonl: &Path) -> Result<Vec<Subagent>> {
let Some(sidecar) = sidecar_dir_for_session(session_jsonl) else {
return Ok(Vec::new());
};
let parent_session_id = session_jsonl
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_default()
.to_string();
let subagents_dir = sidecar.join("subagents");
if !subagents_dir.is_dir() {
return Ok(Vec::new());
}
let mut out = Vec::new();
for p in agent_jsonls_in(&subagents_dir)? {
out.push(make_subagent(
p,
SubagentKind::BuiltinTask,
&parent_session_id,
None,
));
}
let workflows_dir = subagents_dir.join("workflows");
if workflows_dir.is_dir() {
for wf_dir in subdirs_in(&workflows_dir)? {
let workflow_id = wf_dir
.file_name()
.and_then(|n| n.to_str())
.map(str::to_string);
for p in agent_jsonls_in(&wf_dir)? {
out.push(make_subagent(
p,
SubagentKind::Workflow,
&parent_session_id,
workflow_id.clone(),
));
}
}
}
let already: std::collections::HashSet<PathBuf> = out.iter().map(|s| s.path.clone()).collect();
discover_nested_defensive(
&subagents_dir,
&parent_session_id,
&already,
MAX_NESTED_DEPTH,
&mut out,
)?;
out.sort_by(|a, b| (a.kind.label(), &a.agent_id).cmp(&(b.kind.label(), &b.agent_id)));
Ok(out)
}
pub(crate) const MAX_NESTED_DEPTH: usize = 3;
pub(crate) fn discover_nested_defensive(
dir: &Path,
parent_session_id: &str,
already: &std::collections::HashSet<PathBuf>,
depth: usize,
out: &mut Vec<Subagent>,
) -> Result<()> {
if depth == 0 {
return Ok(());
}
let read = match std::fs::read_dir(dir) {
Ok(r) => r,
Err(_) => return Ok(()), };
for entry in read {
let Ok(entry) = entry else { continue };
let p = entry.path();
let Ok(meta) = std::fs::symlink_metadata(&p) else {
continue;
};
if meta.file_type().is_symlink() || !meta.is_dir() {
continue;
}
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or_default();
if name == "workflows" {
continue;
}
if name == "subagents" {
for jp in agent_jsonls_in(&p)? {
if !already.contains(&jp) {
out.push(make_subagent(
jp,
SubagentKind::BuiltinTask,
parent_session_id,
None,
));
}
}
}
discover_nested_defensive(&p, parent_session_id, already, depth - 1, out)?;
}
Ok(())
}
pub fn subagent_transcript_files(session_jsonl: &Path) -> Result<Vec<PathBuf>> {
Ok(discover_subagents(session_jsonl)?
.into_iter()
.map(|s| s.path)
.collect())
}
pub(crate) fn agent_jsonls_in(dir: &Path) -> Result<Vec<PathBuf>> {
let mut out = Vec::new();
let read = std::fs::read_dir(dir)
.with_context(|| format!("cannot read subagents dir {}", dir.display()))?;
for entry in read {
let entry =
entry.with_context(|| format!("error reading an entry in {}", dir.display()))?;
let p = entry.path();
let is_file = entry.file_type().map(|ft| ft.is_file()).unwrap_or(false);
if !is_file {
continue;
}
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or_default();
if name.starts_with("agent-") && p.extension().is_some_and(|e| e == "jsonl") {
out.push(p);
}
}
out.sort();
Ok(out)
}
pub(crate) fn subdirs_in(dir: &Path) -> Result<Vec<PathBuf>> {
let mut out = Vec::new();
let read = std::fs::read_dir(dir)
.with_context(|| format!("cannot read workflows dir {}", dir.display()))?;
for entry in read {
let entry =
entry.with_context(|| format!("error reading an entry in {}", dir.display()))?;
let p = entry.path();
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
if is_dir {
out.push(p);
}
}
out.sort();
Ok(out)
}