feat: add ecc2 coordination status json output

This commit is contained in:
Affaan Mustafa
2026-04-08 13:15:21 -07:00
parent cd94878374
commit 53d8cee6f8
3 changed files with 60 additions and 5 deletions

View File

@@ -103,7 +103,11 @@ enum Commands {
lead_limit: usize, lead_limit: usize,
}, },
/// Show global coordination, backlog, and daemon policy status /// Show global coordination, backlog, and daemon policy status
CoordinationStatus, CoordinationStatus {
/// Emit machine-readable JSON instead of the human summary
#[arg(long)]
json: bool,
},
/// Rebalance unread handoffs across lead teams with backed-up delegates /// Rebalance unread handoffs across lead teams with backed-up delegates
RebalanceAll { RebalanceAll {
/// Agent type for routed delegates /// Agent type for routed delegates
@@ -460,9 +464,9 @@ async fn main() -> Result<()> {
); );
} }
} }
Some(Commands::CoordinationStatus) => { Some(Commands::CoordinationStatus { json }) => {
let status = session::manager::get_coordination_status(&db, &cfg)?; let status = session::manager::get_coordination_status(&db, &cfg)?;
println!("{status}"); println!("{}", format_coordination_status(&status, json)?);
} }
Some(Commands::RebalanceAll { Some(Commands::RebalanceAll {
agent, agent,
@@ -670,6 +674,17 @@ fn short_session(session_id: &str) -> String {
session_id.chars().take(8).collect() session_id.chars().take(8).collect()
} }
fn format_coordination_status(
status: &session::manager::CoordinationStatus,
json: bool,
) -> Result<String> {
if json {
return Ok(serde_json::to_string_pretty(status)?);
}
Ok(status.to_string())
}
fn send_handoff_message( fn send_handoff_message(
db: &session::store::StateStore, db: &session::store::StateStore,
from_id: &str, from_id: &str,
@@ -965,11 +980,48 @@ mod tests {
.expect("coordination-status should parse"); .expect("coordination-status should parse");
match cli.command { match cli.command {
Some(Commands::CoordinationStatus) => {} Some(Commands::CoordinationStatus { json }) => assert!(!json),
_ => panic!("expected coordination-status subcommand"), _ => panic!("expected coordination-status subcommand"),
} }
} }
#[test]
fn cli_parses_coordination_status_json_flag() {
let cli = Cli::try_parse_from(["ecc", "coordination-status", "--json"])
.expect("coordination-status --json should parse");
match cli.command {
Some(Commands::CoordinationStatus { json }) => assert!(json),
_ => panic!("expected coordination-status subcommand"),
}
}
#[test]
fn format_coordination_status_emits_json() {
let status = session::manager::CoordinationStatus {
backlog_leads: 2,
backlog_messages: 5,
absorbable_sessions: 1,
saturated_sessions: 1,
auto_dispatch_enabled: true,
auto_dispatch_limit_per_session: 4,
daemon_activity: session::store::DaemonActivity {
last_dispatch_routed: 3,
last_dispatch_deferred: 1,
last_dispatch_leads: 2,
..Default::default()
},
};
let rendered =
format_coordination_status(&status, true).expect("json formatting should succeed");
let value: serde_json::Value =
serde_json::from_str(&rendered).expect("valid json should be emitted");
assert_eq!(value["backlog_leads"], 2);
assert_eq!(value["backlog_messages"], 5);
assert_eq!(value["daemon_activity"]["last_dispatch_routed"], 3);
}
#[test] #[test]
fn cli_parses_rebalance_team_command() { fn cli_parses_rebalance_team_command() {
let cli = Cli::try_parse_from([ let cli = Cli::try_parse_from([

View File

@@ -1,4 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::Serialize;
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::fmt; use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -1094,6 +1095,7 @@ pub struct CoordinateBacklogOutcome {
pub remaining_saturated_sessions: usize, pub remaining_saturated_sessions: usize,
} }
#[derive(Debug, Clone, Serialize)]
pub struct CoordinationStatus { pub struct CoordinationStatus {
pub backlog_leads: usize, pub backlog_leads: usize,
pub backlog_messages: usize, pub backlog_messages: usize,

View File

@@ -1,5 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use rusqlite::{Connection, OptionalExtension}; use rusqlite::{Connection, OptionalExtension};
use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
@@ -13,7 +14,7 @@ pub struct StateStore {
conn: Connection, conn: Connection,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, Serialize)]
pub struct DaemonActivity { pub struct DaemonActivity {
pub last_dispatch_at: Option<chrono::DateTime<chrono::Utc>>, pub last_dispatch_at: Option<chrono::DateTime<chrono::Utc>>,
pub last_dispatch_routed: usize, pub last_dispatch_routed: usize,