@@ -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