配置: - 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 不一致,已对齐
96 lines
3.3 KiB
Rust
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 无执行对话
|
|
}
|
|
}
|