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; } }