PicoBot/src/command/handlers/list_scheduler_jobs.rs
oudecheng cda14360af chore: 建立工程化基线(rustfmt + clippy + CI + eslint + prettier)
配置:
- rustfmt.toml: 固化 max_width=100 / 4 空格缩进,cargo fmt 全量格式化
- Cargo.toml: 配置 [lints.rust] 与 [lints.clippy] 渐进式规则
- .github/workflows/ci.yml: Rust(fmt+clippy+test) + 前端(eslint+tsc+test) 双平台 CI
- Makefile: 新增 check/fmt/fix 目标,clippy 对齐 --all-targets --all-features
- web: eslint flat config + prettier 配置 + package.json 脚本与依赖
- src/main.rs: loop→while 修复 clippy::never_loop

对抗性审查发现并修复:
- eslint 缺 caughtErrorsIgnorePattern 导致 catch(_) 误报为 error
- 前端 lint 未接入 CI,现已补上 Lint 步骤
- Makefile 与 CI 的 clippy flags 不一致,已对齐
2026-08-03 23:24:02 +08:00

96 lines
3.3 KiB
Rust

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<SessionStore>,
}
impl ListSchedulerJobsCommandHandler {
pub fn new(store: Arc<SessionStore>) -> Self {
Self { store }
}
}
#[async_trait]
impl CommandHandler for ListSchedulerJobsCommandHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::ListSchedulerJobs)
}
fn metadata(&self) -> Option<CommandMetadata> {
Some(CommandMetadata {
name: "list_scheduler_jobs",
description: "列出所有定时任务",
usage: "/list_scheduler_jobs",
})
}
async fn handle(
&self,
_cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
let records = self
.store
.list_scheduler_jobs(false)
.map_err(|e| CommandError::new("LIST_JOBS_ERROR", e.to_string()))?;
let summaries: Vec<SchedulerJobSummary> = 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<SchedulerJobSessionLookup> {
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 无执行对话
}
}