malvin 0.2.5

Non-interactive research and coding agent
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use crate::agent_backend::{
    AgentBackend, agent_backend_attach_run_timing_for_session,
    agent_backend_set_implement_display_name, agent_backend_set_run_timing,
    agent_backend_start_coder_session,
};
use crate::artifacts::{
    RunArtifacts, SessionDotfileBackups, create_run_artifacts_from_text, resolve_user_md_request,
};
use crate::cli::cli_request::require_cli_request;
use crate::run_id::RunDirOptions;

pub fn resolve_one_shot_request_artifacts(
    request: Option<&String>,
    command: &str,
    run_dir_opts: Option<RunDirOptions>,
) -> Result<(String, RunArtifacts), String> {
    let request = require_cli_request(request, command)?;
    let (text, work_dir) = resolve_user_md_request(&request)?;
    let artifacts = match run_dir_opts {
        Some(opts) => crate::artifacts::create_run_artifacts_from_text_opts(
            &text,
            Some(work_dir.as_path()),
            opts,
        )
        .map_err(|e| e.to_string())?,
        None => create_run_artifacts_from_text(&text, Some(work_dir.as_path()))
            .map_err(|e| e.to_string())?,
    };
    crate::run_id::activate_run(artifacts.run_dir.clone());
    Ok((text, artifacts))
}

pub fn finish_one_shot_auth_and_backups(
    client: &mut AgentBackend,
    artifacts: &RunArtifacts,
) -> Result<SessionDotfileBackups, String> {
    client.ensure_authenticated().map_err(|e| e.to_string())?;
    client.prompts_log_run_dir = Some(artifacts.run_dir.clone());
    SessionDotfileBackups::snapshot_after_ensuring_home_config(&artifacts.work_dir)
}

pub struct OneShotCoderGuard {
    timing: Arc<Mutex<crate::run_timing::RunTiming>>,
    run_dir: PathBuf,
}

impl OneShotCoderGuard {
    pub async fn begin(
        client: &mut AgentBackend,
        artifacts: &RunArtifacts,
        implement_label: &'static str,
    ) -> Result<Self, String> {
        let timing = agent_backend_attach_run_timing_for_session(client);
        if let Err(e) = agent_backend_start_coder_session(client, &artifacts.work_dir).await {
            agent_backend_set_run_timing(client, None);
            return Err(e.to_string());
        }
        agent_backend_set_implement_display_name(client, implement_label);
        Ok(Self {
            timing,
            run_dir: artifacts.run_dir.clone(),
        })
    }

    pub async fn finish(
        self,
        client: &mut AgentBackend,
        run_res: Result<(), String>,
    ) -> Result<(), String> {
        let end_res = client.end_coder_session().await.map_err(|e| e.to_string());
        let merged = crate::acp_post_run::prefer_primary_over_secondary(
            run_res,
            end_res,
            "end coder session",
        );
        crate::acp_post_run::emit_run_timing_json_only_after_backend(
            client,
            &self.run_dir,
            &self.timing,
            merged,
        )
    }
}

pub fn finish_one_shot_after_prompt(
    acp_res: Result<(), String>,
    work_dir: &Path,
    backups: &SessionDotfileBackups,
    result_md: &PathBuf,
) -> Result<(), String> {
    let r = crate::acp_post_run::merge_acp_with_workspace_session_restore_and_check_abort(
        acp_res, work_dir, backups, result_md,
    );
    if r.is_ok() {
        crate::cli::error_run_log::clear_command_error_run_dir();
    }
    r
}

#[cfg(test)]
mod kiss_cov {
    #[test]
    fn kiss_static_fn_item_refs() {
        let _ = super::resolve_one_shot_request_artifacts;
        let _ = super::finish_one_shot_auth_and_backups;
        let _ = super::finish_one_shot_after_prompt;
        let _ = stringify!(OneShotCoderGuard);
    }
}