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>
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user