package io.mybatis.learn.s10;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.mybatis.learn.core.AgentRunner;
import io.mybatis.learn.core.team.MessageBus;
import io.mybatis.learn.core.tools.*;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* S10 - Team Protocols:关闭协议 + 计划审批协议
*
* TIPS: 对应Python s10_team_protocols.py。
* 在S09基础上新增3个Lead工具和2个Teammate工具。
*
* 关闭协议FSM: pending → approved | rejected
* Lead调用 shutdown_request → Teammate收到 shutdown_request 消息
* → Teammate调用 shutdown_response → Lead收件箱收到响应
*
* 计划审批FSM: pending → approved | rejected
* Teammate调用 plan_approval → Lead收件箱收到计划
* → Lead调用 plan_approval(review) → Teammate收到审批结果
*
* 关键洞察:"Same request_id correlation pattern, two domains."
*/
@SpringBootApplication(scanBasePackages = "io.mybatis.learn.core")
public class S10TeamProtocols implements CommandLineRunner {
@Autowired
private ChatModel chatModel;
@Override
public void run(String... args) throws Exception {
Path workDir = Path.of(System.getProperty("user.dir"));
Path teamDir = workDir.resolve(".team");
MessageBus bus = new MessageBus(teamDir.resolve("inbox"));
ProtocolTracker tracker = new ProtocolTracker(bus);
S10TeammateManager team = new S10TeammateManager(chatModel, bus, tracker, teamDir);
S10LeadTools leadTools = new S10LeadTools(bus, team, tracker);
ObjectMapper mapper = new ObjectMapper();
String systemPrompt = "You are a team lead at " + workDir
+ ". Manage teammates with shutdown and plan approval protocols.";
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultTools(new BashTool(), new ReadFileTool(),
new WriteFileTool(), new EditFileTool(), leadTools)
.build();
AgentRunner.interactive("s10", input -> {
if ("/team".equals(input)) return team.listAll();
if ("/inbox".equals(input)) {
try { return mapper.writeValueAsString(bus.readInbox("lead")); }
catch (Exception e) { return "Error: " + e.getMessage(); }
}
var inbox = bus.readInbox("lead");
String fullInput = input;
if (!inbox.isEmpty()) {
try {
fullInput = "" + mapper.writeValueAsString(inbox)
+ "\n\n" + input;
} catch (Exception e) { /* ignore */ }
}
return chatClient.prompt(fullInput).call().content();
});
}
// ---- 队友管理器(增加协议处理) ----
/**
* TIPS: 对应Python s10第134-290行的TeammateManager。
* 相比S09增加了:
* 1. should_exit标志 - 队友批准关闭后退出循环
* 2. shutdown_response工具 - 更新tracker + 发送响应消息
* 3. plan_approval工具 - 提交计划到lead收件箱
*/
static class S10TeammateManager {
private final ChatModel chatModel;
private final MessageBus bus;
private final ProtocolTracker tracker;
private final Path configPath;
private final ObjectMapper mapper = new ObjectMapper();
private Map config;
S10TeammateManager(ChatModel chatModel, MessageBus bus,
ProtocolTracker tracker, Path teamDir) {
this.chatModel = chatModel;
this.bus = bus;
this.tracker = tracker;
this.configPath = teamDir.resolve("config.json");
try { Files.createDirectories(teamDir); } catch (IOException e) { throw new RuntimeException(e); }
this.config = loadConfig();
}
@SuppressWarnings("unchecked")
private Map loadConfig() {
if (Files.exists(configPath)) {
try { return mapper.readValue(configPath.toFile(), Map.class); } catch (IOException e) { /* ignore */ }
}
Map cfg = new LinkedHashMap<>();
cfg.put("team_name", "default");
cfg.put("members", new ArrayList<>());
return cfg;
}
private synchronized void saveConfig() {
try { mapper.writerWithDefaultPrettyPrinter().writeValue(configPath.toFile(), config); }
catch (IOException e) { /* ignore */ }
}
@SuppressWarnings("unchecked")
private Map findMember(String name) {
for (Map m : (List