remem-ai 0.6.83

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use super::*;
use rusqlite::params;

fn identify_raw_row(conn: &Connection, row_id: i64, host: &str, transcript: &str) {
    let session_mode = if host == "codex-cli" {
        "interactive"
    } else {
        "unknown"
    };
    identify_raw_row_with_mode(conn, row_id, host, session_mode, transcript);
}

fn identify_raw_row_with_mode(
    conn: &Connection,
    row_id: i64,
    host: &str,
    session_mode: &str,
    transcript: &str,
) {
    let (root, project, session): (String, String, String) = conn
        .query_row(
            "SELECT source_root, project, session_id FROM raw_messages WHERE id = ?1",
            [row_id],
            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
        )
        .unwrap();
    conn.execute(
        "INSERT INTO raw_session_identities
         (source_root, transcript_path, host, session_mode, 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, ?5, ?5, ?6, ?6, 'active', 1, 1, 1, 1, 1)",
        params![root, transcript, host, session_mode, session, project],
    )
    .unwrap();
    let identity_id = conn.last_insert_rowid();
    conn.execute(
        "UPDATE raw_messages
         SET transcript_identity_id = ?1, transcript_record_ordinal = 1
         WHERE id = ?2",
        params![identity_id, row_id],
    )
    .unwrap();
}

#[test]
fn list_sessions_rejects_conflicting_modes_across_contributing_identities() {
    let conn = setup_conn();
    let first = insert_at_epoch(&conn, "shared", "/repo", ROLE_USER, "first", 100);
    let second = insert_at_epoch(&conn, "shared", "/repo", ROLE_ASSISTANT, "second", 200);
    identify_raw_row_with_mode(
        &conn,
        first,
        "codex-cli",
        "interactive",
        "/tmp/.codex/sessions/first.jsonl",
    );
    identify_raw_row_with_mode(
        &conn,
        second,
        "codex-cli",
        "unattended",
        "/tmp/.codex/sessions/second.jsonl",
    );

    let error = list_sessions(&conn, &RawSessionQuery::default())
        .expect_err("one raw session cannot merge conflicting trusted modes");
    assert!(error.to_string().contains("mode provenance conflicts"));
}

#[test]
fn list_sessions_separates_same_selector_across_hosts() {
    let conn = setup_conn();
    let codex_row = insert_at_epoch(&conn, "shared", "/repo", ROLE_USER, "codex", 100);
    let claude_row = insert_at_epoch(&conn, "shared", "/repo", ROLE_USER, "claude", 110);
    identify_raw_row(
        &conn,
        codex_row,
        "codex-cli",
        "/tmp/.codex/sessions/shared.jsonl",
    );
    identify_raw_row(
        &conn,
        claude_row,
        "claude-code",
        "/tmp/.claude/projects/repo/shared.jsonl",
    );

    let sessions = list_sessions(&conn, &RawSessionQuery::default()).unwrap();
    assert_eq!(sessions.len(), 2);
    assert_eq!(sessions[0].host, "codex-cli");
    assert_eq!(sessions[0].session_mode, "interactive");
    assert_eq!(sessions[1].host, "claude-code");
    assert_eq!(sessions[1].session_mode, "unknown");
    assert_ne!(sessions[0].session_ref, sessions[1].session_ref);
    assert_ne!(sessions[0].content_hash, sessions[1].content_hash);
}

#[test]
fn list_sessions_keeps_unbound_hook_fallbacks_outside_the_transcript_contract() {
    let conn = setup_conn();
    let identified = insert_at_epoch(&conn, "shared", "/repo", ROLE_USER, "question", 100);
    identify_raw_row(
        &conn,
        identified,
        "codex-cli",
        "/tmp/.codex/sessions/shared.jsonl",
    );
    insert_raw_message(
        &conn,
        "shared",
        "/repo",
        ROLE_ASSISTANT,
        "hook fallback",
        SOURCE_HOOK,
        None,
        None,
    )
    .unwrap();
    insert_raw_message(
        &conn,
        "hook-only",
        "/repo",
        ROLE_ASSISTANT,
        "partial fallback",
        SOURCE_HOOK,
        None,
        None,
    )
    .unwrap();

    let sessions = list_sessions(&conn, &RawSessionQuery::default()).unwrap();

    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].session_id, "shared");
    assert_eq!(sessions[0].message_count, 1);
    assert_eq!(sessions[0].user_message_count, 1);
    assert_eq!(sessions[0].assistant_message_count, 0);
}

#[test]
fn list_sessions_hash_is_stable_and_changes_with_an_identified_occurrence() {
    let conn = setup_conn();
    let first = insert_at_epoch(&conn, "s1", "/repo", ROLE_USER, "first", 100);
    identify_raw_row(&conn, first, "codex-cli", "/tmp/.codex/sessions/s1.jsonl");
    let before = list_sessions(&conn, &RawSessionQuery::default()).unwrap();
    let repeated = list_sessions(&conn, &RawSessionQuery::default()).unwrap();
    assert_eq!(before[0].session_ref, repeated[0].session_ref);
    assert_eq!(before[0].content_hash, repeated[0].content_hash);

    let second = insert_at_epoch(&conn, "s1", "/repo", ROLE_ASSISTANT, "second", 200);
    let identity_id: i64 = conn
        .query_row(
            "SELECT transcript_identity_id FROM raw_messages WHERE id = ?1",
            [first],
            |row| row.get(0),
        )
        .unwrap();
    conn.execute(
        "UPDATE raw_messages
         SET transcript_identity_id = ?1, transcript_record_ordinal = 2
         WHERE id = ?2",
        params![identity_id, second],
    )
    .unwrap();
    let after = list_sessions(&conn, &RawSessionQuery::default()).unwrap();
    assert_eq!(before[0].session_ref, after[0].session_ref);
    assert_ne!(before[0].content_hash, after[0].content_hash);
    assert_eq!(after[0].message_count, 2);

    conn.execute(
        "UPDATE raw_messages SET transcript_record_ordinal = 3 WHERE id = ?1",
        [second],
    )
    .unwrap();
    let rekeyed = list_sessions(&conn, &RawSessionQuery::default()).unwrap();
    assert_ne!(after[0].content_hash, rekeyed[0].content_hash);
}

#[test]
fn list_sessions_latest_is_bounded_and_missing_host_fails_closed() {
    let conn = setup_conn();
    insert_at_epoch(&conn, "old", "/repo", ROLE_USER, "old", 100);
    insert_at_epoch(&conn, "new", "/repo", ROLE_USER, "new", 200);
    identify_raw_sessions(&conn, "codex-cli");
    let latest = list_sessions(
        &conn,
        &RawSessionQuery {
            latest: Some(1),
            ..RawSessionQuery::default()
        },
    )
    .unwrap();
    assert_eq!(latest.len(), 1);
    assert_eq!(latest[0].session_id, "new");

    let missing = insert_at_epoch(&conn, "missing-host", "/repo", ROLE_USER, "missing", 300);
    assert!(missing > 0);
    conn.execute(
        "UPDATE raw_messages SET source = 'transcript' WHERE id = ?1",
        [missing],
    )
    .unwrap();
    let error = list_sessions(&conn, &RawSessionQuery::default())
        .expect_err("unidentified raw provenance must not be guessed");
    assert!(error
        .to_string()
        .contains("provenance is missing or conflicted"));
}

#[test]
fn list_sessions_reads_only_selected_bounded_samples() {
    let conn = setup_conn();
    let old = insert_at_epoch(&conn, "old", "/repo", ROLE_USER, "old", 100);
    let first = insert_at_epoch(&conn, "new", "/repo", ROLE_USER, "first", 200);
    let second = insert_at_epoch(&conn, "new", "/repo", ROLE_USER, "second", 210);
    identify_raw_sessions(&conn, "codex-cli");
    conn.execute(
        "UPDATE raw_messages SET content = CAST(X'80' AS BLOB) WHERE id IN (?1, ?2)",
        params![old, second],
    )
    .unwrap();

    let sessions = list_sessions(
        &conn,
        &RawSessionQuery {
            sample_user_messages: 1,
            latest: Some(1),
            ..RawSessionQuery::default()
        },
    )
    .expect("discarded and over-limit sample content must not be read");

    assert_eq!(sessions.len(), 1);
    assert_eq!(sessions[0].session_id, "new");
    assert_eq!(sessions[0].message_count, 2);
    assert_eq!(sessions[0].user_message_samples, vec!["first"]);
    assert!(first > 0);
}