use std::time::Duration;
use super::prefs::AgentPrefs;
use super::tui::app::AgentOp;
use crate::config::Configs;
use crate::telemetry::{self, CliTrackEvent};
fn truncate(message: &str) -> String {
telemetry::truncate_message(message)
}
fn event(
command: &str,
sub_command: String,
duration_ms: u64,
success: bool,
error_message: Option<String>,
) -> CliTrackEvent {
CliTrackEvent {
command: command.to_string(),
sub_command: Some(sub_command),
duration_ms,
success,
error_message,
os: std::env::consts::OS,
arch: std::env::consts::ARCH,
cli_version: env!("CARGO_PKG_VERSION"),
is_ci: Configs::env_is_ci(),
}
}
pub async fn track_launch_outcome(
harness: &'static str,
created: Option<bool>,
duration: Duration,
error: Option<&str>,
) {
let sub_command = match (error, created) {
(Some(_), _) => format!("{harness}_failed"),
(None, Some(true)) => format!("{harness}_created"),
(None, _) => format!("{harness}_reused"),
};
telemetry::send(event(
"cloud_agent_launch",
sub_command,
duration.as_millis() as u64,
error.is_none(),
error.map(truncate),
))
.await;
}
pub async fn track_agent_op(op: AgentOp, error: Option<&str>) {
let kind = match op {
AgentOp::Sleep => "sleep",
AgentOp::Wake => "wake",
AgentOp::Delete => "delete",
};
telemetry::send(event(
"cloud_agent",
format!("agent_{kind}"),
0,
error.is_none(),
error.map(truncate),
))
.await;
}
pub async fn track_lifecycle(kind: &str, duration: Duration, error: Option<&str>) {
telemetry::send(event(
"cloud_agent",
lifecycle_sub_command(kind, error.is_some()),
duration.as_millis() as u64,
error.is_none(),
error.map(truncate),
))
.await;
}
fn lifecycle_sub_command(kind: &str, failed: bool) -> String {
match failed {
true => format!("cli_{kind}_failed"),
false => format!("cli_{kind}"),
}
}
pub async fn track_session_event(kind: &str, error: Option<&str>) {
telemetry::send(event(
"cloud_agent",
kind.to_string(),
0,
error.is_none(),
error.map(truncate),
))
.await;
}
pub async fn track_setup_saved(entry: &str, prefs: &AgentPrefs) {
let harness = prefs.agent.as_deref().unwrap_or("none");
telemetry::send(event(
"cloud_agent",
format!("setup_{entry}_agent_{harness}"),
0,
true,
None,
))
.await;
let theme = prefs.theme.as_deref().unwrap_or("default");
telemetry::send(event(
"cloud_agent",
format!("setup_{entry}_theme_{theme}"),
0,
true,
None,
))
.await;
let project_state = if prefs.default_project.is_some() {
"set"
} else {
"unset"
};
telemetry::send(event(
"cloud_agent",
format!("setup_{entry}_project_{project_state}"),
0,
true,
None,
))
.await;
let skills_state = if !prefs.skills.enabled {
"off"
} else {
prefs.skills.source.as_deref().unwrap_or("unknown")
};
telemetry::send(event(
"cloud_agent",
format!("setup_{entry}_skills_{skills_state}"),
0,
true,
None,
))
.await;
}
pub async fn track_setup_failed(entry: &str, error: &str) {
telemetry::send(event(
"cloud_agent",
format!("setup_{entry}_failed"),
0,
false,
Some(truncate(error)),
))
.await;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lifecycle_slugs_are_prefixed_and_marked() {
assert_eq!(lifecycle_sub_command("create", false), "cli_create");
assert_eq!(lifecycle_sub_command("create", true), "cli_create_failed");
}
#[test]
fn lifecycle_slugs_do_not_collide_with_the_tui_ops() {
for kind in ["sleep", "wake", "delete"] {
assert_ne!(lifecycle_sub_command(kind, false), format!("agent_{kind}"));
}
}
#[test]
fn truncate_caps_long_messages() {
let long = "x".repeat(300);
assert_eq!(truncate(&long).len(), 256);
}
#[test]
fn truncate_leaves_short_messages_alone() {
assert_eq!(truncate("boom"), "boom");
}
}