Skip to main content

aft/db/
bash_tasks.rs

1use rusqlite::{params, Connection, OptionalExtension, Row};
2
3#[derive(Debug, Clone)]
4pub struct BashTaskRow {
5    pub harness: String,
6    pub session_id: String,
7    pub task_id: String,
8    pub project_key: String,
9    pub command: String,
10    pub cwd: String,
11    pub status: String,
12    pub exit_code: Option<i32>,
13    pub pid: Option<i64>,
14    pub pgid: Option<i64>,
15    pub started_at: i64,
16    pub completed_at: Option<i64>,
17    pub stdout_path: Option<String>,
18    pub stderr_path: Option<String>,
19    pub compressed: bool,
20    pub timeout_ms: Option<i64>,
21    pub completion_delivered: bool,
22    pub output_bytes: Option<i64>,
23    pub metadata: String,
24}
25
26pub fn upsert_bash_task(conn: &Connection, row: &BashTaskRow) -> rusqlite::Result<()> {
27    conn.execute(
28        "INSERT INTO bash_tasks (
29            harness, session_id, task_id, project_key, command, cwd, status,
30            exit_code, pid, pgid, started_at, completed_at, stdout_path, stderr_path,
31            compressed, timeout_ms, completion_delivered, output_bytes, metadata
32         ) VALUES (
33            ?1, ?2, ?3, ?4, ?5, ?6, ?7,
34            ?8, ?9, ?10, ?11, ?12, ?13, ?14,
35            ?15, ?16, ?17, ?18, ?19
36         )
37         ON CONFLICT(harness, session_id, task_id) DO UPDATE SET
38            project_key = excluded.project_key,
39            command = excluded.command,
40            cwd = excluded.cwd,
41            status = excluded.status,
42            exit_code = excluded.exit_code,
43            pid = excluded.pid,
44            pgid = excluded.pgid,
45            started_at = excluded.started_at,
46            completed_at = excluded.completed_at,
47            stdout_path = excluded.stdout_path,
48            stderr_path = excluded.stderr_path,
49            compressed = excluded.compressed,
50            timeout_ms = excluded.timeout_ms,
51            completion_delivered = excluded.completion_delivered,
52            output_bytes = excluded.output_bytes,
53            metadata = excluded.metadata",
54        params![
55            row.harness,
56            row.session_id,
57            row.task_id,
58            row.project_key,
59            row.command,
60            row.cwd,
61            row.status,
62            row.exit_code,
63            row.pid,
64            row.pgid,
65            row.started_at,
66            row.completed_at,
67            row.stdout_path,
68            row.stderr_path,
69            row.compressed,
70            row.timeout_ms,
71            row.completion_delivered,
72            row.output_bytes,
73            row.metadata,
74        ],
75    )?;
76    Ok(())
77}
78
79pub fn delete_delivered_terminal_bash_task(
80    conn: &Connection,
81    harness: &str,
82    session_id: &str,
83    task_id: &str,
84) -> rusqlite::Result<usize> {
85    conn.execute(
86        "DELETE FROM bash_tasks
87         WHERE harness = ?1 AND session_id = ?2 AND task_id = ?3
88           AND completion_delivered = 1
89           AND status IN ('completed', 'failed', 'killed', 'timed_out')",
90        params![harness, session_id, task_id],
91    )
92}
93
94pub fn get_bash_task(
95    conn: &Connection,
96    harness: &str,
97    session_id: &str,
98    task_id: &str,
99) -> rusqlite::Result<Option<BashTaskRow>> {
100    conn.query_row(
101        "SELECT harness, session_id, task_id, project_key, command, cwd, status,
102                exit_code, pid, pgid, started_at, completed_at, stdout_path, stderr_path,
103                compressed, timeout_ms, completion_delivered, output_bytes, metadata
104         FROM bash_tasks
105         WHERE harness = ?1 AND session_id = ?2 AND task_id = ?3",
106        params![harness, session_id, task_id],
107        map_bash_task_row,
108    )
109    .optional()
110}
111
112pub fn list_bash_tasks_for_session(
113    conn: &Connection,
114    harness: &str,
115    session_id: &str,
116) -> rusqlite::Result<Vec<BashTaskRow>> {
117    let mut stmt = conn.prepare(
118        "SELECT harness, session_id, task_id, project_key, command, cwd, status,
119                exit_code, pid, pgid, started_at, completed_at, stdout_path, stderr_path,
120                compressed, timeout_ms, completion_delivered, output_bytes, metadata
121         FROM bash_tasks
122         WHERE harness = ?1 AND session_id = ?2
123         ORDER BY started_at ASC, task_id ASC",
124    )?;
125
126    let rows = stmt
127        .query_map(params![harness, session_id], map_bash_task_row)?
128        .collect();
129    rows
130}
131
132pub fn find_bash_task_for_project(
133    conn: &Connection,
134    harness: &str,
135    project_key: &str,
136    task_id: &str,
137) -> rusqlite::Result<Option<BashTaskRow>> {
138    conn.query_row(
139        "SELECT harness, session_id, task_id, project_key, command, cwd, status,
140                exit_code, pid, pgid, started_at, completed_at, stdout_path, stderr_path,
141                compressed, timeout_ms, completion_delivered, output_bytes, metadata
142         FROM bash_tasks
143         WHERE harness = ?1 AND project_key = ?2 AND task_id = ?3
144         ORDER BY started_at DESC
145         LIMIT 1",
146        params![harness, project_key, task_id],
147        map_bash_task_row,
148    )
149    .optional()
150}
151
152fn map_bash_task_row(row: &Row<'_>) -> rusqlite::Result<BashTaskRow> {
153    Ok(BashTaskRow {
154        harness: row.get(0)?,
155        session_id: row.get(1)?,
156        task_id: row.get(2)?,
157        project_key: row.get(3)?,
158        command: row.get(4)?,
159        cwd: row.get(5)?,
160        status: row.get(6)?,
161        exit_code: row.get(7)?,
162        pid: row.get(8)?,
163        pgid: row.get(9)?,
164        started_at: row.get(10)?,
165        completed_at: row.get(11)?,
166        stdout_path: row.get(12)?,
167        stderr_path: row.get(13)?,
168        compressed: row.get::<_, i64>(14)? != 0,
169        timeout_ms: row.get(15)?,
170        completion_delivered: row.get::<_, i64>(16)? != 0,
171        output_bytes: row.get(17)?,
172        metadata: row.get::<_, Option<String>>(18)?.unwrap_or_default(),
173    })
174}