Skip to content

Commit bf7d505

Browse files
committed
Improve desktop runtime visibility and session parsing
1 parent 9e8cfa5 commit bf7d505

21 files changed

Lines changed: 2511 additions & 525 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Desktop lifecycle cleanup progress/status snapshot
2+
CREATE TABLE IF NOT EXISTS lifecycle_cleanup_jobs (
3+
id INTEGER PRIMARY KEY AUTOINCREMENT,
4+
status TEXT NOT NULL,
5+
deleted_sessions INTEGER NOT NULL DEFAULT 0,
6+
deleted_summaries INTEGER NOT NULL DEFAULT 0,
7+
message TEXT,
8+
started_at TEXT,
9+
finished_at TEXT,
10+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
11+
);
12+
13+
CREATE INDEX IF NOT EXISTS idx_lifecycle_cleanup_jobs_status
14+
ON lifecycle_cleanup_jobs(status, updated_at DESC);

crates/api/src/db/migrations.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub const LOCAL_MIGRATIONS: &[Migration] = &[
3131
"local_0004_summary_batch_status",
3232
include_str!("../../migrations/local_0004_summary_batch_status.sql"),
3333
),
34+
(
35+
"local_0005_lifecycle_cleanup_status",
36+
include_str!("../../migrations/local_0005_lifecycle_cleanup_status.sql"),
37+
),
3438
];
3539

3640
#[cfg(test)]
@@ -41,11 +45,12 @@ mod tests {
4145
fn schema_migration_set_is_minimal() {
4246
assert_eq!(MIGRATIONS.len(), 1);
4347
assert_eq!(MIGRATIONS[0].0, "0001_schema");
44-
assert_eq!(LOCAL_MIGRATIONS.len(), 4);
48+
assert_eq!(LOCAL_MIGRATIONS.len(), 5);
4549
assert_eq!(LOCAL_MIGRATIONS[0].0, "local_0001_schema");
4650
assert_eq!(LOCAL_MIGRATIONS[1].0, "local_0002_session_summaries");
4751
assert_eq!(LOCAL_MIGRATIONS[2].0, "local_0003_vector_index");
4852
assert_eq!(LOCAL_MIGRATIONS[3].0, "local_0004_summary_batch_status");
53+
assert_eq!(LOCAL_MIGRATIONS[4].0, "local_0005_lifecycle_cleanup_status");
4954
}
5055

5156
#[test]

crates/api/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,32 @@ pub struct DesktopRuntimeLifecycleSettingsUpdate {
876876
pub cleanup_interval_secs: u64,
877877
}
878878

879+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
880+
#[serde(rename_all = "snake_case")]
881+
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
882+
#[cfg_attr(feature = "ts", ts(export))]
883+
pub enum DesktopLifecycleCleanupState {
884+
Idle,
885+
Running,
886+
Complete,
887+
Failed,
888+
}
889+
890+
#[derive(Debug, Clone, Serialize, Deserialize)]
891+
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
892+
#[cfg_attr(feature = "ts", ts(export))]
893+
pub struct DesktopLifecycleCleanupStatusResponse {
894+
pub state: DesktopLifecycleCleanupState,
895+
pub deleted_sessions: u32,
896+
pub deleted_summaries: u32,
897+
#[serde(default, skip_serializing_if = "Option::is_none")]
898+
pub message: Option<String>,
899+
#[serde(default, skip_serializing_if = "Option::is_none")]
900+
pub started_at: Option<String>,
901+
#[serde(default, skip_serializing_if = "Option::is_none")]
902+
pub finished_at: Option<String>,
903+
}
904+
879905
#[derive(Debug, Clone, Serialize, Deserialize)]
880906
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
881907
#[cfg_attr(feature = "ts", ts(export))]
@@ -1841,6 +1867,8 @@ mod tests {
18411867
DesktopRuntimeChangeReaderSettingsUpdate,
18421868
DesktopRuntimeLifecycleSettings,
18431869
DesktopRuntimeLifecycleSettingsUpdate,
1870+
DesktopLifecycleCleanupState,
1871+
DesktopLifecycleCleanupStatusResponse,
18441872
DesktopVectorPreflightResponse,
18451873
DesktopVectorInstallStatusResponse,
18461874
DesktopVectorIndexStatusResponse,

crates/local-db/src/lib.rs

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ pub struct SummaryBatchJobRow {
145145
pub finished_at: Option<String>,
146146
}
147147

148+
/// Lifecycle cleanup progress/status snapshot.
149+
#[derive(Debug, Clone, PartialEq, Eq)]
150+
pub struct LifecycleCleanupJobRow {
151+
pub status: String,
152+
pub deleted_sessions: u32,
153+
pub deleted_summaries: u32,
154+
pub message: Option<String>,
155+
pub started_at: Option<String>,
156+
pub finished_at: Option<String>,
157+
}
158+
148159
fn infer_tool_from_source_path(source_path: Option<&str>) -> Option<&'static str> {
149160
let source_path = source_path.map(|path| path.to_ascii_lowercase())?;
150161

@@ -1436,6 +1447,53 @@ impl LocalDb {
14361447
Ok(row)
14371448
}
14381449

1450+
pub fn set_lifecycle_cleanup_job(&self, payload: &LifecycleCleanupJobRow) -> Result<()> {
1451+
self.conn().execute(
1452+
"INSERT INTO lifecycle_cleanup_jobs \
1453+
(id, status, deleted_sessions, deleted_summaries, message, started_at, finished_at, updated_at) \
1454+
VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, datetime('now')) \
1455+
ON CONFLICT(id) DO UPDATE SET \
1456+
status=excluded.status, \
1457+
deleted_sessions=excluded.deleted_sessions, \
1458+
deleted_summaries=excluded.deleted_summaries, \
1459+
message=excluded.message, \
1460+
started_at=excluded.started_at, \
1461+
finished_at=excluded.finished_at, \
1462+
updated_at=datetime('now')",
1463+
params![
1464+
payload.status,
1465+
payload.deleted_sessions as i64,
1466+
payload.deleted_summaries as i64,
1467+
payload.message,
1468+
payload.started_at,
1469+
payload.finished_at,
1470+
],
1471+
)?;
1472+
Ok(())
1473+
}
1474+
1475+
pub fn get_lifecycle_cleanup_job(&self) -> Result<Option<LifecycleCleanupJobRow>> {
1476+
let row = self
1477+
.conn()
1478+
.query_row(
1479+
"SELECT status, deleted_sessions, deleted_summaries, message, started_at, finished_at \
1480+
FROM lifecycle_cleanup_jobs WHERE id = 1 LIMIT 1",
1481+
[],
1482+
|row| {
1483+
Ok(LifecycleCleanupJobRow {
1484+
status: row.get(0)?,
1485+
deleted_sessions: row.get::<_, i64>(1)?.max(0) as u32,
1486+
deleted_summaries: row.get::<_, i64>(2)?.max(0) as u32,
1487+
message: row.get(3)?,
1488+
started_at: row.get(4)?,
1489+
finished_at: row.get(5)?,
1490+
})
1491+
},
1492+
)
1493+
.optional()?;
1494+
Ok(row)
1495+
}
1496+
14391497
// ── Sync cursor ────────────────────────────────────────────────────
14401498

14411499
pub fn get_sync_cursor(&self, team_id: &str) -> Result<Option<String>> {
@@ -2538,10 +2596,14 @@ mod tests {
25382596
migration_names.contains(&"local_0004_summary_batch_status"),
25392597
"expected local_0004_summary_batch_status migration from opensession-api"
25402598
);
2599+
assert!(
2600+
migration_names.contains(&"local_0005_lifecycle_cleanup_status"),
2601+
"expected local_0005_lifecycle_cleanup_status migration from opensession-api"
2602+
);
25412603
assert_eq!(
25422604
migration_names.len(),
2543-
4,
2544-
"local schema should include baseline + summary cache + vector index + summary batch status steps"
2605+
5,
2606+
"local schema should include baseline + summary cache + vector index + summary batch status + lifecycle cleanup status steps"
25452607
);
25462608

25472609
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -3427,6 +3489,30 @@ mod tests {
34273489
assert_eq!(loaded.message.as_deref(), Some("processing summaries"));
34283490
}
34293491

3492+
#[test]
3493+
fn test_lifecycle_cleanup_job_round_trip() {
3494+
let db = test_db();
3495+
let payload = LifecycleCleanupJobRow {
3496+
status: "complete".to_string(),
3497+
deleted_sessions: 3,
3498+
deleted_summaries: 7,
3499+
message: Some("cleanup complete".to_string()),
3500+
started_at: Some("2026-03-06T01:00:00Z".to_string()),
3501+
finished_at: Some("2026-03-06T01:00:04Z".to_string()),
3502+
};
3503+
db.set_lifecycle_cleanup_job(&payload)
3504+
.expect("set lifecycle cleanup job snapshot");
3505+
3506+
let loaded = db
3507+
.get_lifecycle_cleanup_job()
3508+
.expect("read lifecycle cleanup job snapshot")
3509+
.expect("lifecycle cleanup row should exist");
3510+
assert_eq!(loaded.status, "complete");
3511+
assert_eq!(loaded.deleted_sessions, 3);
3512+
assert_eq!(loaded.deleted_summaries, 7);
3513+
assert_eq!(loaded.message.as_deref(), Some("cleanup complete"));
3514+
}
3515+
34303516
#[test]
34313517
fn test_session_count() {
34323518
let db = test_db();

crates/worker/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/src-tauri/Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)