malvin 0.2.6

Non-interactive research and coding agent
use std::path::PathBuf;

use crate::agent_backend::SdkClient;
use crate::artifacts::RunArtifacts;
use crate::orchestrator::workflow_context_paths_only;
use crate::prompt_stratification::{PromptStratum, join_labeled_strata};
use crate::prompts::{DO_HEADER_MD, PromptError, PromptStore, render_header};

pub struct BindMalvinHeader<'a> {
    pub client: &'a mut SdkClient,
    pub store: &'a PromptStore,
    pub artifacts: &'a RunArtifacts,
    pub model: &'a str,
    pub log_path: PathBuf,
}

pub fn render_malvin_header_body(
    store: &PromptStore,
    artifacts: &RunArtifacts,
    model: &str,
) -> Result<String, String> {
    let ctx = workflow_context_paths_only(artifacts, model);
    let prompt = render_header(store, ctx.as_map()).map_err(|e| e.0)?;
    Ok(prompt.trim().to_string())
}

pub fn bind_do_header(input: BindMalvinHeader<'_>) -> Result<(), String> {
    let ctx = workflow_context_paths_only(input.artifacts, input.model);
    let coding = render_malvin_header_body(input.store, input.artifacts, input.model)?;
    let mode = input
        .store
        .render_prompt_only(DO_HEADER_MD, ctx.as_map())
        .map_err(|e: PromptError| e.0)?;
    let prompt = join_labeled_strata([
        (PromptStratum::WorkflowHeader, coding.trim_end()),
        (PromptStratum::WorkflowHeader, mode.trim_end()),
    ]);
    input
        .client
        .bind_session_header(prompt, input.log_path, DO_HEADER_MD);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::prompts::{DO_HEADER_MD, HEADER_MD, PromptStore};
    use crate::test_utils::with_isolated_home;

    #[test]
    fn kiss_cov_bind_malvin_header() {
        let _ = super::bind_do_header;
        let _ = super::render_malvin_header_body;
        let _: Option<super::BindMalvinHeader<'_>> = None;
    }

    #[test]
    fn kiss_cov_bind_session_headers() {
        let _ = super::bind_do_header;
        let _ = super::render_malvin_header_body;
    }

    #[test]
    fn bind_do_header_includes_header_and_do_header_at_spawn() {
        with_isolated_home(|work| {
            let artifacts = crate::artifacts::create_run_artifacts_from_text_opts(
                "req",
                Some(work),
                crate::run_id::RunDirOptions::default(),
            )
            .expect("artifacts");
            let prompt_root = artifacts.run_dir.join("prompts");
            std::fs::create_dir_all(&prompt_root).expect("mkdir");
            std::fs::write(prompt_root.join(HEADER_MD), "HDR\n").expect("header");
            std::fs::write(prompt_root.join(DO_HEADER_MD), "DO\n").expect("do_header");
            let store = PromptStore::with_root(prompt_root);
            let mut client = crate::cursor_sdk::cursor_sdk_client_from_raw(
                "cursor:auto",
                crate::acp::AgentIoOptions {

                    no_tee: true,
                    raw_output: true,
                    show_thoughts_on_stdout: false,
                    emit_stdout_markdown: false,
                    log_full_outgoing_prompts: false,
                },
                1,
            );
            bind_do_header(BindMalvinHeader {
                client: &mut client,
                store: &store,
                artifacts: &artifacts,
                model: crate::config::DEFAULT_CLI_MODEL,
                log_path: artifacts.log_path("do_header"),
            })
            .expect("bind");
            let header = client
                .header_lifecycle
                .pending_header()
                .expect("bound");
            assert!(header.prompt.contains("HDR"));
            assert!(header.prompt.contains("DO"));
            assert_eq!(header.stdout_label, DO_HEADER_MD);
        });
    }
}