remem-ai 0.6.93

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use rusqlite::{params, Connection};

use super::sessions::{list_sessions, RawSessionQuery};
use super::{insert_raw_message, ROLE_USER, SOURCE_HOOK};

const SHANGHAI_NEW_YEAR: i64 = 1_735_660_800;

fn setup_conn() -> Connection {
    let conn = Connection::open_in_memory().unwrap();
    crate::migrate::run_migrations(&conn).unwrap();
    conn
}

fn insert_at_epoch(conn: &Connection, session_id: &str, project: &str, content: &str, epoch: i64) {
    let outcome = insert_raw_message(
        conn,
        session_id,
        project,
        ROLE_USER,
        content,
        SOURCE_HOOK,
        None,
        None,
    )
    .unwrap()
    .expect("insert must produce a row");
    conn.execute(
        "UPDATE raw_messages SET created_at_epoch = ?1 WHERE id = ?2",
        params![epoch, outcome.id],
    )
    .unwrap();
}

fn identify_raw_sessions(conn: &Connection, host: &str) {
    let tuples = {
        let mut statement = conn
            .prepare("SELECT DISTINCT source_root, project, session_id FROM raw_messages")
            .unwrap();
        statement
            .query_map([], |row| {
                Ok((
                    row.get::<_, String>(0)?,
                    row.get::<_, String>(1)?,
                    row.get::<_, String>(2)?,
                ))
            })
            .unwrap()
            .collect::<rusqlite::Result<Vec<_>>>()
            .unwrap()
    };
    for (root, project, session) in tuples {
        conn.execute(
            "INSERT INTO raw_session_identities
             (source_root, transcript_path, host, fallback_session_id,
              canonical_session_id, project, legacy_project, status,
              contract_version, observed_mtime_ns, observed_size_bytes,
              first_seen_at_epoch, last_seen_at_epoch)
             VALUES (?1, ?2, ?3, ?4, ?4, ?5, ?5, 'active', 1, 1, 1, 1, 1)",
            params![
                root,
                format!("/tmp/.codex/sessions/{session}.jsonl"),
                host,
                session,
                project
            ],
        )
        .unwrap();
        let identity_id = conn.last_insert_rowid();
        conn.execute(
            "UPDATE raw_messages
             SET transcript_identity_id = ?1, transcript_record_ordinal = id
             WHERE source_root = ?2 AND project = ?3 AND session_id = ?4",
            params![identity_id, root, project, session],
        )
        .unwrap();
    }
}

fn list_project(conn: &Connection, project: &str) -> Vec<super::sessions::RawSessionSummary> {
    list_sessions(
        conn,
        &RawSessionQuery {
            since_epoch: None,
            until_epoch: None,
            project: Some(project.to_string()),
            sample_user_messages: 1,
            latest: None,
        },
    )
    .unwrap()
}

fn seed_host_project_session(
    conn: &Connection,
    host: &str,
    project: &str,
    session_id: &str,
    epoch: i64,
) -> i64 {
    let host_id: i64 = conn
        .query_row("SELECT id FROM hosts WHERE name = ?1", [host], |row| {
            row.get(0)
        })
        .unwrap();
    conn.execute(
        "INSERT INTO workspaces(root_path, created_at_epoch, updated_at_epoch)
         VALUES (?1, ?2, ?2)",
        params![project, epoch],
    )
    .unwrap();
    let workspace_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO projects(workspace_id, project_path, project_key, created_at_epoch, updated_at_epoch)
         VALUES (?1, ?2, ?3, ?4, ?4)",
        params![workspace_id, project, project.trim_start_matches('/'), epoch],
    )
    .unwrap();
    let project_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO sessions(host_id, workspace_id, project_id, session_id, started_at_epoch,
                              last_seen_at_epoch, status)
         VALUES (?1, ?2, ?3, ?4, ?5, ?5, 'active')",
        params![host_id, workspace_id, project_id, session_id, epoch],
    )
    .unwrap();
    conn.last_insert_rowid()
}

#[test]
fn list_sessions_without_summary_exposes_mmdd_and_abstains_from_intent() {
    let conn = setup_conn();
    insert_at_epoch(
        &conn,
        "s-bare",
        "/proj",
        "first question",
        SHANGHAI_NEW_YEAR,
    );
    identify_raw_sessions(&conn, "codex-cli");

    let sessions = list_project(&conn, "/proj");
    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].mmdd.as_deref(), Some("0101"));
    assert_eq!(sessions[0].session_intent, None);
    assert_eq!(sessions[0].session_topic, None);
    assert_eq!(sessions[0].display_label, None);
    assert_eq!(sessions[0].session_intent_source, None);
}

#[test]
fn list_sessions_joins_summary_intent_by_session_row_id() {
    let conn = setup_conn();
    insert_at_epoch(
        &conn,
        "s-linked",
        "/proj",
        "repair listing",
        SHANGHAI_NEW_YEAR,
    );
    identify_raw_sessions(&conn, "codex-cli");
    let session_row_id =
        seed_host_project_session(&conn, "codex-cli", "/proj", "s-linked", SHANGHAI_NEW_YEAR);
    conn.execute(
        "INSERT INTO session_summaries
         (memory_session_id, project, session_row_id, request, created_at_epoch,
          session_intent, session_topic, session_intent_source)
         VALUES ('s-linked', '/proj', ?1, 'ignored fallback', ?2, 'fix',
                 'Batch text display', 'summary')",
        params![session_row_id, SHANGHAI_NEW_YEAR],
    )
    .unwrap();

    let sessions = list_project(&conn, "/proj");
    assert_eq!(sessions.len(), 1);
    assert_eq!(
        sessions[0].display_label.as_deref(),
        Some("0101|fix|Batch text display")
    );
    assert_eq!(sessions[0].session_intent.as_deref(), Some("fix"));
    assert_eq!(
        sessions[0].session_intent_source.as_deref(),
        Some("summary")
    );
}

#[test]
fn list_sessions_abstains_when_summary_host_is_unknown() {
    let conn = setup_conn();
    insert_at_epoch(
        &conn,
        "s-memory",
        "/proj",
        "fallback title",
        SHANGHAI_NEW_YEAR,
    );
    identify_raw_sessions(&conn, "codex-cli");
    conn.execute(
        "INSERT INTO session_summaries
         (memory_session_id, project, request, created_at_epoch,
          session_intent, session_topic, session_intent_source)
         VALUES ('s-memory', '/proj', 'ignored', ?1, 'doc', 'README navigation', 'override')",
        [SHANGHAI_NEW_YEAR],
    )
    .unwrap();

    let sessions = list_project(&conn, "/proj");
    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].display_label, None);
    assert_eq!(sessions[0].session_intent, None);
    assert_eq!(sessions[0].session_topic, None);
    assert_eq!(sessions[0].session_intent_source, None);
}

#[test]
fn list_sessions_does_not_attach_summary_from_another_host() {
    let conn = setup_conn();
    insert_at_epoch(
        &conn,
        "s-shared",
        "/proj",
        "codex question",
        SHANGHAI_NEW_YEAR,
    );
    identify_raw_sessions(&conn, "codex-cli");
    let session_row_id =
        seed_host_project_session(&conn, "claude-code", "/proj", "s-shared", SHANGHAI_NEW_YEAR);
    conn.execute(
        "INSERT INTO session_summaries
         (memory_session_id, project, session_row_id, request, created_at_epoch,
          session_intent, session_topic, session_intent_source)
         VALUES ('s-shared', '/proj', ?1, 'claude question', ?2, 'fix',
                 'Claude-only topic', 'summary')",
        params![session_row_id, SHANGHAI_NEW_YEAR],
    )
    .unwrap();

    let sessions = list_project(&conn, "/proj");
    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].host, "codex-cli");
    assert_eq!(sessions[0].mmdd.as_deref(), Some("0101"));
    assert_eq!(sessions[0].display_label, None);
    assert_eq!(sessions[0].session_intent, None);
    assert_eq!(sessions[0].session_topic, None);
    assert_eq!(sessions[0].session_intent_source, None);
}