diff --git a/src/main/java/com/claudecode/config/AppConfig.java b/src/main/java/com/claudecode/config/AppConfig.java
index 1968526..deea100 100644
--- a/src/main/java/com/claudecode/config/AppConfig.java
+++ b/src/main/java/com/claudecode/config/AppConfig.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 工具映射为本地工具)
diff --git a/src/main/java/com/claudecode/tool/impl/SleepTool.java b/src/main/java/com/claudecode/tool/impl/SleepTool.java
new file mode 100644
index 0000000..63cb7ac
--- /dev/null
+++ b/src/main/java/com/claudecode/tool/impl/SleepTool.java
@@ -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。
+ *
+ * 等待指定时长。用于暂停操作、等待外部进程、或用户要求休眠。
+ * 支持通过中断取消等待。
+ */
+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 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 input) {
+ Number ms = (Number) input.getOrDefault("duration_ms", 0);
+ return "💤 Sleeping " + (ms.longValue() / 1000.0) + "s";
+ }
+}
diff --git a/src/main/java/com/claudecode/tool/impl/TaskOutputTool.java b/src/main/java/com/claudecode/tool/impl/TaskOutputTool.java
new file mode 100644
index 0000000..f014dc1
--- /dev/null
+++ b/src/main/java/com/claudecode/tool/impl/TaskOutputTool.java
@@ -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。
+ *
+ * 获取任务的执行输出/结果。当任务完成后,可以通过此工具读取其结果。
+ * 对于正在运行的任务,返回当前状态信息。
+ */
+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 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 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 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");
+ }
+}
diff --git a/src/main/java/com/claudecode/tool/impl/TaskStopTool.java b/src/main/java/com/claudecode/tool/impl/TaskStopTool.java
new file mode 100644
index 0000000..bdd5b50
--- /dev/null
+++ b/src/main/java/com/claudecode/tool/impl/TaskStopTool.java
@@ -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。
+ *
+ * 停止一个正在运行的后台任务。通过 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 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 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 input) {
+ return "🛑 Stopping task " + input.getOrDefault("task_id", "...");
+ }
+
+ private String escapeJson(String s) {
+ if (s == null) return "";
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
+ }
+}