supercode-core 0.2.1

A lightweight, fully-customizable AI coding agent SDK in Rust. Talks to any model via OpenRouter or any OpenAI-compatible endpoint.
Documentation
use std::path::{Path, PathBuf};

use supercode::{SessionFollower, SessionSnapshotReason, SessionSource, SessionWatchEvent};

fn fixture(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name)
}

fn fresh_dir(tag: &str) -> PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let dir = std::env::temp_dir().join(format!(
        "supercode-watch-{tag}-{}-{nanos}",
        std::process::id()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

#[test]
fn initial_snapshot_supports_all_four_harnesses_without_repolling_unchanged_sources() {
    let cases = [
        (
            fixture("claude_code_session.jsonl"),
            SessionSource::ClaudeCode,
        ),
        (fixture("codex_session.jsonl"), SessionSource::Codex),
        (fixture("pi_session_live_corpus.jsonl"), SessionSource::Pi),
        (
            fixture("opencode_fixture/opencode.db"),
            SessionSource::OpenCode,
        ),
    ];

    for (path, expected_source) in cases {
        let mut follower = SessionFollower::open(&path, None).unwrap();
        let first = follower.poll().unwrap().expect("initial event");
        match first {
            SessionWatchEvent::SessionSnapshot {
                sequence,
                reason,
                session,
            } => {
                assert_eq!(sequence, 1, "{}", path.display());
                assert_eq!(reason, SessionSnapshotReason::Initial, "{}", path.display());
                assert_eq!(session.meta.source, expected_source, "{}", path.display());
                assert!(!session.messages.is_empty(), "{}", path.display());
            }
            other => panic!(
                "expected initial snapshot for {}: {other:?}",
                path.display()
            ),
        }
        assert!(
            follower.poll().unwrap().is_none(),
            "an unchanged source must not be reparsed into another event: {}",
            path.display()
        );
    }
}

#[test]
fn append_emits_only_new_messages_and_rewrite_emits_a_snapshot() {
    let dir = fresh_dir("append-rewrite");
    let path = dir.join("session.jsonl");
    let first_line = concat!(
        r#"{"type":"user","message":{"role":"user","content":"one"},"uuid":"u1","parentUuid":null,"sessionId":"s1"}"#,
        "\n"
    );
    let second_line = concat!(
        r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"two"}]},"uuid":"a1","parentUuid":"u1","sessionId":"s1"}"#,
        "\n"
    );
    std::fs::write(&path, first_line).unwrap();

    let mut follower = SessionFollower::open(&path, None).unwrap();
    assert_eq!(follower.poll().unwrap().unwrap().sequence(), 1);

    use std::io::Write;
    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(&path)
        .unwrap();
    file.write_all(second_line.as_bytes()).unwrap();
    file.flush().unwrap();

    match follower.poll().unwrap().expect("append event") {
        SessionWatchEvent::MessagesAppended {
            sequence,
            session_id,
            messages,
        } => {
            assert_eq!(sequence, 2);
            assert_eq!(session_id.as_deref(), Some("s1"));
            assert_eq!(messages.len(), 1);
            assert_eq!(messages[0].content.as_deref(), Some("two"));
        }
        other => panic!("expected append event: {other:?}"),
    }

    std::fs::write(
        &path,
        r#"{"type":"user","message":{"role":"user","content":"replacement history is deliberately longer"},"uuid":"u2","parentUuid":null,"sessionId":"s1"}
"#,
    )
    .unwrap();
    match follower.poll().unwrap().expect("rewrite event") {
        SessionWatchEvent::SessionSnapshot {
            sequence,
            reason,
            session,
        } => {
            assert_eq!(sequence, 3);
            assert_eq!(reason, SessionSnapshotReason::HistoryRewritten);
            assert_eq!(session.messages.len(), 1);
        }
        other => panic!("expected rewrite snapshot: {other:?}"),
    }

    std::fs::remove_dir_all(dir).ok();
}

#[test]
fn malformed_partial_write_is_recoverable_and_keeps_the_last_good_snapshot() {
    let dir = fresh_dir("partial");
    let path = dir.join("session.jsonl");
    let first_line = concat!(
        r#"{"type":"user","message":{"role":"user","content":"one"},"uuid":"u1","parentUuid":null,"sessionId":"s1"}"#,
        "\n"
    );
    std::fs::write(&path, first_line).unwrap();
    let mut follower = SessionFollower::open(&path, None).unwrap();
    follower.poll().unwrap().unwrap();

    use std::io::Write;
    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(&path)
        .unwrap();
    file.write_all(b"{").unwrap();
    file.flush().unwrap();
    match follower.poll().unwrap().expect("parse error event") {
        SessionWatchEvent::WatchError { sequence, message } => {
            assert_eq!(sequence, 2);
            assert!(message.contains("malformed or truncated"));
        }
        other => panic!("expected recoverable error: {other:?}"),
    }
    assert!(follower.poll().unwrap().is_none());

    std::fs::remove_dir_all(dir).ok();
}

#[test]
fn claude_subagent_file_creation_invalidates_the_source_fingerprint() {
    let dir = fresh_dir("subagent");
    let path = dir.join("main.jsonl");
    let main = concat!(
        r#"{"type":"user","message":{"role":"user","content":"delegate"},"uuid":"u1","parentUuid":null,"sessionId":"main"}"#,
        "\n"
    );
    std::fs::write(&path, main).unwrap();
    let mut follower = SessionFollower::open(&path, None).unwrap();
    follower.poll().unwrap().unwrap();

    let subagent_dir = dir.join("main/subagents");
    std::fs::create_dir_all(&subagent_dir).unwrap();
    std::fs::write(
        subagent_dir.join("agent-child.jsonl"),
        concat!(
            r#"{"type":"user","message":{"role":"user","content":"child work"},"uuid":"c1","parentUuid":null,"sessionId":"child","agentId":"child"}"#,
            "\n"
        ),
    )
    .unwrap();

    match follower.poll().unwrap().expect("subagent snapshot") {
        SessionWatchEvent::SessionSnapshot {
            reason, session, ..
        } => {
            assert_eq!(reason, SessionSnapshotReason::HistoryRewritten);
            assert_eq!(session.subagents.len(), 1);
        }
        other => panic!("expected snapshot after subagent creation: {other:?}"),
    }

    std::fs::remove_dir_all(dir).ok();
}

#[test]
fn opencode_default_selection_is_pinned_when_an_unrelated_session_becomes_newest() {
    const PRIMARY: &str = "ses_fixtureAAAAAAAAAAAAAAA1";
    const SECONDARY: &str = "ses_fixtureBBBBBBBBBBBBBBB1";
    let dir = fresh_dir("opencode-pin");
    let path = dir.join("opencode.db");
    std::fs::copy(fixture("opencode_fixture/opencode.db"), &path).unwrap();

    let mut follower = SessionFollower::open(&path, None).unwrap();
    match follower.poll().unwrap().unwrap() {
        SessionWatchEvent::SessionSnapshot { session, .. } => {
            assert_eq!(session.meta.session_id.as_deref(), Some(PRIMARY));
        }
        other => panic!("expected initial OpenCode snapshot: {other:?}"),
    }

    let connection = rusqlite::Connection::open(&path).unwrap();
    connection
        .execute(
            "UPDATE session SET time_updated = time_updated + 999999999 WHERE id = ?1",
            [SECONDARY],
        )
        .unwrap();
    drop(connection);

    assert!(
        follower.poll().unwrap().is_none(),
        "activity in another OpenCode session must not switch the pinned target"
    );
    std::fs::remove_dir_all(dir).ok();
}