feat: add TaskStopTool, TaskOutputTool, and SleepTool

- TaskStopTool: cancel running tasks via TaskManager.cancelTask()
- TaskOutputTool: retrieve task results with status-aware formatting
- SleepTool: Thread.sleep() with 5-minute max and interrupt support
- Registered all 3 tools in AppConfig (total: 22 tools)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
abel533 1 month ago
parent 08ebc72416
commit d9b8c795b6
  1. 6
      src/main/java/com/claudecode/config/AppConfig.java
  2. 86
      src/main/java/com/claudecode/tool/impl/SleepTool.java
  3. 112
      src/main/java/com/claudecode/tool/impl/TaskOutputTool.java
  4. 93
      src/main/java/com/claudecode/tool/impl/TaskStopTool.java

@ -104,8 +104,12 @@ public class AppConfig {
new TaskGetTool(),
new TaskListTool(),
new TaskUpdateTool(),
new TaskStopTool(),
new TaskOutputTool(),
// P2: 配置工具
new ConfigTool()
new ConfigTool(),
// P2: 实用工具
new SleepTool()
);
// P2: 注册 MCP 工具桥接(将远程 MCP 工具映射为本地工具)

@ -0,0 +1,86 @@
package com.claudecode.tool.impl;
import com.claudecode.tool.Tool;
import com.claudecode.tool.ToolContext;
import java.util.Map;
/**
* Sleep 工具 对应 claude-code/src/tools/SleepTool
* <p>
* 等待指定时长用于暂停操作等待外部进程或用户要求休眠
* 支持通过中断取消等待
*/
public class SleepTool implements Tool {
private static final long MAX_DURATION_MS = 300_000; // 5 minutes max
@Override
public String name() {
return "Sleep";
}
@Override
public String description() {
return """
Wait for a specified duration in milliseconds. The user can interrupt the sleep at \
any time. Use this when:
- The user tells you to sleep or rest
- You have nothing to do and are waiting for something
- You need to wait for an external process to complete
Maximum duration: 300000ms (5 minutes).""";
}
@Override
public String inputSchema() {
return """
{
"type": "object",
"properties": {
"duration_ms": {
"type": "integer",
"description": "Duration to sleep in milliseconds (max: 300000)"
}
},
"required": ["duration_ms"]
}""";
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public String execute(Map<String, Object> input, ToolContext context) {
Number durationNum = (Number) input.get("duration_ms");
if (durationNum == null) {
return "Error: 'duration_ms' is required";
}
long durationMs = durationNum.longValue();
if (durationMs <= 0) {
return "Error: duration_ms must be positive";
}
if (durationMs > MAX_DURATION_MS) {
durationMs = MAX_DURATION_MS;
}
long startTime = System.currentTimeMillis();
try {
Thread.sleep(durationMs);
long elapsed = System.currentTimeMillis() - startTime;
return String.format("Slept for %d ms", elapsed);
} catch (InterruptedException e) {
long elapsed = System.currentTimeMillis() - startTime;
Thread.currentThread().interrupt();
return String.format("Sleep interrupted after %d ms (requested %d ms)", elapsed, durationMs);
}
}
@Override
public String activityDescription(Map<String, Object> input) {
Number ms = (Number) input.getOrDefault("duration_ms", 0);
return "💤 Sleeping " + (ms.longValue() / 1000.0) + "s";
}
}

@ -0,0 +1,112 @@
package com.claudecode.tool.impl;
import com.claudecode.core.TaskManager;
import com.claudecode.core.TaskManager.TaskInfo;
import com.claudecode.tool.Tool;
import com.claudecode.tool.ToolContext;
import java.util.Map;
import java.util.Optional;
/**
* TaskOutput 工具 对应 claude-code/src/tools/TaskOutputTool
* <p>
* 获取任务的执行输出/结果当任务完成后可以通过此工具读取其结果
* 对于正在运行的任务返回当前状态信息
*/
public class TaskOutputTool implements Tool {
private static final String TASK_MANAGER_KEY = "TASK_MANAGER";
@Override
public String name() {
return "TaskOutput";
}
@Override
public String description() {
return """
Get the output/result of a task. Use this to retrieve the result of a completed task, \
or to check the current status of a running task. For completed tasks, returns the full \
execution result. For running tasks, returns the current status.""";
}
@Override
public String inputSchema() {
return """
{
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "The ID of the task to get output from"
}
},
"required": ["task_id"]
}""";
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public String execute(Map<String, Object> input, ToolContext context) {
String taskId = (String) input.get("task_id");
if (taskId == null || taskId.isBlank()) {
return "Error: 'task_id' is required";
}
TaskManager taskManager = context.getOrDefault(TASK_MANAGER_KEY, null);
if (taskManager == null) {
return "Error: TaskManager is not available";
}
Optional<TaskInfo> taskOpt = taskManager.getTask(taskId);
if (taskOpt.isEmpty()) {
return "Error: Task not found: " + taskId;
}
TaskInfo task = taskOpt.get();
return switch (task.status()) {
case COMPLETED -> {
String result = task.result();
yield String.format("""
{"task_id": "%s", "status": "COMPLETED", "description": "%s", "result": "%s"}""",
taskId, escapeJson(task.description()),
escapeJson(result != null ? result : "(no output)"));
}
case FAILED -> {
String error = task.result();
yield String.format("""
{"task_id": "%s", "status": "FAILED", "description": "%s", "error": "%s"}""",
taskId, escapeJson(task.description()),
escapeJson(error != null ? error : "(unknown error)"));
}
case CANCELLED -> String.format("""
{"task_id": "%s", "status": "CANCELLED", "description": "%s"}""",
taskId, escapeJson(task.description()));
case RUNNING -> String.format("""
{"task_id": "%s", "status": "RUNNING", "description": "%s", \
"message": "Task is still running. Check back later."}""",
taskId, escapeJson(task.description()));
case PENDING -> String.format("""
{"task_id": "%s", "status": "PENDING", "description": "%s", \
"message": "Task has not started yet."}""",
taskId, escapeJson(task.description()));
};
}
@Override
public String activityDescription(Map<String, Object> input) {
return "📋 Getting output of task " + input.getOrDefault("task_id", "...");
}
private String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
}
}

@ -0,0 +1,93 @@
package com.claudecode.tool.impl;
import com.claudecode.core.TaskManager;
import com.claudecode.core.TaskManager.TaskInfo;
import com.claudecode.tool.Tool;
import com.claudecode.tool.ToolContext;
import java.util.Map;
import java.util.Optional;
/**
* TaskStop 工具 对应 claude-code/src/tools/TaskStopTool
* <p>
* 停止一个正在运行的后台任务通过 TaskManager.cancelTask() 取消任务执行
*/
public class TaskStopTool implements Tool {
private static final String TASK_MANAGER_KEY = "TASK_MANAGER";
@Override
public String name() {
return "TaskStop";
}
@Override
public String description() {
return """
Stop a running background task by its ID. Use this tool when you need to terminate \
a long-running task that is no longer needed, or when a task appears to be stuck. \
Returns a success or failure status. Tasks in terminal states (COMPLETED/FAILED/CANCELLED) \
cannot be stopped.""";
}
@Override
public String inputSchema() {
return """
{
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "The ID of the task to stop"
}
},
"required": ["task_id"]
}""";
}
@Override
public String execute(Map<String, Object> input, ToolContext context) {
String taskId = (String) input.get("task_id");
if (taskId == null || taskId.isBlank()) {
return "Error: 'task_id' is required";
}
TaskManager taskManager = context.getOrDefault(TASK_MANAGER_KEY, null);
if (taskManager == null) {
return "Error: TaskManager is not available";
}
// Check if task exists first
Optional<TaskInfo> taskOpt = taskManager.getTask(taskId);
if (taskOpt.isEmpty()) {
return "Error: Task not found: " + taskId;
}
TaskInfo task = taskOpt.get();
String previousStatus = task.status().name();
boolean cancelled = taskManager.cancelTask(taskId);
if (cancelled) {
return String.format("""
{"status": "stopped", "task_id": "%s", "previous_status": "%s", \
"description": "%s"}""",
taskId, previousStatus, escapeJson(task.description()));
} else {
return String.format(
"Error: Cannot stop task %s — it is already in terminal state: %s",
taskId, previousStatus);
}
}
@Override
public String activityDescription(Map<String, Object> input) {
return "🛑 Stopping task " + input.getOrDefault("task_id", "...");
}
private String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
}
}
Loading…
Cancel
Save