rho-coding-agent 1.13.0

A lightweight agent harness inspired by Pi
Documentation
use super::*;
use crate::tui::tests::test_app;

#[test]
fn terminal_lifecycle_errors_bypass_sdk_failure_handling() {
    let error = sdk_failure_from_running_terminal_error(
        super::during_turn::RunningTerminalError::Terminal(anyhow::anyhow!("resume failed")),
    )
    .unwrap_err();

    assert_eq!(error.to_string(), "resume failed");
}

fn failed_turn() -> FailedTurn {
    FailedTurn {
        input: rho_sdk::UserInput::text("continue the existing goal turn"),
        display_user: Some(Message::user_text("continuing active goal")),
        notification_context: None,
        initial_tool_call: None,
    }
}

#[test]
fn approval_and_questionnaire_share_one_interaction_slot() {
    assert!(interaction_slot_available(false, false));
    assert!(!interaction_slot_available(true, false));
    assert!(!interaction_slot_available(false, true));
    assert!(!interaction_slot_available(true, true));
}

#[test]
fn retry_request_reuses_the_failed_turn_input_and_display() {
    let failed_turn = failed_turn();
    let PromptTurnRequest::Retry(retry) = PromptTurnRequest::Retry(failed_turn.clone()) else {
        unreachable!("constructed a retry request")
    };

    assert_eq!(retry, failed_turn);
}

#[test]
fn persisted_display_excludes_notification_context_for_standard_and_command_prompts() {
    let cases = [
        (
            TurnPrompt::standard("model prompt".into(), "visible prompt".into()),
            "model prompt",
            "visible prompt",
        ),
        (
            TurnPrompt::command("expanded command context".into(), "/goal run tests".into()),
            "expanded command context",
            "/goal run tests",
        ),
    ];

    for (prompt, expected_model, expected_display) in cases {
        let mut failed_turn = FailedTurn::from_prompt(prompt, Vec::new()).unwrap();
        failed_turn.attach_notification_context("hidden agent result".into());
        let model_input = failed_turn.model_input().unwrap();

        assert_eq!(
            model_input.blocks(),
            &[
                ContentBlock::Text("hidden agent result".into()),
                ContentBlock::Text(expected_model.into()),
            ]
        );
        assert_eq!(
            failed_turn.display_user,
            Some(Message::user_text(expected_display))
        );
    }
}

#[test]
fn retry_attachment_keeps_prior_batches_and_adds_each_new_batch_once() {
    let mut failed_turn = FailedTurn::from_prompt(
        TurnPrompt::standard("user prompt".into(), "user prompt".into()),
        Vec::new(),
    )
    .unwrap();
    failed_turn.attach_notification_context("first batch".into());

    let mut retry = failed_turn.clone();
    retry.attach_notification_context("second batch".into());
    let later_retry_without_new_notifications = retry.clone();
    let model_input = later_retry_without_new_notifications.model_input().unwrap();
    let ContentBlock::Text(context) = &model_input.blocks()[0] else {
        panic!("notification context must be text")
    };

    assert_eq!(context.matches("first batch").count(), 1);
    assert_eq!(context.matches("second batch").count(), 1);
    assert!(context.find("first batch") < context.find("second batch"));
    assert!(context.len() <= crate::tools::agent::NOTIFICATION_CONTEXT_BYTES);
    assert_eq!(
        &model_input.blocks()[1..],
        &[ContentBlock::Text("user prompt".into())]
    );
    assert_eq!(
        later_retry_without_new_notifications.display_user,
        Some(Message::user_text("user prompt"))
    );
}

#[test]
fn failed_turn_keeps_live_partial_assistant_text_before_error() {
    let mut app = test_app();
    app.begin_provider_turn_ui();
    app.turn.set_current_turn_start(Some(0));
    app.streams
        .assistant_stream
        .push_delta("partial assistant before stream failure");

    let outcome = app.finalize_failed_turn("provider stream failed".into(), failed_turn());

    assert_eq!(outcome.kind(), TurnOutcomeKind::Failed);
    assert!(matches!(
        app.history.entries(),
        [Entry::Assistant(text), Entry::Error(error)]
            if text == "partial assistant before stream failure"
                && error == "provider stream failed"
    ));
    assert!(!app.is_ui_busy());
    assert!(app.streams.assistant_stream.is_empty());
    assert_eq!(app.status, "error");
}