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 {
|
||||
// 传入 id 但旧状态中没有 → 按新 item 处理
|
||||
if new_status != TodoStatus::Pending {
|
||||
if !is_valid_new_status(&new_status) {
|
||||
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
|
||||
));
|
||||
continue;
|
||||
@ -291,10 +291,10 @@ impl Tool for TodoWriteTool {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 新 item(无 id)— 必须是 pending
|
||||
if new_status != TodoStatus::Pending {
|
||||
// 新 item(无 id)— 允许 pending 或 in_progress
|
||||
if !is_valid_new_status(&new_status) {
|
||||
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
|
||||
));
|
||||
continue;
|
||||
@ -423,6 +423,11 @@ pub(crate) fn scope_key_from_context(context: &ToolContext) -> Option<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> {
|
||||
if old.is_terminal() {
|
||||
@ -694,7 +699,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[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 context = test_context();
|
||||
|
||||
@ -711,7 +716,49 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
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]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user