daat-locus 0.2.0

A long-running local agent runtime with memory, workflows, apps, and sleep-time self-improvement.
use super::sleep_driver::{SleepTrigger, start_background_sleep};
use super::*;
use crate::daemon::DaemonControlCommand;
use crate::openskills::{reload_openskills_for_runtime, set_openskill_auto_use};

pub(crate) async fn handle_dashboard_control_command(
    context: &mut Context,
    tx: &tokio::sync::watch::Sender<DashboardState>,
    sleep_result_tx: &tokio::sync::mpsc::UnboundedSender<SleepTaskResult>,
    sleep_running: &mut bool,
    sleep_status: &mut SleepStatusSnapshot,
    command: DashboardControlCommand,
) {
    match command {
        DashboardControlCommand::RunSleep => {
            if *sleep_running {
                set_runtime_status_only(Some(tx), "sleep is already running in the background");
                sync_dashboard_state(context, tx, sleep_status, None);
                return;
            }
            start_background_sleep(
                context,
                tx,
                sleep_result_tx,
                sleep_running,
                sleep_status,
                SleepTrigger::Manual,
                "running sleep in the background",
            )
            .await;
        }
        DashboardControlCommand::RestartDaemon => {
            set_runtime_status(
                Some(tx),
                RuntimeStatusLevel::Info,
                "daemon restart requested from command system",
            );
            sync_dashboard_state(context, tx, sleep_status, None);
            match context
                .daemon_control_tx
                .send(DaemonControlCommand::RestartRequested)
            {
                Ok(()) => {}
                Err(err) => {
                    tracing::error!("failed to queue daemon restart from dashboard command: {err}");
                    set_runtime_status(
                        Some(tx),
                        RuntimeStatusLevel::Error,
                        format!("failed to queue daemon restart: {err}"),
                    );
                    sync_dashboard_state(context, tx, sleep_status, None);
                }
            }
        }
        DashboardControlCommand::ReloadSkills => {
            context.openskills = reload_openskills_for_runtime(&context.execution_cwd);
            set_runtime_status(Some(tx), RuntimeStatusLevel::Info, "skills reloaded");
            sync_dashboard_state(context, tx, sleep_status, None);
        }
        DashboardControlCommand::SetSkillAutoUse { path, enabled } => {
            match set_openskill_auto_use(&path, enabled) {
                Ok(()) => {
                    context.openskills = reload_openskills_for_runtime(&context.execution_cwd);
                    let action = if enabled { "enabled" } else { "disabled" };
                    set_runtime_status(
                        Some(tx),
                        RuntimeStatusLevel::Info,
                        format!("skills auto-use {action} for {}", path.display()),
                    );
                }
                Err(err) => {
                    set_runtime_status(
                        Some(tx),
                        RuntimeStatusLevel::Error,
                        format!("failed to update skills auto-use: {err}"),
                    );
                }
            }
            sync_dashboard_state(context, tx, sleep_status, None);
        }
        DashboardControlCommand::ClearConversation => {
            let cleared_events = match context.events.clear_all() {
                Ok(count) => count,
                Err(err) => {
                    tracing::error!("failed to clear events during /clear: {err:?}");
                    0
                }
            };
            let cleared_event_work = match context.pending_work.clear_events() {
                Ok(count) => count,
                Err(err) => {
                    tracing::error!("failed to clear event pending work during /clear: {err:?}");
                    0
                }
            };
            let cleared_outbound = match context.telegram.clear_outbox() {
                Ok(count) => count,
                Err(err) => {
                    tracing::error!("failed to clear telegram outbox during /clear: {err:?}");
                    0
                }
            };
            let cleared_live_drafts = {
                let mut live_drafts = context.telegram_live_drafts.lock();
                let count = live_drafts.len();
                live_drafts.clear();
                count
            };
            let cleared_dashboard_history = match context.dashboard_history.as_ref() {
                Some(history) => match history.clear_all() {
                    Ok(count) => count,
                    Err(err) => {
                        tracing::error!(
                            "failed to clear dashboard activity history during /clear: {err:?}"
                        );
                        0
                    }
                },
                None => 0,
            };
            if let Some(session) = context.active_primitive_run.as_mut() {
                session.final_summary = "abandoned by dashboard /clear".to_string();
            }
            context.queue_active_primitive_run_for_flush(
                crate::workflow::PrimitiveRunOutcome::Abandoned,
            );
            context.bound_primitive_id = None;
            context.bound_primitive_composition = None;
            context.install_live_progress(None);
            context.claimed_event_ids.clear();
            context.active_runtime_turn = false;
            context.set_runtime_phase(None);
            context.runtime_turn_started_at = None;
            context.runtime_turn_started_at_ms = None;
            context.current_work_origin = None;
            context.workflow_step_started_bound_id = None;
            context.memory.clear_runtime_conversation().await;
            if context.plan.clear()
                && let Err(err) = context.plan.sync_to_disk().await
            {
                tracing::error!("failed to persist cleared plan: {err}");
            }
            tx.send_modify(|state| {
                state.activity_history = DashboardActivityHistoryWindow::default();
                state.activity_cells.clear();
                state.live_activity_cells.clear();
                state.web_activity_items.clear();
                state.live_web_activity_items.clear();
                crate::dashboard::sync_web_activity_state(state);
            });
            set_runtime_status(
                Some(tx),
                RuntimeStatusLevel::Info,
                format!(
                    "runtime conversation, current plan, events, and dashboard activity cleared (events={cleared_events}, event_work={cleared_event_work}, telegram_outbox={cleared_outbound}, live_drafts={cleared_live_drafts}, activity_items={cleared_dashboard_history})"
                ),
            );
            sync_dashboard_state(context, tx, sleep_status, None);
        }
        DashboardControlCommand::InterruptRuntime => {
            if context.active_runtime_turn {
                let outcome = crate::runtime::runtime_loop::interrupt_active_runtime_turn(
                    context,
                    "dashboard interrupt",
                );
                set_runtime_status(
                    Some(tx),
                    RuntimeStatusLevel::Info,
                    format!(
                        "runtime turn interrupted (events={}, app_notices={})",
                        outcome.failed_events, outcome.suppressed_app_notices
                    ),
                );
            } else {
                set_runtime_status(
                    Some(tx),
                    RuntimeStatusLevel::Info,
                    "no runtime turn to interrupt",
                );
            }
            sync_dashboard_state(context, tx, sleep_status, None);
        }
    }
}