feat: P2全部功能 - MCP协议+插件系统+任务管理+CLI增强

A组 CLI增强:
- DiffRenderer: 彩色diff渲染(行号/stat摘要/unified格式)
- /hooks: 查看已注册Hook列表
- /review: AI代码审查(支持--staged/文件路径)
- /stats: 会话使用统计(tokens/费用/API调用/运行时长)
- /branch: 对话分支管理(save/load/list/delete)
- /rewind: 回退对话历史
- /tag: 对话位置标签
- /security-review: AI安全审查

B组 任务系统:
- TaskManager: 后台任务管理(线程安全/自动执行/手动管理)
- TaskCreate/TaskGet/TaskList/TaskUpdate 4个工具
- ConfigTool: 配置读写工具

C组 MCP协议:
- McpClient: MCP客户端(JSON-RPC 2.0/工具发现/资源读取)
- McpTransport: 传输层接口
- StdioTransport: StdIO传输实现(子进程/异步读写)
- McpManager: 多MCP服务器管理(配置加载/生命周期)
- McpToolBridge: MCP工具桥接为本地Tool
- /mcp: MCP服务器管理命令

D组 插件系统:
- Plugin接口 + PluginContext + PluginManager
- JAR插件加载(ClassLoader隔离/Manifest读取)
- OutputStylePlugin: 内置输出样式插件
- /plugin: 插件管理命令

集成: AppConfig注册全部18工具+28命令

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
liuzh
2026-04-01 22:56:35 +08:00
co-authored by Copilot
parent da26f02498
commit 79736cf16f
27 changed files with 5592 additions and 9 deletions
@@ -0,0 +1,390 @@
package com.claudecode.core;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* 任务管理器 —— 对应 claude-code 中的 TaskCreate / TaskGet / TaskList / TaskUpdate 功能。
* <p>
* 管理后台任务的创建、执行、查询和更新。
* 所有公共方法均线程安全,内部使用 {@link ConcurrentHashMap} 存储任务、
* 使用守护线程池异步执行带有 {@link Callable} 工作体的任务。
* </p>
*
* <h3>两种创建模式</h3>
* <ul>
* <li>{@link #createTask(String, Callable)} —— 自动执行模式:提交后立即在后台线程池中运行</li>
* <li>{@link #createManualTask(String)} —— 手动管理模式:仅创建 PENDING 状态的记录,
* 由外部通过 {@link #updateTask} 驱动状态流转</li>
* </ul>
*/
public class TaskManager {
/* ------------------------------------------------------------------ */
/* 任务状态枚举 */
/* ------------------------------------------------------------------ */
/**
* 任务生命周期状态。
*/
public enum TaskStatus {
/** 已创建,等待执行 */
PENDING,
/** 正在执行 */
RUNNING,
/** 执行成功完成 */
COMPLETED,
/** 执行失败 */
FAILED,
/** 已被取消 */
CANCELLED
}
/* ------------------------------------------------------------------ */
/* 任务信息记录 */
/* ------------------------------------------------------------------ */
/**
* 不可变的任务快照。每次状态变更都会创建新的 {@code TaskInfo} 实例写入映射表,
* 从而保证读取端永远拿到一致的快照。
*
* @param id 任务唯一 IDUUID 前 8 位)
* @param description 任务描述
* @param status 当前状态
* @param result 执行结果(可为 {@code null}
* @param createdAt 创建时间
* @param updatedAt 最后更新时间
* @param metadata 附加元数据(不可变视图)
*/
public record TaskInfo(
String id,
String description,
TaskStatus status,
String result,
Instant createdAt,
Instant updatedAt,
Map<String, String> metadata
) {
/**
* 创建一个更新了状态、结果和时间戳的新快照。
*/
public TaskInfo withStatusAndResult(TaskStatus newStatus, String newResult) {
return new TaskInfo(
id, description, newStatus, newResult,
createdAt, Instant.now(),
metadata
);
}
/**
* 创建一个仅更新了状态和时间戳的新快照。
*/
public TaskInfo withStatus(TaskStatus newStatus) {
return withStatusAndResult(newStatus, result);
}
}
/* ------------------------------------------------------------------ */
/* 内部状态 */
/* ------------------------------------------------------------------ */
/** 任务存储:taskId → TaskInfo(不可变快照) */
private final ConcurrentHashMap<String, TaskInfo> tasks = new ConcurrentHashMap<>();
/** 自动执行模式下对应的 Future,用于取消 */
private final ConcurrentHashMap<String, Future<?>> futures = new ConcurrentHashMap<>();
/** 守护线程池,用于执行自动任务 */
private final ExecutorService executor = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r, "task-worker-" + Thread.currentThread().threadId());
t.setDaemon(true);
return t;
});
/* ------------------------------------------------------------------ */
/* 创建任务 */
/* ------------------------------------------------------------------ */
/**
* 创建并自动执行一个后台任务。
* <p>
* 任务会立即被提交到线程池执行;执行完成后状态自动变为 COMPLETED 或 FAILED。
* </p>
*
* @param description 任务描述
* @param work 要执行的工作体
* @return 生成的任务 IDUUID 前 8 位)
* @throws NullPointerException 若 description 或 work 为 null
*/
public String createTask(String description, Callable<String> work) {
Objects.requireNonNull(description, "任务描述不能为 null");
Objects.requireNonNull(work, "任务工作体不能为 null");
String taskId = generateId();
Instant now = Instant.now();
TaskInfo info = new TaskInfo(
taskId, description, TaskStatus.PENDING, null,
now, now, Collections.emptyMap()
);
tasks.put(taskId, info);
// 提交到线程池异步执行
Future<?> future = executor.submit(() -> {
// 标记为 RUNNING
tasks.computeIfPresent(taskId, (id, old) -> old.withStatus(TaskStatus.RUNNING));
try {
String result = work.call();
// 标记为 COMPLETED
tasks.computeIfPresent(taskId, (id, old) ->
old.withStatusAndResult(TaskStatus.COMPLETED, result));
} catch (Exception e) {
// 标记为 FAILED,记录异常信息
String errorMsg = e.getClass().getSimpleName() + ": " + e.getMessage();
tasks.computeIfPresent(taskId, (id, old) ->
old.withStatusAndResult(TaskStatus.FAILED, errorMsg));
}
});
futures.put(taskId, future);
return taskId;
}
/**
* 创建并自动执行一个带元数据的后台任务。
*
* @param description 任务描述
* @param work 要执行的工作体
* @param metadata 附加元数据
* @return 生成的任务 ID
*/
public String createTask(String description, Callable<String> work,
Map<String, String> metadata) {
Objects.requireNonNull(metadata, "元数据不能为 null");
String taskId = createTask(description, work);
// 把元数据补充进去(创建时尚处于 PENDING/RUNNING 初期阶段)
tasks.computeIfPresent(taskId, (id, old) -> new TaskInfo(
old.id(), old.description(), old.status(), old.result(),
old.createdAt(), old.updatedAt(),
Collections.unmodifiableMap(new LinkedHashMap<>(metadata))
));
return taskId;
}
/**
* 创建一个手动管理的任务(不自动执行)。
* <p>
* 初始状态为 PENDING,需要外部通过 {@link #updateTask} 手动驱动状态变化。
* </p>
*
* @param description 任务描述
* @return 生成的任务 ID
*/
public String createManualTask(String description) {
return createManualTask(description, Collections.emptyMap());
}
/**
* 创建一个带元数据的手动管理任务。
*
* @param description 任务描述
* @param metadata 附加元数据
* @return 生成的任务 ID
*/
public String createManualTask(String description, Map<String, String> metadata) {
Objects.requireNonNull(description, "任务描述不能为 null");
String taskId = generateId();
Instant now = Instant.now();
Map<String, String> metaCopy = (metadata == null || metadata.isEmpty())
? Collections.emptyMap()
: Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
TaskInfo info = new TaskInfo(
taskId, description, TaskStatus.PENDING, null,
now, now, metaCopy
);
tasks.put(taskId, info);
return taskId;
}
/* ------------------------------------------------------------------ */
/* 查询任务 */
/* ------------------------------------------------------------------ */
/**
* 获取指定任务的信息快照。
*
* @param taskId 任务 ID
* @return 包含任务信息的 Optional,不存在时返回 empty
*/
public Optional<TaskInfo> getTask(String taskId) {
if (taskId == null || taskId.isBlank()) {
return Optional.empty();
}
return Optional.ofNullable(tasks.get(taskId));
}
/**
* 列出所有任务。
*
* @return 任务列表(按创建时间升序)
*/
public List<TaskInfo> listTasks() {
return listTasks(null);
}
/**
* 列出任务,可按状态过滤。
*
* @param statusFilter 状态过滤器,{@code null} 表示不过滤
* @return 符合条件的任务列表(按创建时间升序)
*/
public List<TaskInfo> listTasks(TaskStatus statusFilter) {
return tasks.values().stream()
.filter(t -> statusFilter == null || t.status() == statusFilter)
.sorted(Comparator.comparing(TaskInfo::createdAt))
.collect(Collectors.toUnmodifiableList());
}
/* ------------------------------------------------------------------ */
/* 更新任务 */
/* ------------------------------------------------------------------ */
/**
* 更新任务的状态和结果。
*
* @param taskId 任务 ID
* @param newStatus 新状态
* @param result 执行结果(可为 null
* @return 如果任务存在且更新成功返回 {@code true};否则 {@code false}
*/
public boolean updateTask(String taskId, TaskStatus newStatus, String result) {
if (taskId == null || newStatus == null) {
return false;
}
TaskInfo existing = tasks.get(taskId);
if (existing == null) {
return false;
}
// 不允许对已终态(COMPLETED / FAILED / CANCELLED)的任务再次更新
if (isTerminal(existing.status())) {
return false;
}
tasks.computeIfPresent(taskId, (id, old) ->
old.withStatusAndResult(newStatus, result));
return true;
}
/* ------------------------------------------------------------------ */
/* 取消任务 */
/* ------------------------------------------------------------------ */
/**
* 取消指定任务。
* <p>
* 如果任务由线程池执行且尚未完成,会尝试中断执行线程。
* </p>
*
* @param taskId 任务 ID
* @return 如果任务存在且成功取消返回 {@code true}
*/
public boolean cancelTask(String taskId) {
if (taskId == null) {
return false;
}
TaskInfo existing = tasks.get(taskId);
if (existing == null) {
return false;
}
// 已处于终态则无需取消
if (isTerminal(existing.status())) {
return false;
}
// 尝试中断线程池中正在运行的 Future
Future<?> future = futures.remove(taskId);
if (future != null && !future.isDone()) {
future.cancel(true);
}
tasks.computeIfPresent(taskId, (id, old) ->
old.withStatus(TaskStatus.CANCELLED));
return true;
}
/* ------------------------------------------------------------------ */
/* 汇总信息 */
/* ------------------------------------------------------------------ */
/**
* 返回当前所有任务的汇总字符串,适合用于 UI 展示。
*
* @return 格式化的汇总信息
*/
public String getSummary() {
if (tasks.isEmpty()) {
return "当前没有任何任务。";
}
Map<TaskStatus, Long> counts = tasks.values().stream()
.collect(Collectors.groupingBy(TaskInfo::status, Collectors.counting()));
StringBuilder sb = new StringBuilder();
sb.append("任务汇总 (共 ").append(tasks.size()).append(" 个):\n");
for (TaskStatus status : TaskStatus.values()) {
long count = counts.getOrDefault(status, 0L);
if (count > 0) {
sb.append(" ").append(status.name()).append(": ").append(count).append('\n');
}
}
return sb.toString().stripTrailing();
}
/* ------------------------------------------------------------------ */
/* 生命周期管理 */
/* ------------------------------------------------------------------ */
/**
* 关闭执行器。等待最多 5 秒让正在运行的任务完成,超时后强制关闭。
*/
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
/* ------------------------------------------------------------------ */
/* 内部辅助方法 */
/* ------------------------------------------------------------------ */
/**
* 生成任务 ID:取 UUID 前 8 位。
*/
private String generateId() {
return UUID.randomUUID().toString().substring(0, 8);
}
/**
* 判断状态是否为终态。
*/
private boolean isTerminal(TaskStatus status) {
return status == TaskStatus.COMPLETED
|| status == TaskStatus.FAILED
|| status == TaskStatus.CANCELLED;
}
}