use crate::command::context::CommandContext; use crate::command::handler::{CommandHandler, CommandMetadata}; use crate::command::response::{CommandError, CommandResponse}; use crate::command::Command; use crate::protocol::TodoItemSummary; use crate::storage::SessionStore; use async_trait::async_trait; use std::sync::Arc; pub struct ListTodosCommandHandler { store: Arc, } impl ListTodosCommandHandler { pub fn new(store: Arc) -> Self { Self { store } } } #[async_trait] impl CommandHandler for ListTodosCommandHandler { fn can_handle(&self, cmd: &Command) -> bool { matches!(cmd, Command::ListTodos) } fn metadata(&self) -> Option { Some(CommandMetadata { name: "list_todos", description: "列出当前 Todo 列表", usage: "/list_todos", }) } async fn handle( &self, _cmd: Command, ctx: CommandContext, ) -> Result { // scope_key = topic_id.unwrap_or(session_id) let scope_key = ctx .topic_id .as_deref() .filter(|t| !t.is_empty()) .or(ctx.session_id.as_deref()) .ok_or_else(|| { CommandError::new( "MISSING_CONTEXT", "Cannot list todos: no session_id or topic_id in command context", ) })?; let records = self .store .list_todos(scope_key) .map_err(|e| CommandError::new("LIST_TODOS_ERROR", e.to_string()))?; tracing::info!( scope_key = %scope_key, record_count = records.len(), "list_todos handler: reading from store" ); let summaries: Vec = records .into_iter() .map(|r| TodoItemSummary { id: r.id, content: r.content, status: r.status, priority: r.priority, created_at: r.created_at, updated_at: r.updated_at, }) .collect(); let todos_json = serde_json::to_string(&summaries) .map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?; Ok(CommandResponse::success(ctx.request_id) .with_metadata("todos", &todos_json) .with_metadata("todos_scope_key", scope_key)) } }