fix: 新创建 todo 项允许直接设为 in_progress,无需先 pending 再更新
agent 创建 todo 列表时可以将第一个任务直接标为 in_progress, 避免浪费一次工具调用。仍然禁止新项为 completed/cancelled。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
881fcace47
commit
ce6dce81f4
@ -273,9 +273,9 @@ impl Tool for TodoWriteTool {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 传入 id 但旧状态中没有 → 按新 item 处理
|
// 传入 id 但旧状态中没有 → 按新 item 处理
|
||||||
if new_status != TodoStatus::Pending {
|
if !is_valid_new_status(&new_status) {
|
||||||
validation_errors.push(format!(
|
validation_errors.push(format!(
|
||||||
"Item '{}': new items must start as 'pending', got '{}'",
|
"Item '{}': new items must start as 'pending' or 'in_progress', got '{}'",
|
||||||
content, status_str
|
content, status_str
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@ -291,10 +291,10 @@ impl Tool for TodoWriteTool {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新 item(无 id)— 必须是 pending
|
// 新 item(无 id)— 允许 pending 或 in_progress
|
||||||
if new_status != TodoStatus::Pending {
|
if !is_valid_new_status(&new_status) {
|
||||||
validation_errors.push(format!(
|
validation_errors.push(format!(
|
||||||
"Item '{}': new items must start as 'pending', got '{}'",
|
"Item '{}': new items must start as 'pending' or 'in_progress', got '{}'",
|
||||||
content, status_str
|
content, status_str
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@ -423,6 +423,11 @@ pub(crate) fn scope_key_from_context(context: &ToolContext) -> Option<String> {
|
|||||||
tid.or(sid).map(str::to_string)
|
tid.or(sid).map(str::to_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 新创建的 item 允许 pending 或 in_progress,不允许 completed/cancelled
|
||||||
|
fn is_valid_new_status(status: &TodoStatus) -> bool {
|
||||||
|
matches!(status, TodoStatus::Pending | TodoStatus::InProgress)
|
||||||
|
}
|
||||||
|
|
||||||
/// 校验状态转换合法性
|
/// 校验状态转换合法性
|
||||||
fn validate_transition(old: &TodoStatus, new: &TodoStatus) -> Result<(), String> {
|
fn validate_transition(old: &TodoStatus, new: &TodoStatus) -> Result<(), String> {
|
||||||
if old.is_terminal() {
|
if old.is_terminal() {
|
||||||
@ -694,7 +699,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_new_item_must_be_pending() {
|
async fn test_new_item_cannot_be_completed_or_cancelled() {
|
||||||
let tool = TodoWriteTool::new(test_state());
|
let tool = TodoWriteTool::new(test_state());
|
||||||
let context = test_context();
|
let context = test_context();
|
||||||
|
|
||||||
@ -711,7 +716,49 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(!result.success);
|
assert!(!result.success);
|
||||||
assert!(result.error.unwrap().contains("must start as 'pending'"));
|
assert!(result.error.unwrap().contains("new items must start as"));
|
||||||
|
|
||||||
|
let result2 = tool
|
||||||
|
.execute_with_context(
|
||||||
|
&context,
|
||||||
|
json!({
|
||||||
|
"todos": [
|
||||||
|
{"content": "任务B", "status": "cancelled"}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!result2.success);
|
||||||
|
assert!(result2.error.unwrap().contains("new items must start as"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_new_item_can_start_as_in_progress() {
|
||||||
|
let tool = TodoWriteTool::new(test_state());
|
||||||
|
let context = test_context();
|
||||||
|
|
||||||
|
// 直接创建第一个任务为 in_progress — 应该允许
|
||||||
|
let result = tool
|
||||||
|
.execute_with_context(
|
||||||
|
&context,
|
||||||
|
json!({
|
||||||
|
"todos": [
|
||||||
|
{"content": "第一个任务", "status": "in_progress"},
|
||||||
|
{"content": "第二个任务", "status": "pending"},
|
||||||
|
{"content": "第三个任务", "status": "pending"}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
let output: serde_json::Value = serde_json::from_str(&result.output).unwrap();
|
||||||
|
let todos = output["current_todos"].as_array().unwrap();
|
||||||
|
assert_eq!(todos.len(), 3);
|
||||||
|
assert_eq!(todos[0]["status"], "in_progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user