use super::sleep_driver::{SleepTrigger, start_background_sleep};
use super::{
Context, DashboardActivityHistoryWindow, DashboardControlCommand, DashboardState,
RuntimeStatusLevel, SleepStatusSnapshot, SleepTaskResult, set_runtime_status,
set_runtime_status_only, sync_dashboard_state,
};
use crate::daemon::DaemonControlCommand;
use crate::openskills::{reload_openskills_for_runtime, set_openskill_auto_use};
use crate::workflow::{WorkflowInvocation, invoke as invoke_workflow};
pub 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);
context.workflows.reload();
set_runtime_status(
Some(tx),
RuntimeStatusLevel::Info,
"skills and workflows reloaded",
);
sync_dashboard_state(context, tx, sleep_status, None);
}
DashboardControlCommand::RunWorkflow { workflow_id, input } => {
set_runtime_status(
Some(tx),
RuntimeStatusLevel::Info,
format!("running workflow `{workflow_id}`"),
);
sync_dashboard_state(context, tx, sleep_status, None);
let result = invoke_workflow(context, WorkflowInvocation { workflow_id, input }).await;
match result {
Ok(result) => {
let level = match result.status {
crate::workflow::WorkflowInvocationStatus::Completed
| crate::workflow::WorkflowInvocationStatus::Running => {
RuntimeStatusLevel::Info
}
crate::workflow::WorkflowInvocationStatus::Failed
| crate::workflow::WorkflowInvocationStatus::Interrupted => {
RuntimeStatusLevel::Error
}
};
crate::runtime::runtime_loop::append_workflow_activity_event(
context, tx, &result,
);
set_runtime_status(
Some(tx),
level,
format!("workflow `{}`: {}", result.workflow_id, result.message),
);
}
Err(err) => set_runtime_status(
Some(tx),
RuntimeStatusLevel::Error,
format!("workflow invocation failed: {err}"),
),
}
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 =
context
.dashboard_history
.as_ref()
.map_or(0, |history| match history.clear_all() {
Ok(count) => count,
Err(err) => {
tracing::error!(
"failed to clear dashboard activity history during /clear: {err:?}"
);
0
}
});
if let Some(session) = context.active_skill_run.as_mut() {
session.final_summary = "abandoned by dashboard /clear".to_string();
}
context.queue_active_skill_run_for_flush(crate::context::SkillRunOutcome::Abandoned);
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.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_events.clear();
state.live_activity_events.clear();
});
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 failed_events = crate::runtime::runtime_loop::interrupt_active_runtime_turn(
context,
"dashboard interrupt",
);
set_runtime_status(
Some(tx),
RuntimeStatusLevel::Info,
format!("runtime turn interrupted (events={failed_events})"),
);
} else {
set_runtime_status(
Some(tx),
RuntimeStatusLevel::Info,
"no runtime turn to interrupt",
);
}
sync_dashboard_state(context, tx, sleep_status, None);
}
}
}