use anyhow::{Context, Result};
use chrono::{Duration as ChronoDuration, Utc};
use rusqlite::{params, Connection, OptionalExtension};
use crate::usage::SAVE_TOOLS;
const PALACE_TOOL_PREFIX: &str = "mcp__palace__";
const CURSOR_MCP_PREFIX: &str = "MCP:";
const RETENTION_DAYS: i64 = 7;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SessionActivity {
pub prompts: i64,
pub tool_calls: i64,
pub palace_used: bool,
pub saved: bool,
}
pub fn palace_tool(tool_name: &str) -> Option<&str> {
tool_name.strip_prefix(PALACE_TOOL_PREFIX).or_else(|| {
tool_name
.strip_prefix(CURSOR_MCP_PREFIX)
.filter(|tool| tool.starts_with("palace_"))
})
}
pub fn record_prompt(conn: &Connection, session_id: &str, cwd: &str) -> Result<()> {
upsert(conn, session_id, cwd, 1, 0, false, false)?;
let cutoff = (Utc::now() - ChronoDuration::days(RETENTION_DAYS)).to_rfc3339();
conn.execute(
"DELETE FROM hook_sessions WHERE updated_at < ?1",
params![cutoff],
)
.context("pruning idle hook sessions")?;
Ok(())
}
pub fn record_tool(conn: &Connection, session_id: &str, cwd: &str, tool_name: &str) -> Result<()> {
let palace = palace_tool(tool_name);
let saved = palace.is_some_and(|tool| SAVE_TOOLS.contains(&tool));
upsert(conn, session_id, cwd, 0, 1, palace.is_some(), saved)
}
pub fn activity(conn: &Connection, session_id: &str) -> Result<Option<SessionActivity>> {
conn.query_row(
"SELECT prompts, tool_calls, palace_used, saved FROM hook_sessions WHERE session_id = ?1",
params![session_id],
|row| {
Ok(SessionActivity {
prompts: row.get(0)?,
tool_calls: row.get(1)?,
palace_used: row.get(2)?,
saved: row.get(3)?,
})
},
)
.optional()
.context("reading hook session activity")
}
fn upsert(
conn: &Connection,
session_id: &str,
cwd: &str,
prompts: i64,
tool_calls: i64,
palace_used: bool,
saved: bool,
) -> Result<()> {
conn.execute(
"INSERT INTO hook_sessions
(session_id, cwd, prompts, tool_calls, palace_used, saved, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
ON CONFLICT(session_id) DO UPDATE SET
cwd = excluded.cwd,
prompts = prompts + excluded.prompts,
tool_calls = tool_calls + excluded.tool_calls,
palace_used = MAX(palace_used, excluded.palace_used),
saved = MAX(saved, excluded.saved),
updated_at = excluded.updated_at",
params![
session_id,
cwd,
prompts,
tool_calls,
palace_used,
saved,
Utc::now().to_rfc3339()
],
)
.context("recording hook session activity")?;
Ok(())
}