- 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
parent
08ebc72416
commit
d9b8c795b6
@ -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…
Reference in new issue