refactor(todo): 重构待办事项管理逻辑及更新状态规则
- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段 - 强制每个任务都必须有唯一 id,且由用户负责生成 - 修改合并模式逻辑,merge=true 下保留未提及的旧任务 - 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress) - 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled - 优化状态转换校验,允许特定状态间合法切换 - 简化任务变更消息,移除详细的新增/更新/移除统计 - 更新文档和示例,明确 id 必须由用户生成和使用 - 修复和补充测试,增强状态转换和合并模式验证 - 调整任务时间戳生成逻辑,统一使用当前时间及索引 - 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
This commit is contained in:
parent
02172b6065
commit
229221aab1
@ -76,9 +76,6 @@ impl CommandHandler for ListTodosCommandHandler {
|
|||||||
id: r.id,
|
id: r.id,
|
||||||
content: r.content,
|
content: r.content,
|
||||||
status: r.status,
|
status: r.status,
|
||||||
priority: r.priority,
|
|
||||||
created_at: r.created_at,
|
|
||||||
updated_at: r.updated_at,
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@ -141,9 +141,15 @@ impl BusToolCallEmitter {
|
|||||||
|
|
||||||
let topic_id = self.metadata.get("topic_id").filter(|t| !t.is_empty()).cloned();
|
let topic_id = self.metadata.get("topic_id").filter(|t| !t.is_empty()).cloned();
|
||||||
|
|
||||||
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs() as i64;
|
||||||
|
|
||||||
let records: Vec<crate::storage::TodoRecord> = todos_array
|
let records: Vec<crate::storage::TodoRecord> = todos_array
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| {
|
.enumerate()
|
||||||
|
.filter_map(|(idx, item)| {
|
||||||
Some(crate::storage::TodoRecord {
|
Some(crate::storage::TodoRecord {
|
||||||
id: item.get("id")?.as_str()?.to_string(),
|
id: item.get("id")?.as_str()?.to_string(),
|
||||||
scope_key: scope_key.clone(),
|
scope_key: scope_key.clone(),
|
||||||
@ -151,9 +157,9 @@ impl BusToolCallEmitter {
|
|||||||
topic_id: topic_id.clone(),
|
topic_id: topic_id.clone(),
|
||||||
content: item.get("content")?.as_str()?.to_string(),
|
content: item.get("content")?.as_str()?.to_string(),
|
||||||
status: item.get("status")?.as_str()?.to_string(),
|
status: item.get("status")?.as_str()?.to_string(),
|
||||||
priority: item.get("priority")?.as_str()?.to_string(),
|
priority: "medium".to_string(),
|
||||||
created_at: item.get("created_at")?.as_i64()?,
|
created_at: now + idx as i64,
|
||||||
updated_at: item.get("updated_at")?.as_i64()?,
|
updated_at: now,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const TODO_WRITE_INSTRUCTIONS: &str = r#"
|
|||||||
|
|
||||||
### merge 参数
|
### merge 参数
|
||||||
- `merge: false`(默认):全量替换 — 只传入需要追踪的 todo,不在列表中的项将被移除
|
- `merge: false`(默认):全量替换 — 只传入需要追踪的 todo,不在列表中的项将被移除
|
||||||
- `merge: true`(推荐):增量更新 — 只传入需要添加或更新的项,未提及的项保持不变。**绝大多数情况应该使用 merge=true,这样你不需要记住所有 id**
|
- `merge: true`(推荐):增量更新 — 只传入需要添加或更新的项,未提及的项保持不变。**绝大多数情况应该使用 merge=true**
|
||||||
|
|
||||||
### 状态语义
|
### 状态语义
|
||||||
- `pending` — 尚未开始
|
- `pending` — 尚未开始
|
||||||
@ -39,33 +39,34 @@ const TODO_WRITE_INSTRUCTIONS: &str = r#"
|
|||||||
### 核心规则
|
### 核心规则
|
||||||
1. 同一时间只能有一个任务处于 `in_progress` 状态
|
1. 同一时间只能有一个任务处于 `in_progress` 状态
|
||||||
2. 必须先完成当前 `in_progress` 的任务,再开始下一个
|
2. 必须先完成当前 `in_progress` 的任务,再开始下一个
|
||||||
3. `completed` 和 `cancelled` 是终端状态,已完成的项不能被重新激活
|
3. `completed` 和 `cancelled` 的项可以重新激活(改回 `in_progress` 或 `pending`),用于任务返工或恢复
|
||||||
4. 不要先标记 completed 再去实际执行 — 先完成工作,再标记
|
4. `in_progress` 不能退回 `pending`,应直接标记为 `completed` 或 `cancelled`
|
||||||
5. `content` 字段保持简洁、可执行
|
5. 不要先标记 completed 再去实际执行 — 先完成工作,再标记
|
||||||
6. **更新已有任务必须传 `id`**。新任务不传 id(工具会自动生成),更新状态时必须传 id。id 可以从之前 todo_write 返回的 `current_todos` 中获取
|
6. `content` 字段保持简洁、可执行
|
||||||
|
7. **每个任务都必须传 `id`**。新任务由你生成一个短随机字符串作为 id(如 `"r9Tg8Kq2"`),更新任务时使用相同的 id。id 可以从之前 todo_write 返回的 `current_todos` 中获取
|
||||||
|
|
||||||
### 使用范例
|
### 使用范例
|
||||||
|
|
||||||
创建新任务(新任务必须 pending 或 in_progress,不传 id):
|
创建新任务(生成随机 id):
|
||||||
```json
|
```json
|
||||||
{"merge": true, "todos": [{"content": "修复登录 bug", "status": "in_progress"}]}
|
{"merge": true, "todos": [{"id": "aB3kLm9x", "content": "修复登录 bug", "status": "in_progress"}]}
|
||||||
```
|
```
|
||||||
|
|
||||||
追加新任务:
|
追加新任务:
|
||||||
```json
|
```json
|
||||||
{"merge": true, "todos": [{"content": "补充测试", "status": "pending"}]}
|
{"merge": true, "todos": [{"id": "pQ7nWy2z", "content": "补充测试", "status": "pending"}]}
|
||||||
```
|
```
|
||||||
|
|
||||||
更新已有任务(**必须传 id**,从上次返回的 current_todos 中取得):
|
更新已有任务(使用创建时的 id):
|
||||||
```json
|
```json
|
||||||
{"merge": true, "todos": [{"id": "abc-123", "content": "修复登录 bug", "status": "completed"}]}
|
{"merge": true, "todos": [{"id": "aB3kLm9x", "content": "修复登录 bug", "status": "completed"}]}
|
||||||
```
|
```
|
||||||
|
|
||||||
同时更新多项:
|
同时更新多项:
|
||||||
```json
|
```json
|
||||||
{"merge": true, "todos": [
|
{"merge": true, "todos": [
|
||||||
{"id": "abc-123", "content": "修复登录 bug", "status": "completed"},
|
{"id": "aB3kLm9x", "content": "修复登录 bug", "status": "completed"},
|
||||||
{"content": "代码审查", "status": "in_progress"}
|
{"id": "pQ7nWy2z", "content": "补充测试", "status": "in_progress"}
|
||||||
]}
|
]}
|
||||||
```
|
```
|
||||||
"#;
|
"#;
|
||||||
|
|||||||
@ -88,9 +88,6 @@ pub struct TodoItemSummary {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub priority: String,
|
|
||||||
pub created_at: i64,
|
|
||||||
pub updated_at: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -57,20 +57,20 @@ impl Default for InMemoryTaskRepository {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TaskRepository for InMemoryTaskRepository {
|
impl TaskRepository for InMemoryTaskRepository {
|
||||||
async fn save_task_session(&self, session: &TaskSession) -> Result<(), StorageError> {
|
async fn save_task_session(&self, session: &TaskSession) -> Result<(), StorageError> {
|
||||||
tracing::warn!(
|
tracing::debug!(
|
||||||
task_id = %session.id,
|
task_id = %session.id,
|
||||||
session_id = %session.session_id,
|
session_id = %session.session_id,
|
||||||
state = ?session.state,
|
state = ?session.state,
|
||||||
"REPO_SAVE: Saving task session"
|
"Saving task session"
|
||||||
);
|
);
|
||||||
self.sessions
|
self.sessions
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(session.id.clone(), session.clone());
|
.insert(session.id.clone(), session.clone());
|
||||||
tracing::warn!(
|
tracing::debug!(
|
||||||
task_id = %session.id,
|
task_id = %session.id,
|
||||||
total_tasks = self.sessions.read().unwrap().len(),
|
total_tasks = self.sessions.read().unwrap().len(),
|
||||||
"REPO_SAVE: Task session saved, current repository size"
|
"Task session saved, current repository size"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -79,11 +79,11 @@ impl TaskRepository for InMemoryTaskRepository {
|
|||||||
let sessions = self.sessions.read().unwrap();
|
let sessions = self.sessions.read().unwrap();
|
||||||
let total = sessions.len();
|
let total = sessions.len();
|
||||||
let keys: Vec<&str> = sessions.keys().map(|k| k.as_str()).collect();
|
let keys: Vec<&str> = sessions.keys().map(|k| k.as_str()).collect();
|
||||||
tracing::warn!(
|
tracing::debug!(
|
||||||
lookup_task_id = %task_id,
|
lookup_task_id = %task_id,
|
||||||
total_tasks = total,
|
total_tasks = total,
|
||||||
all_keys = ?keys,
|
all_keys = ?keys,
|
||||||
"REPO_LOOKUP: Looking up task session"
|
"Looking up task session"
|
||||||
);
|
);
|
||||||
Ok(sessions.get(task_id).cloned())
|
Ok(sessions.get(task_id).cloned())
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user