//! The Team page's new tabs (R4): the org chart (members → the claws they //! manage) and the leaderboard (agents ranked by real usage_events). use std::sync::Arc; use cm_api::AppState; use cm_auth::AuthService; use cm_domain::{ AccessPolicy, Agent, AgentId, AgentStatus, Role, User, UserId, Workspace, WorkspaceId, }; use cm_llm::ScriptedProvider; use cm_runtime::{Runtime, RuntimeConfig}; use serde_json::{json, Value}; async fn serve(pool: sqlx::PgPool) -> (String, reqwest::Client) { let runtime = Runtime::new( pool.clone(), Arc::new(ScriptedProvider::from_toml("").unwrap()), RuntimeConfig::basic("scripted", 1024), ); let app = cm_api::router(AppState::new(pool, runtime)); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { axum::serve(listener, app).await.unwrap(); }); (format!("http://{addr}"), reqwest::Client::new()) } async fn owner(pool: &sqlx::PgPool, ws: WorkspaceId, name: &str) -> User { let user = User { id: UserId::new(), workspace_id: ws, email: format!("{}@acme.test", UserId::new()), role: Role::Owner, display_name: name.into(), created_at: time::OffsetDateTime::UNIX_EPOCH, }; cm_db::repo::users::insert(pool, &user).await.unwrap(); user } async fn claw(pool: &sqlx::PgPool, ws: WorkspaceId, manager: UserId, name: &str) -> AgentId { let agent = Agent { id: AgentId::new(), workspace_id: ws, name: name.into(), job_title: "Analyst".into(), system_prompt: String::new(), avatar: String::new(), accent: "#f96565".into(), wallpaper: String::new(), managed_by: manager, status: AgentStatus::Online, }; cm_db::repo::agents::insert(pool, &agent, &AccessPolicy::default()) .await .unwrap(); agent.id } async fn login(base: &str, client: &reqwest::Client, email: &str) -> String { client .post(format!("{base}/api/auth/login")) .json(&json!({"email": email, "password": "pw"})) .send() .await .unwrap() .json::() .await .unwrap()["token"] .as_str() .unwrap() .to_owned() } #[tokio::test] async fn orgchart_groups_each_member_with_the_claws_they_manage() { let pool = cm_testkit::test_pool().await; let (base, client) = serve(pool.clone()).await; let ws = Workspace { id: WorkspaceId::new(), name: "Acme".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(&pool, &ws).await.unwrap(); let ada = owner(&pool, ws.id, "Ada").await; let ben = owner(&pool, ws.id, "Ben").await; AuthService::new(pool.clone()) .set_password(ada.id, "pw") .await .unwrap(); let scout = claw(&pool, ws.id, ada.id, "Scout").await; let quill = claw(&pool, ws.id, ada.id, "Quill").await; let _solo = claw(&pool, ws.id, ben.id, "Atlas").await; let token = login(&base, &client, &ada.email).await; let chart: Value = client .get(format!("{base}/api/team/orgchart")) .bearer_auth(&token) .send() .await .unwrap() .json() .await .unwrap(); let nodes = chart.as_array().unwrap(); assert_eq!(nodes.len(), 2, "two managers"); let ada_node = nodes .iter() .find(|n| n["user"]["display_name"] == "Ada") .unwrap(); let managed: Vec<&str> = ada_node["claws"] .as_array() .unwrap() .iter() .map(|c| c["name"].as_str().unwrap()) .collect(); assert!(managed.contains(&"Scout") && managed.contains(&"Quill")); assert_eq!(managed.len(), 2); // The claw ids are real (usable to deep-link into a chat). assert!(ada_node["claws"][0]["id"].as_str().is_some()); let _ = (scout, quill); } #[tokio::test] async fn leaderboard_ranks_claws_by_real_usage() { let pool = cm_testkit::test_pool().await; let (base, client) = serve(pool.clone()).await; let ws = Workspace { id: WorkspaceId::new(), name: "Acme".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(&pool, &ws).await.unwrap(); let ada = owner(&pool, ws.id, "Ada").await; AuthService::new(pool.clone()) .set_password(ada.id, "pw") .await .unwrap(); let busy = claw(&pool, ws.id, ada.id, "Busy").await; let idle = claw(&pool, ws.id, ada.id, "Idle").await; // Two real usage events for Busy, none for Idle. for (tin, tout, credits) in [(1000i64, 500i64, 2i64), (3000, 1000, 4)] { sqlx::query( "INSERT INTO usage_events (workspace_id, agent_id, kind, tokens_in, tokens_out, credits) VALUES ($1, $2, 'llm_tokens', $3, $4, $5)", ) .bind(ws.id.as_uuid()) .bind(busy.as_uuid()) .bind(tin) .bind(tout) .bind(sqlx::types::BigDecimal::from(credits)) .execute(&pool) .await .unwrap(); } let token = login(&base, &client, &ada.email).await; let board: Value = client .get(format!("{base}/api/team/leaderboard")) .bearer_auth(&token) .send() .await .unwrap() .json() .await .unwrap(); let rows = board.as_array().unwrap(); assert_eq!(rows.len(), 2); // Busy ranks first with the summed usage; Idle is present at zero. assert_eq!(rows[0]["name"], "Busy"); assert_eq!(rows[0]["credits"], 6); assert_eq!(rows[0]["tokens"], 5500); assert_eq!(rows[0]["runs"], 2); assert_eq!(rows[1]["name"], "Idle"); assert_eq!(rows[1]["credits"], 0); assert_eq!(rows[1]["runs"], 0); let _ = idle; }