package com.claudecode.tool.impl;
import com.claudecode.tool.Tool;
import com.claudecode.tool.ToolContext;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 待办任务工具 —— 对应 claude-code/src/tools/TodoWriteTool。
*
* 管理 AI 工作过程中的待办事项列表,支持创建、更新、完成和删除任务。
* 任务存储在内存中(ToolContext 的共享状态中),生命周期与会话一致。
*/
public class TodoWriteTool implements Tool {
private static final String TODOS_KEY = "__todos__";
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public String name() {
return "TodoWrite";
}
@Override
public String description() {
return """
Manage a todo list for tracking tasks during the conversation. \
Supports operations: add, update, complete, delete, list. \
Use this to track multi-step tasks, record progress, and organize work.""";
}
@Override
public String inputSchema() {
return """
{
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "update", "complete", "delete", "list"],
"description": "The operation to perform"
},
"id": {
"type": "string",
"description": "Task ID (required for update/complete/delete)"
},
"title": {
"type": "string",
"description": "Task title (required for add)"
},
"description": {
"type": "string",
"description": "Task description (optional)"
},
"status": {
"type": "string",
"enum": ["pending", "in_progress", "done", "blocked"],
"description": "Task status (for update)"
},
"priority": {
"type": "string",
"enum": ["high", "medium", "low"],
"description": "Task priority (default: medium)"
}
},
"required": ["operation"]
}""";
}
@Override
@SuppressWarnings("unchecked")
public String execute(Map input, ToolContext context) {
String operation = (String) input.get("operation");
if (operation == null) {
return "Error: 'operation' is required";
}
// 从 ToolContext 获取或初始化 todo 列表
Map todos = context.getOrDefault(TODOS_KEY, null);
if (todos == null) {
todos = new ConcurrentHashMap<>();
context.set(TODOS_KEY, todos);
}
return switch (operation) {
case "add" -> addTodo(input, todos);
case "update" -> updateTodo(input, todos);
case "complete" -> completeTodo(input, todos);
case "delete" -> deleteTodo(input, todos);
case "list" -> listTodos(todos);
default -> "Error: Unknown operation '" + operation + "'. Use: add, update, complete, delete, list";
};
}
private String addTodo(Map input, Map todos) {
String title = (String) input.get("title");
if (title == null || title.isBlank()) {
return "Error: 'title' is required for add operation";
}
String id = generateId();
String description = (String) input.getOrDefault("description", "");
String priority = (String) input.getOrDefault("priority", "medium");
TodoItem item = new TodoItem(id, title, description, "pending", priority, LocalDateTime.now());
todos.put(id, item);
return "✅ Task added:\n" + formatItem(item);
}
private String updateTodo(Map input, Map todos) {
String id = (String) input.get("id");
if (id == null) {
return "Error: 'id' is required for update operation";
}
TodoItem item = todos.get(id);
if (item == null) {
return "Error: Task not found: " + id;
}
// 更新字段
String title = (String) input.getOrDefault("title", item.title());
String description = (String) input.getOrDefault("description", item.description());
String status = (String) input.getOrDefault("status", item.status());
String priority = (String) input.getOrDefault("priority", item.priority());
TodoItem updated = new TodoItem(id, title, description, status, priority, item.createdAt());
todos.put(id, updated);
return "✏️ Task updated:\n" + formatItem(updated);
}
private String completeTodo(Map input, Map todos) {
String id = (String) input.get("id");
if (id == null) {
return "Error: 'id' is required for complete operation";
}
TodoItem item = todos.get(id);
if (item == null) {
return "Error: Task not found: " + id;
}
TodoItem completed = new TodoItem(id, item.title(), item.description(), "done", item.priority(), item.createdAt());
todos.put(id, completed);
return "✅ Task completed: " + item.title();
}
private String deleteTodo(Map input, Map todos) {
String id = (String) input.get("id");
if (id == null) {
return "Error: 'id' is required for delete operation";
}
TodoItem removed = todos.remove(id);
if (removed == null) {
return "Error: Task not found: " + id;
}
return "🗑️ Task deleted: " + removed.title();
}
private String listTodos(Map todos) {
if (todos.isEmpty()) {
return "📋 No tasks. Use 'add' operation to create one.";
}
// 按状态分组,优先级排序
Map> byStatus = todos.values().stream()
.sorted(Comparator.comparingInt(this::priorityOrder))
.collect(Collectors.groupingBy(TodoItem::status, LinkedHashMap::new, Collectors.toList()));
StringBuilder sb = new StringBuilder();
sb.append("📋 Task List (").append(todos.size()).append(" tasks)\n");
sb.append("━".repeat(40)).append("\n");
for (Map.Entry> entry : byStatus.entrySet()) {
String statusIcon = statusIcon(entry.getKey());
sb.append("\n").append(statusIcon).append(" ").append(entry.getKey().toUpperCase()).append(":\n");
for (TodoItem item : entry.getValue()) {
sb.append(formatItem(item)).append("\n");
}
}
return sb.toString().stripTrailing();
}
private String formatItem(TodoItem item) {
String priorityIcon = switch (item.priority()) {
case "high" -> "🔴";
case "medium" -> "🟡";
case "low" -> "🟢";
default -> "⚪";
};
return String.format(" %s [%s] %s - %s (%s)",
priorityIcon, item.id(), item.title(),
item.description().isEmpty() ? "(no description)" : item.description(),
item.createdAt().format(FMT));
}
private int priorityOrder(TodoItem item) {
return switch (item.priority()) {
case "high" -> 0;
case "medium" -> 1;
case "low" -> 2;
default -> 3;
};
}
private String statusIcon(String status) {
return switch (status) {
case "pending" -> "⏳";
case "in_progress" -> "🔄";
case "done" -> "✅";
case "blocked" -> "🚫";
default -> "❓";
};
}
/** 生成短 ID(4 位十六进制) */
private String generateId() {
return UUID.randomUUID().toString().substring(0, 8);
}
/** 不可变任务数据记录 */
record TodoItem(String id, String title, String description, String status,
String priority, LocalDateTime createdAt) {
}
@Override
public String activityDescription(Map input) {
String op = (String) input.getOrDefault("operation", "managing");
return "📋 Todo: " + op;
}
}