Skip to main content

agentmux_srv/backend/storage/agents/
row.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! The `db_agents` column lists and row mappers the definition and instance
5//! reads share.
6
7use super::*;
8
9/// The `db_agents` columns every instance read selects, in the order
10/// [`map_instance_row`] expects.
11pub(super) const INSTANCE_COLUMNS: &str = "id, last_block_id, session_id, status, github_context, started_at, ended_at, \
12     created_at, identity_id, memory_id, instance_name, working_directory, user_hidden";
13
14/// Project a non-template `db_agents` row (selected with
15/// [`INSTANCE_COLUMNS`]) into the `AgentInstance` shape: the row's id is
16/// both `id` and `definition_id`, `last_block_id` is `block_id`, chains are
17/// pre-collapsed so `parent_instance_id` is empty, and a row that never
18/// recorded a launch reports `created_at` as `started_at`.
19pub(super) fn map_instance_row(row: &rusqlite::Row) -> rusqlite::Result<AgentInstance> {
20    let id: String = row.get(0)?;
21    let started_at: i64 = row.get(5)?;
22    let created_at: i64 = row.get(7)?;
23    Ok(AgentInstance {
24        definition_id: id.clone(),
25        id,
26        parent_instance_id: String::new(),
27        block_id: row.get(1)?,
28        session_id: row.get(2)?,
29        status: row.get(3)?,
30        github_context: row.get(4)?,
31        started_at: if started_at == 0 { created_at } else { started_at },
32        ended_at: row.get(6)?,
33        created_at,
34        identity_id: row.get(8)?,
35        memory_id: row.get(9)?,
36        instance_name: row.get(10)?,
37        working_directory: row.get(11)?,
38        display_hidden: row.get::<_, i64>(12)? != 0,
39    })
40}
41
42/// The `db_agents` SELECT that reads a row in the `AgentDefinition` shape —
43/// the column order [`map_agent_definition_row`] expects. Shared by
44/// `agent_def_list` (all rows) and `agent_row_get` (one row).
45pub(super) const AGENT_DEFINITION_SELECT: &str = "SELECT id, slug, name, icon, provider, description,
46            working_directory, shell, provider_flags, auto_start,
47            restart_on_crash, idle_timeout_minutes, created_at,
48            agent_type, environment, agent_bus_id, is_seeded,
49            accounts, parent_template_id, branch_label, updated_at,
50            user_hidden, container_image, container_volumes, container_name,
51            use_ambient_login, model_vendor_base_url, auto_continue_enabled,
52            default_memory_id, conversation_visibility
53     FROM db_agents";
54
55/// Row mapper for `db_agents` rows projected back into the
56/// `AgentDefinition` shape. The column order MUST match the SELECT in
57/// `agent_def_list`. `parent_template_id` maps to `parent_id` because
58/// the consolidated table renamed the field to clarify its semantics
59/// (template lineage), but the wire shape kept the old name.
60pub(super) fn map_agent_definition_row(row: &rusqlite::Row) -> rusqlite::Result<AgentDefinition> {
61    Ok(AgentDefinition {
62        id: row.get(0)?,
63        slug: row.get(1)?,
64        name: row.get(2)?,
65        icon: row.get(3)?,
66        provider: row.get(4)?,
67        description: row.get(5)?,
68        working_directory: row.get(6)?,
69        shell: row.get(7)?,
70        provider_flags: row.get(8)?,
71        auto_start: row.get(9)?,
72        restart_on_crash: row.get(10)?,
73        idle_timeout_minutes: row.get(11)?,
74        created_at: row.get(12)?,
75        agent_type: row.get(13)?,
76        environment: row.get(14)?,
77        agent_bus_id: row.get(15)?,
78        is_seeded: row.get(16)?,
79        accounts: row.get(17)?,
80        parent_id: row.get(18)?,
81        branch_label: row.get(19)?,
82        updated_at: row.get(20)?,
83        user_hidden: row.get(21)?,
84        container_image: row.get(22)?,
85        container_volumes: row.get(23)?,
86        container_name: row.get(24)?,
87        use_ambient_login: row.get(25)?,
88        model_vendor_base_url: row.get(26)?,
89        auto_continue_enabled: row.get(27)?,
90        memory_id: row.get(28)?,
91        conversation_visibility: row.get(29)?,
92    })
93}