use crate::command::Command; use crate::command::context::CommandContext; use crate::command::handler::{CommandHandler, CommandMetadata}; use crate::command::response::{CommandError, CommandResponse}; use crate::protocol::{SchedulerJobSessionLookup, SchedulerJobSummary}; use crate::storage::SessionStore; use async_trait::async_trait; use std::sync::Arc; pub struct ListSchedulerJobsCommandHandler { store: Arc, } impl ListSchedulerJobsCommandHandler { pub fn new(store: Arc) -> Self { Self { store } } } #[async_trait] impl CommandHandler for ListSchedulerJobsCommandHandler { fn can_handle(&self, cmd: &Command) -> bool { matches!(cmd, Command::ListSchedulerJobs) } fn metadata(&self) -> Option { Some(CommandMetadata { name: "list_scheduler_jobs", description: "列出所有定时任务", usage: "/list_scheduler_jobs", }) } async fn handle( &self, _cmd: Command, ctx: CommandContext, ) -> Result { let records = self .store .list_scheduler_jobs(false) .map_err(|e| CommandError::new("LIST_JOBS_ERROR", e.to_string()))?; let summaries: Vec = records .into_iter() .map(|r| { let session_lookup = build_session_lookup(&r); SchedulerJobSummary { id: r.id, kind: r.kind, schedule: r.schedule, enabled: r.enabled, state: r.state.as_str().to_string(), last_status: r.last_status.map(|s| s.as_str().to_string()), last_error: r.last_error, run_count: r.run_count, max_runs: r.max_runs, last_fired_at: r.last_fired_at, next_fire_at: r.next_fire_at, created_at: r.created_at, session_lookup, } }) .collect(); let jobs_json = serde_json::to_string(&summaries) .map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?; Ok(CommandResponse::success(ctx.request_id).with_metadata("scheduler_jobs", &jobs_json)) } } /// 从 job 的 target_json 推导 session_lookup。 /// 只有 agent_task / silent_agent_task 才有执行对话可查看。 fn build_session_lookup( r: &crate::storage::SchedulerJobRecord, ) -> Option { let target: serde_json::Value = r.target.clone(); match r.kind.as_str() { "agent_task" => { let channel = target.get("channel")?.as_str()?.to_string(); let chat_id = target.get("chat_id")?.as_str()?.to_string(); Some(SchedulerJobSessionLookup { channel, chat_id }) } "silent_agent_task" => { let channel = target.get("channel")?.as_str()?.to_string(); // silent_agent_task 使用虚拟 chat_id: scheduler/{job_id} let chat_id = format!("scheduler/{}", r.id); Some(SchedulerJobSessionLookup { channel, chat_id }) } _ => None, // internal_event / outbound_message 无执行对话 } }