feat: phase 2 — coûts par session (stats --costs) + am monitor TUI (closes #49 #50)

This commit is contained in:
2026-08-18 11:23:50 -04:00
parent 63dc36660b
commit 1b3fe09074
22 changed files with 978 additions and 23 deletions
+17
View File
@@ -29,6 +29,14 @@ pub struct SessionRecord {
/// RFC 3339 end timestamp (None while running).
#[serde(default)]
pub ended_at: Option<String>,
/// Tokens billed once the agent exposed its usage (issue #49).
#[serde(default)]
pub tokens_in: Option<u64>,
#[serde(default)]
pub tokens_out: Option<u64>,
/// Estimated cost in USD (issue #49).
#[serde(default)]
pub cost_usd: Option<f64>,
#[serde(default)]
pub exit_code: Option<i32>,
#[serde(default)]
@@ -125,6 +133,9 @@ pub fn start_agent(
project,
started_at: crate::installers::now_rfc3339(),
ended_at: None,
tokens_in: None,
tokens_out: None,
cost_usd: None,
exit_code: None,
log,
args,
@@ -151,6 +162,9 @@ pub fn start_repl(app: &App, id: &str, cwd: Option<String>) -> Result<()> {
project: None,
started_at: crate::installers::now_rfc3339(),
ended_at: None,
tokens_in: None,
tokens_out: None,
cost_usd: None,
exit_code: None,
log: None,
args: Vec::new(),
@@ -184,12 +198,15 @@ pub fn finish_agent(app: &App, agent: &str, pid: Option<u32>, exit_code: i32, ok
candidates.first().cloned()
};
if let Some(k) = key {
let cwd = sf.sessions.get(&k).and_then(|r| r.cwd.clone());
if let Some(r) = sf.sessions.get_mut(&k) {
r.ended_at = Some(crate::installers::now_rfc3339());
r.exit_code = Some(exit_code);
r.status = if ok { "stopped" } else { "failed" }.to_string();
}
save(app, &sf)?;
// Issue #49: collect the token usage the agent exposed and price it.
let _ = crate::costs::record_session_usage(app, agent, &k, cwd.as_deref());
}
Ok(())
}