Skip to content

Commit 5c33289

Browse files
tombelieberclaude
andcommitted
feat(teams): add TeamMemberSidechain type + resolve_team_sidechains resolver
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 2e4e43e commit 5c33289

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

crates/server/src/teams.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,103 @@ pub fn resolve_team_member_sessions(
10641064
members
10651065
}
10661066

1067+
// ============================================================================
1068+
// Team Member Sidechains (subagent JSONL files per member)
1069+
// ============================================================================
1070+
1071+
/// A single sidechain instance for a team member.
1072+
#[derive(Debug, Clone, Serialize, TS)]
1073+
#[cfg_attr(feature = "codegen", ts(export))]
1074+
#[serde(rename_all = "camelCase")]
1075+
pub struct TeamMemberSidechain {
1076+
/// Hex agent ID — used with `/api/sessions/{sid}/subagents/{hex}/messages`.
1077+
pub hex_id: String,
1078+
/// Agent name from meta.json (e.g., "js-advocate").
1079+
pub member_name: String,
1080+
/// Number of JSONL lines (proxy for amount of work done).
1081+
#[ts(type = "number")]
1082+
pub line_count: u32,
1083+
/// File size in bytes.
1084+
#[ts(type = "number")]
1085+
pub file_size_bytes: u64,
1086+
}
1087+
1088+
/// Scan `{session_dir}/subagents/*.meta.json` and return sidechain info for each.
1089+
///
1090+
/// Each meta.json has `{"agentType":"js-advocate"}` and filename `agent-{hexId}.meta.json`.
1091+
/// The corresponding `.jsonl` file is read for line count and file size.
1092+
/// Results are sorted by `member_name` ascending, then `line_count` descending.
1093+
pub fn resolve_team_sidechains(session_dir: &Path) -> Vec<TeamMemberSidechain> {
1094+
use std::io::{BufRead, BufReader};
1095+
1096+
#[derive(Deserialize)]
1097+
struct Meta {
1098+
#[serde(rename = "agentType")]
1099+
agent_type: String,
1100+
}
1101+
1102+
let subagents_dir = session_dir.join("subagents");
1103+
let entries = match std::fs::read_dir(&subagents_dir) {
1104+
Ok(e) => e,
1105+
Err(_) => return Vec::new(),
1106+
};
1107+
1108+
let mut sidechains = Vec::new();
1109+
1110+
for entry in entries.flatten() {
1111+
let file_name = entry.file_name();
1112+
let name = file_name.to_string_lossy();
1113+
1114+
// Match pattern: agent-{hexId}.meta.json
1115+
let hex_id = match name
1116+
.strip_prefix("agent-")
1117+
.and_then(|s| s.strip_suffix(".meta.json"))
1118+
{
1119+
Some(id) => id.to_string(),
1120+
None => continue,
1121+
};
1122+
1123+
// Parse meta.json for member name
1124+
let meta_path = entry.path();
1125+
let meta_bytes = match std::fs::read(&meta_path) {
1126+
Ok(b) => b,
1127+
Err(_) => continue,
1128+
};
1129+
let meta: Meta = match serde_json::from_slice(&meta_bytes) {
1130+
Ok(m) => m,
1131+
Err(_) => continue,
1132+
};
1133+
1134+
// Read corresponding .jsonl file for line count and size
1135+
let jsonl_path = subagents_dir.join(format!("agent-{hex_id}.jsonl"));
1136+
let (line_count, file_size_bytes) = match std::fs::File::open(&jsonl_path) {
1137+
Ok(file) => {
1138+
let size = file.metadata().map(|m| m.len()).unwrap_or(0);
1139+
let reader = BufReader::new(file);
1140+
let count = reader.lines().count() as u32;
1141+
(count, size)
1142+
}
1143+
Err(_) => (0, 0),
1144+
};
1145+
1146+
sidechains.push(TeamMemberSidechain {
1147+
hex_id,
1148+
member_name: meta.agent_type,
1149+
line_count,
1150+
file_size_bytes,
1151+
});
1152+
}
1153+
1154+
// Sort by member_name asc, then line_count desc
1155+
sidechains.sort_by(|a, b| {
1156+
a.member_name
1157+
.cmp(&b.member_name)
1158+
.then(b.line_count.cmp(&a.line_count))
1159+
});
1160+
1161+
sidechains
1162+
}
1163+
10671164
/// Build a `TeamCostBreakdown` by resolving member sessions and computing costs.
10681165
///
10691166
/// `resolve_session_path` maps session_id → JSONL file path, allowing the caller

0 commit comments

Comments
 (0)