feat: 添加 ListTodos 命令处理器,支持列出当前待办事项列表
This commit is contained in:
parent
3f32079f92
commit
750eed7326
74
src/command/handlers/list_todos.rs
Normal file
74
src/command/handlers/list_todos.rs
Normal file
@ -0,0 +1,74 @@
|
||||
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<SessionStore>,
|
||||
}
|
||||
|
||||
impl ListTodosCommandHandler {
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for ListTodosCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::ListTodos)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "list_todos",
|
||||
description: "列出当前 Todo 列表",
|
||||
usage: "/list_todos",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
_cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
// 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()))?;
|
||||
|
||||
let summaries: Vec<TodoItemSummary> = 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))
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ pub mod list_channels;
|
||||
pub mod list_memories;
|
||||
pub mod list_scheduler_jobs;
|
||||
pub mod list_skills;
|
||||
pub mod list_todos;
|
||||
pub mod memory_crud;
|
||||
pub mod list_sessions;
|
||||
pub mod list_sessions_by_channel;
|
||||
|
||||
@ -73,6 +73,8 @@ pub enum Command {
|
||||
DeleteMemory { id: String },
|
||||
/// 列出所有技能
|
||||
ListSkills,
|
||||
/// 列出当前 Todo 列表
|
||||
ListTodos,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@ -100,6 +102,7 @@ impl Command {
|
||||
Command::UpdateMemory { .. } => "update_memory",
|
||||
Command::DeleteMemory { .. } => "delete_memory",
|
||||
Command::ListSkills => "list_skills",
|
||||
Command::ListTodos => "list_todos",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,3 +112,7 @@
|
||||
- 默认创建静默任务(silent_agent_task),在独立后台会话中执行,不干扰主对话
|
||||
- 静默模式下如需发送消息给用户,prompt中需显式使用 send_session_message 工具
|
||||
|
||||
## todo工具使用规范
|
||||
|
||||
- 严格按照既定的未完成的todo工作项执行任务,如果工作项不在适用就更新,不得随意遗漏工作项
|
||||
- 禁止将未完成的工作项标记为已完成
|
||||
@ -42,21 +42,30 @@ const TODO_WRITE_INSTRUCTIONS: &str = r#"
|
||||
3. `completed` 和 `cancelled` 是终端状态,已完成的项不能被重新激活
|
||||
4. 不要先标记 completed 再去实际执行 — 先完成工作,再标记
|
||||
5. `content` 字段保持简洁、可执行
|
||||
6. **更新已有任务必须传 `id`**。新任务不传 id(工具会自动生成),更新状态时必须传 id。id 可以从之前 todo_write 返回的 `current_todos` 中获取
|
||||
|
||||
### 使用范例
|
||||
|
||||
开始任务时(merge 模式):
|
||||
创建新任务(新任务必须 pending 或 in_progress,不传 id):
|
||||
```json
|
||||
{"merge": true, "todos": [{"content": "修复登录 bug", "status": "in_progress"}]}
|
||||
```
|
||||
|
||||
发现新任务时:
|
||||
追加新任务:
|
||||
```json
|
||||
{"merge": true, "todos": [{"content": "补充测试", "status": "pending"}]}
|
||||
```
|
||||
|
||||
完成任务时(传入 id + 新状态):
|
||||
更新已有任务(**必须传 id**,从上次返回的 current_todos 中取得):
|
||||
```json
|
||||
{"merge": true, "todos": [{"id": "xxx", "content": "修复登录 bug", "status": "completed"}]}
|
||||
{"merge": true, "todos": [{"id": "abc-123", "content": "修复登录 bug", "status": "completed"}]}
|
||||
```
|
||||
|
||||
同时更新多项:
|
||||
```json
|
||||
{"merge": true, "todos": [
|
||||
{"id": "abc-123", "content": "修复登录 bug", "status": "completed"},
|
||||
{"content": "代码审查", "status": "in_progress"}
|
||||
]}
|
||||
```
|
||||
"#;
|
||||
|
||||
@ -12,6 +12,7 @@ use crate::command::handlers::list_channels::ListChannelsCommandHandler;
|
||||
use crate::command::handlers::list_memories::ListMemoriesCommandHandler;
|
||||
use crate::command::handlers::list_skills::ListSkillsCommandHandler;
|
||||
use crate::command::handlers::list_scheduler_jobs::ListSchedulerJobsCommandHandler;
|
||||
use crate::command::handlers::list_todos::ListTodosCommandHandler;
|
||||
use crate::command::handlers::memory_crud::MemoryCrudCommandHandler;
|
||||
use crate::command::handlers::list_sessions::ListSessionsCommandHandler;
|
||||
use crate::command::handlers::list_sessions_by_channel::ListSessionsByChannelCommandHandler;
|
||||
@ -422,6 +423,8 @@ async fn handle_inbound(
|
||||
router.register(Box::new(ListMemoriesCommandHandler::new(store.clone())));
|
||||
// 注册 list_skills 处理器
|
||||
router.register(Box::new(ListSkillsCommandHandler::new(skills_for_handler)));
|
||||
// 注册 list_todos 处理器
|
||||
router.register(Box::new(ListTodosCommandHandler::new(store.clone())));
|
||||
// 注册 memory_crud 处理器
|
||||
router.register(Box::new(MemoryCrudCommandHandler::new(store.clone())));
|
||||
// 注册 load_chat_messages 处理器
|
||||
@ -519,6 +522,13 @@ async fn handle_inbound(
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 Todo 列表
|
||||
if let Some(todos_json) = response.metadata.get("todos") {
|
||||
if let Ok(todos) = serde_json::from_str::<Vec<crate::protocol::TodoItemSummary>>(todos_json) {
|
||||
let _ = sender.send(WsOutbound::TodoList { todos, scope_key: String::new() }).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理记忆列表
|
||||
if let Some(memories_json) = response.metadata.get("memories") {
|
||||
if let Ok(memories) = serde_json::from_str::<Vec<crate::protocol::MemorySummary>>(memories_json) {
|
||||
|
||||
@ -323,7 +323,7 @@ function App() {
|
||||
}
|
||||
}, [sidebarTab, status, handleCommand, sendMessage, requestSchedulerJobList])
|
||||
|
||||
// 连接就绪时自动拉取记忆和技能列表
|
||||
// 连接就绪时自动拉取记忆、技能和待办列表
|
||||
useEffect(() => {
|
||||
if (status === 'connected') {
|
||||
const memCmd = requestMemoryList()
|
||||
@ -332,8 +332,11 @@ function App() {
|
||||
const skillCmd = requestSkillList()
|
||||
handleCommand(skillCmd)
|
||||
sendMessage({ type: 'command', payload: JSON.stringify(skillCmd) })
|
||||
const todoCmd = requestTodoList()
|
||||
handleCommand(todoCmd)
|
||||
sendMessage({ type: 'command', payload: JSON.stringify(todoCmd) })
|
||||
}
|
||||
}, [status, handleCommand, sendMessage, requestMemoryList, requestSkillList])
|
||||
}, [status, handleCommand, sendMessage, requestMemoryList, requestSkillList, requestTodoList])
|
||||
|
||||
const handleRefreshMemories = useCallback(() => {
|
||||
const cmd = requestMemoryList()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user