【从0到1构建一个ClaudeAgent】协作-Worktree+任务隔离
·
任务板管 "做什么" 但不管 "在哪做"。解法: 给每个任务一个独立的 git worktree 目录, 用任务 ID 把两边关联起来。"各干各的目录, 互不干扰",任务管目标,,worktree 管目录,按 ID 绑定。
Java实现代码
java
public class WorktreeTaskIsolationSystem {
// --- 新增配置 ---
private static final Path REPO_ROOT = detectRepoRoot(WORKDIR); // Git仓库根目录
private static final Path TASKS_DIR = REPO_ROOT.resolve(".tasks");
private static final Path WORKTREES_DIR = REPO_ROOT.resolve(".worktrees");
private static final Path EVENTS_LOG_PATH = WORKTREES_DIR.resolve("events.jsonl");
// --- 新增:Git 仓库检测 ---
private static Path detectRepoRoot(Path cwd) {
try {
ProcessBuilder pb = new ProcessBuilder("git", "rev-parse", "--show-toplevel");
pb.directory(cwd.toFile());
Process process = pb.start();
boolean finished = process.waitFor(10, TimeUnit.SECONDS);
if (!finished) {
return cwd;
}
String output = new String(process.getInputStream().readAllBytes()).trim();
Path root = Paths.get(output);
return Files.exists(root) ? root : cwd;
// 自动检测:自动发现Git仓库根目录
// 向后兼容:如果不是Git仓库,使用当前目录
} catch (Exception e) {
return cwd;
}
}
// --- 新增:事件总线(EventBus)---
static class EventBus {
private final Path eventLogPath;
public EventBus(Path eventLogPath) {
this.eventLogPath = eventLogPath;
try {
Files.createDirectories(eventLogPath.getParent());
if (!Files.exists(eventLogPath)) {
Files.writeString(eventLogPath, "");
}
} catch (IOException e) {
throw new RuntimeException("Failed to create event bus", e);
}
}
/**
* 发出事件
*/
public void emit(String event, Map<String, Object> task,
Map<String, Object> worktree, String error) {
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("event", event);
payload.put("ts", System.currentTimeMillis() / 1000.0);
payload.put("task", task != null ? task : Map.of());
payload.put("worktree", worktree != null ? worktree : Map.of());
if (error != null) {
payload.put("error", error);
}
// 结构化事件:事件类型、时间戳、关联数据
// 错误跟踪:支持记录操作失败信息
try {
String jsonLine = gson.toJson(payload) + "\n";
Files.writeString(eventLogPath, jsonLine,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
// JSONL格式:每行一个事件,便于流式处理
// 追加写入:支持长时间运行的事件记录
} catch (IOException e) {
System.err.println("Failed to emit event: " + e.getMessage());
}
}
/**
* 列出最近事件
*/
public String listRecent(int limit) {
int n = Math.max(1, Math.min(limit, 200));
try {
List<String> lines = Files.readAllLines(eventLogPath);
List<Map<String, Object>> events = new ArrayList<>();
int start = Math.max(0, lines.size() - n);
for (int i = start; i < lines.size(); i++) {
try {
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> event = gson.fromJson(lines.get(i), type);
events.add(event);
} catch (Exception e) {
Map<String, Object> errorEvent = new HashMap<>();
errorEvent.put("event", "parse_error");
errorEvent.put("raw", lines.get(i));
events.add(errorEvent);
// 容错处理:即使解析失败也记录原始数据
}
}
return gson.toJson(events);
} catch (IOException e) {
return "[]";
}
}
}
// 初始化事件总线
private static final EventBus EVENTS = new EventBus(EVENTS_LOG_PATH);
// --- 新增:任务管理器(支持工作树绑定)---
static class TaskManager {
private final Path tasksDir;
private int nextId = 1;
public TaskManager(Path tasksDir) {
this.tasksDir = tasksDir;
try {
Files.createDirectories(tasksDir);
this.nextId = getMaxId() + 1;
} catch (IOException e) {
throw new RuntimeException("Failed to initialize task manager", e);
}
}
/**
* 绑定任务到工作树
*/
public String bindWorktree(int taskId, String worktree, String owner) throws IOException {
Map<String, Object> task = loadTask(taskId);
task.put("worktree", worktree);
// 任务-工作树关联:建立任务和工作树的双向链接
if (owner != null && !owner.isEmpty()) {
task.put("owner", owner);
}
if ("pending".equals(task.get("status"))) {
task.put("status", "in_progress");
// 状态自动转换:绑定工作树时自动开始任务
}
task.put("updated_at", System.currentTimeMillis() / 1000.0);
saveTask(task);
return gson.toJson(task);
}
/**
* 解绑工作树
*/
public String unbindWorktree(int taskId) throws IOException {
Map<String, Object> task = loadTask(taskId);
task.put("worktree", "");
task.put("updated_at", System.currentTimeMillis() / 1000.0);
saveTask(task);
return gson.toJson(task);
// 解绑机制:支持任务和工作树的解耦
}
/**
* 列出所有任务
*/
public String listAllTasks() throws IOException {
// ... 列出任务逻辑,包含工作树绑定信息
sb.append(String.format("%s #%d: %s%s%s\n",
marker, id, subject, ownerStr, worktreeStr));
// 可视化展示:显示任务状态、所有者、工作树绑定
}
}
// 初始化任务管理器
private static final TaskManager TASKS = new TaskManager(TASKS_DIR);
// --- 新增:工作树管理器(WorktreeManager)---
static class WorktreeManager {
private final Path repoRoot;
private final TaskManager taskManager;
private final EventBus eventBus;
private final Path worktreesDir;
private final Path indexPath;
private final boolean gitAvailable;
public WorktreeManager(Path repoRoot, TaskManager taskManager, EventBus eventBus) {
this.repoRoot = repoRoot;
this.taskManager = taskManager;
this.eventBus = eventBus;
this.worktreesDir = repoRoot.resolve(".worktrees");
this.indexPath = worktreesDir.resolve("index.json");
// 集成架构:工作树管理器与任务管理器、事件总线集成
try {
Files.createDirectories(worktreesDir);
if (!Files.exists(indexPath)) {
Map<String, Object> index = new HashMap<>();
index.put("worktrees", new ArrayList<Map<String, Object>>());
Files.writeString(indexPath, gson.toJson(index));
}
} catch (IOException e) {
throw new RuntimeException("Failed to initialize worktree manager", e);
}
this.gitAvailable = isGitRepo();
}
/**
* 检查是否是Git仓库
*/
private boolean isGitRepo() {
try {
ProcessBuilder pb = new ProcessBuilder("git", "rev-parse", "--is-inside-work-tree");
pb.directory(repoRoot.toFile());
Process process = pb.start();
return process.exitValue() == 0;
} catch (Exception e) {
return false;
}
}
/**
* 执行Git命令
*/
private String runGit(List<String> args) throws Exception {
if (!gitAvailable) {
throw new RuntimeException("Not in a git repository. worktree tools require git.");
}
ProcessBuilder pb = new ProcessBuilder("git");
pb.command().addAll(args);
pb.directory(repoRoot.toFile());
Process process = pb.start();
boolean finished = process.waitFor(120, TimeUnit.SECONDS);
if (!finished) {
process.destroy();
throw new RuntimeException("git command timeout");
}
int exitCode = process.exitValue();
if (exitCode != 0) {
String error = new String(process.getErrorStream().readAllBytes()).trim();
String output = new String(process.getInputStream().readAllBytes()).trim();
String message = error.isEmpty() ? output : error;
if (message.isEmpty()) {
message = "git " + String.join(" ", args) + " failed";
}
throw new RuntimeException(message);
}
String output = new String(process.getInputStream().readAllBytes()).trim();
String error = new String(process.getErrorStream().readAllBytes()).trim();
String result = output + (error.isEmpty() ? "" : "\n" + error);
return result.isEmpty() ? "(no output)" : result;
}
/**
* 加载索引文件
*/
@SuppressWarnings("unchecked")
private Map<String, Object> loadIndex() throws IOException {
String content = Files.readString(indexPath);
Type type = new TypeToken<Map<String, Object>>(){}.getType();
return gson.fromJson(content, type);
}
/**
* 保存索引文件
*/
private void saveIndex(Map<String, Object> index) throws IOException {
Files.writeString(indexPath, gson.toJson(index));
}
/**
* 验证工作树名称
*/更多推荐

所有评论(0)