zagens-cli 0.8.3

Zagens headless CLI + HTTP/SSE runtime sidecar (`zagens`, `zagens-runtime` binaries)
Documentation
//! Tui [`EnginePlatformExt`] — op-loop dispatch (M8).

use async_trait::async_trait;
use zagens_core::engine::Engine as CoreEngine;
use zagens_core::engine::op::Op;
use zagens_core::engine::platform_ext::EnginePlatformExt;

use crate::agent_surface::AppMode;
use crate::context_snapshot::ThreadContextSnapshot;
use crate::core::events::Event;
use crate::hooks::HookEvent;
use tokio::sync::oneshot;

use super::Engine;
use super::runtime_ext::EngineRuntimeExt;
use super::turn_loop::host_impl::turn_loop_to_app_mode;

#[async_trait]
impl EnginePlatformExt<crate::sandbox::SandboxPolicy, crate::tools::user_input::UserInputResponse>
    for EngineRuntimeExt
{
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }

    async fn dispatch_op(
        &mut self,
        core: &mut CoreEngine<
            crate::sandbox::SandboxPolicy,
            crate::tools::user_input::UserInputResponse,
        >,
        op: Op,
    ) {
        let engine = super::engine_from_core(core);
        match op {
            Op::SendMessage {
                content,
                mode,
                model,
                goal_objective,
                reasoning_effort,
                reasoning_effort_auto,
                auto_model,
                allow_shell,
                trust_mode,
                auto_approve,
                approval_mode,
                temperature,
                top_p,
                max_output_tokens,
            } => {
                engine
                    .handle_send_message(
                        content,
                        turn_loop_to_app_mode(mode),
                        model,
                        goal_objective,
                        reasoning_effort,
                        reasoning_effort_auto,
                        auto_model,
                        allow_shell,
                        trust_mode,
                        auto_approve,
                        approval_mode,
                        temperature,
                        top_p,
                        max_output_tokens,
                    )
                    .await;
            }
            Op::SpawnSubAgent { prompt } => {
                engine.handle_spawn_subagent_op(&prompt).await;
            }
            Op::ListSubAgents => {
                engine.handle_list_subagents_op().await;
            }
            Op::ChangeMode { mode } => {
                engine
                    .handle_change_mode_op(turn_loop_to_app_mode(mode))
                    .await;
            }
            Op::SetModel { model } => {
                engine.apply_set_model_op(model).await;
            }
            Op::SetCompaction { config } => {
                engine.apply_set_compaction_op(config).await;
            }
            Op::SyncSession {
                messages,
                system_prompt,
                model,
                workspace,
            } => {
                engine
                    .sync_session_from_op(messages, system_prompt, model, workspace)
                    .await;
            }
            Op::ApplyKernelResume { hints } => {
                engine.apply_kernel_resume_with_replay(&hints).await;
            }
            Op::CompactContext => {
                engine.handle_compact_context_op().await;
            }
            Op::Rlm {
                content,
                model,
                child_model,
                max_depth,
            } => {
                engine
                    .handle_rlm_op(content, model, child_model, max_depth)
                    .await;
            }
            Op::EditLastTurn { new_message } => {
                engine.handle_edit_last_turn(new_message).await;
            }
            Op::QueryContext { reply } => {
                engine.handle_query_context_op(reply);
            }
            Op::QueryHarnessTaskGraph { reply } => {
                engine.handle_query_harness_task_graph_op(reply).await;
            }
            Op::QueryHarnessCycles { reply } => {
                engine.handle_query_harness_cycles_op(reply).await;
            }
            Op::CancelRequest
            | Op::ApproveToolCall { .. }
            | Op::DenyToolCall { .. }
            | Op::TruncateBeforeLastUserMessage { .. }
            | Op::Shutdown => {}
        }
    }

    async fn on_shutdown(&mut self) {
        if self
            .hook_executor
            .has_hooks_for_event(HookEvent::SessionEnd)
        {
            let ctx = self.hook_executor.base_context();
            self.hook_executor.execute(HookEvent::SessionEnd, &ctx);
        }
        if let Some(pool) = self.mcp_pool.as_ref() {
            let mut guard = pool.lock().await;
            guard.shutdown_all().await;
        }
    }
}

impl Engine {
    pub(in crate::core::engine) async fn handle_list_subagents_op(&self) {
        let agents = self.list_subagents().await;
        let _ = self.tx_event.send(Event::AgentList { agents }).await;
    }

    pub(in crate::core::engine) async fn handle_change_mode_op(&mut self, mode: AppMode) {
        let previous = self.runtime_ext().turn_app_mode;
        if previous != mode {
            self.fire_mode_change(previous, mode);
            self.runtime_ext_mut().turn_app_mode = mode;
        }
        let _ = self
            .tx_event
            .send(Event::status(format!("Mode changed to: {mode:?}")))
            .await;
    }

    pub(in crate::core::engine) fn handle_query_context_op(
        &self,
        reply: oneshot::Sender<ThreadContextSnapshot>,
    ) {
        let _ = reply.send(self.engine_context_snapshot());
    }

    pub(in crate::core::engine) async fn handle_query_harness_task_graph_op(
        &self,
        reply: oneshot::Sender<serde_json::Value>,
    ) {
        let plan = self.config_ext().plan_state.lock().await.snapshot();
        let checklist = self.config_ext().todos.lock().await.snapshot();
        let value = crate::long_horizon::build_task_graph_value(
            &plan,
            &checklist,
            &self.session.messages,
            &self.config.locale_tag,
            &self.config.long_horizon,
            Some(&self.runtime_ext().long_horizon_state),
        );
        let _ = reply.send(value);
    }

    pub(in crate::core::engine) async fn handle_query_harness_cycles_op(
        &self,
        reply: oneshot::Sender<serde_json::Value>,
    ) {
        let active = self.estimated_input_tokens() as u64;
        let headroom = super::context::turn_response_headroom_tokens();
        let pressure =
            crate::long_horizon::context_pressure_ratio(active, headroom, &self.session.model)
                .map(|r| (r * 100.0).round() as u8);
        let archives = crate::cycle_manager::list_cycle_archive_summaries(&self.session.id);
        let configured_threshold =
            u32::try_from(self.config.cycle.threshold_for(&self.session.model)).ok();
        let value = crate::long_horizon::build_cycles_value(
            self.session.cycle_count,
            &self.session.cycle_briefings,
            &archives,
            pressure,
            Some(self.session.model.as_str()),
            configured_threshold,
        );
        let _ = reply.send(value);
    }

    pub(in crate::core::engine) async fn handle_compact_context_op(&mut self) {
        self.handle_manual_compaction().await;
    }

    pub(in crate::core::engine) async fn handle_rlm_op(
        &mut self,
        content: String,
        model: String,
        child_model: String,
        max_depth: u32,
    ) {
        self.handle_rlm(content, model, child_model, max_depth)
            .await;
    }
}