ralph-agent-loop 0.3.1

A Rust CLI for managing AI agent loops with a structured JSON task queue
Documentation
//! Runner invocation validation tests.

use crate::commands::run::PhaseType;
use crate::contracts::{Model, ReasoningEffort, Runner};
use crate::runner::{OutputStream, RunnerBinaries, execution, resume_session, run_prompt};
use tempfile::tempdir;

#[test]
fn resume_session_missing_session_id_includes_runner_and_bin() {
    let dir = tempdir().expect("tempdir");
    let bins = RunnerBinaries {
        codex: "codex",
        opencode: "opencode",
        gemini: "gemini",
        claude: "claude",
        cursor: "agent",
        kimi: "kimi",
        pi: "pi",
    };

    let err = resume_session(
        Runner::Opencode,
        dir.path(),
        bins,
        Model::Glm47,
        None,
        execution::ResolvedRunnerCliOptions::default(),
        "   ",
        "hello",
        None,
        None,
        None,
        OutputStream::HandlerOnly,
        PhaseType::Implementation,
        None,
    )
    .unwrap_err();

    let msg = format!("{err}");
    assert!(msg.contains("operation=resume_session"));
    assert!(msg.contains("runner=opencode"));
    assert!(msg.contains("bin=opencode"));
    assert!(msg.to_lowercase().contains("session_id"));
}

#[test]
fn run_prompt_invalid_model_includes_operation_and_bin() {
    let dir = tempdir().expect("tempdir");
    let bins = RunnerBinaries {
        codex: "codex",
        opencode: "opencode",
        gemini: "gemini",
        claude: "claude",
        cursor: "agent",
        kimi: "kimi",
        pi: "pi",
    };

    let err = run_prompt(
        Runner::Codex,
        dir.path(),
        bins,
        Model::Glm47,
        Some(ReasoningEffort::Low),
        execution::ResolvedRunnerCliOptions::default(),
        "prompt",
        None,
        None,
        None,
        OutputStream::HandlerOnly,
        PhaseType::Implementation,
        None,
        None,
    )
    .unwrap_err();

    let msg = format!("{err}");
    assert!(msg.contains("operation=run_prompt"));
    assert!(msg.contains("runner=codex"));
    assert!(msg.contains("bin=codex"));
}

#[test]
fn semantic_failure_reason_detects_opencode_session_validation_error() {
    let stderr =
        r#"ZodError: [{"path":["sessionID"],"message":"Invalid string: must start with \"ses\""}]"#;
    let reason = super::super::invoke::semantic_failure_reason(&Runner::Opencode, stderr);
    assert_eq!(
        reason,
        Some("opencode rejected session_id during resume validation")
    );
}

#[test]
fn semantic_failure_reason_ignores_non_opencode_runners() {
    let stderr = "ZodError sessionID invalid_format must start with \"ses\"";
    let reason = super::super::invoke::semantic_failure_reason(&Runner::Gemini, stderr);
    assert_eq!(reason, None);
}