i18n: 全部用户可见字符串统一为英文(46个文件)
- 所有Command/Tool/Core/MCP/Plugin中的中文提示改为英文 - Javadoc注释和行内注释保留中文不变 - AI提示词(compact/commit/review等)改为英文 - 编译验证通过 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -24,7 +24,7 @@ public class ClaudeCodeRunner implements CommandLineRunner {
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
log.info("Claude Code (Java) 启动中...");
|
||||
log.info("Claude Code (Java) starting...");
|
||||
replSession.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class CommandRegistry {
|
||||
for (String alias : command.aliases()) {
|
||||
commands.put(alias.toLowerCase(), command);
|
||||
}
|
||||
log.debug("注册命令: /{}", command.name());
|
||||
log.debug("Registered command: /{}", command.name());
|
||||
}
|
||||
|
||||
/** 批量注册 */
|
||||
|
||||
@@ -44,7 +44,7 @@ public class BranchCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable.");
|
||||
}
|
||||
|
||||
String trimmedArgs = args != null ? args.trim() : "";
|
||||
@@ -63,7 +63,7 @@ public class BranchCommand implements SlashCommand {
|
||||
case "load" -> loadBranch(branchName, context);
|
||||
case "list" -> listBranches(context);
|
||||
case "delete" -> deleteBranch(branchName);
|
||||
default -> AnsiStyle.red(" ✗ 未知子命令: " + subCommand) + "\n" + showUsage();
|
||||
default -> AnsiStyle.red(" ✗ Unknown subcommand: " + subCommand) + "\n" + showUsage();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,8 +76,8 @@ public class BranchCommand implements SlashCommand {
|
||||
*/
|
||||
private String saveBranch(String branchName, CommandContext context) {
|
||||
if (branchName.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 请指定分支名称。") + "\n"
|
||||
+ AnsiStyle.dim(" 用法: /branch save <name>");
|
||||
return AnsiStyle.red(" ✗ Please specify branch name.") + "\n"
|
||||
+ AnsiStyle.dim(" Usage: /branch save <name>");
|
||||
}
|
||||
|
||||
List<Message> currentHistory = context.agentLoop().getMessageHistory();
|
||||
@@ -87,8 +87,8 @@ public class BranchCommand implements SlashCommand {
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
branches.put(branchName, new BranchSnapshot(snapshot, timestamp));
|
||||
|
||||
return AnsiStyle.green(" ✓ 分支已保存: ") + AnsiStyle.bold(branchName) + "\n"
|
||||
+ AnsiStyle.dim(" 消息数: " + snapshot.size() + " 时间: " + timestamp);
|
||||
return AnsiStyle.green(" ✓ Branch saved: ") + AnsiStyle.bold(branchName) + "\n"
|
||||
+ AnsiStyle.dim(" Messages: " + snapshot.size() + " Time: " + timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,21 +100,21 @@ public class BranchCommand implements SlashCommand {
|
||||
*/
|
||||
private String loadBranch(String branchName, CommandContext context) {
|
||||
if (branchName.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 请指定分支名称。") + "\n"
|
||||
+ AnsiStyle.dim(" 用法: /branch load <name>");
|
||||
return AnsiStyle.red(" ✗ Please specify branch name.") + "\n"
|
||||
+ AnsiStyle.dim(" Usage: /branch load <name>");
|
||||
}
|
||||
|
||||
BranchSnapshot snapshot = branches.get(branchName);
|
||||
if (snapshot == null) {
|
||||
return AnsiStyle.red(" ✗ 分支不存在: " + branchName) + "\n"
|
||||
+ AnsiStyle.dim(" 使用 /branch list 查看所有可用分支。");
|
||||
return AnsiStyle.red(" ✗ Branch not found: " + branchName) + "\n"
|
||||
+ AnsiStyle.dim(" Use /branch list to see all available branches.");
|
||||
}
|
||||
|
||||
// 恢复对话历史
|
||||
context.agentLoop().replaceHistory(new ArrayList<>(snapshot.messages()));
|
||||
|
||||
return AnsiStyle.green(" ✓ 已恢复到分支: ") + AnsiStyle.bold(branchName) + "\n"
|
||||
+ AnsiStyle.dim(" 已加载 " + snapshot.messages().size() + " 条消息 (保存于 " + snapshot.timestamp() + ")");
|
||||
return AnsiStyle.green(" ✓ Restored to branch: ") + AnsiStyle.bold(branchName) + "\n"
|
||||
+ AnsiStyle.dim(" Loaded " + snapshot.messages().size() + " messages (saved at " + snapshot.timestamp() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,8 +125,8 @@ public class BranchCommand implements SlashCommand {
|
||||
*/
|
||||
private String listBranches(CommandContext context) {
|
||||
if (branches.isEmpty()) {
|
||||
return AnsiStyle.dim(" 没有保存的分支。") + "\n"
|
||||
+ AnsiStyle.dim(" 使用 /branch save <name> 保存当前对话。");
|
||||
return AnsiStyle.dim(" No saved branches.") + "\n"
|
||||
+ AnsiStyle.dim(" Use /branch save <name> to save current conversation.");
|
||||
}
|
||||
|
||||
int currentSize = context.agentLoop().getMessageHistory().size();
|
||||
@@ -149,7 +149,7 @@ public class BranchCommand implements SlashCommand {
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
sb.append("\n").append(AnsiStyle.dim(" 共 " + branches.size() + " 个分支。")).append("\n");
|
||||
sb.append("\n").append(AnsiStyle.dim(" Total " + branches.size() + " branches.")).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -161,16 +161,16 @@ public class BranchCommand implements SlashCommand {
|
||||
*/
|
||||
private String deleteBranch(String branchName) {
|
||||
if (branchName.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 请指定分支名称。") + "\n"
|
||||
+ AnsiStyle.dim(" 用法: /branch delete <name>");
|
||||
return AnsiStyle.red(" ✗ Please specify branch name.") + "\n"
|
||||
+ AnsiStyle.dim(" Usage: /branch delete <name>");
|
||||
}
|
||||
|
||||
BranchSnapshot removed = branches.remove(branchName);
|
||||
if (removed == null) {
|
||||
return AnsiStyle.red(" ✗ 分支不存在: " + branchName);
|
||||
return AnsiStyle.red(" ✗ Branch not found: " + branchName);
|
||||
}
|
||||
|
||||
return AnsiStyle.green(" ✓ 分支已删除: ") + AnsiStyle.bold(branchName);
|
||||
return AnsiStyle.green(" ✓ Branch deleted: ") + AnsiStyle.bold(branchName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,11 +180,11 @@ public class BranchCommand implements SlashCommand {
|
||||
*/
|
||||
private String showUsage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.bold("\n 🌿 Branch — 对话分支管理\n\n"));
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch save <name>")).append(" 保存当前对话为分支\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch load <name>")).append(" 恢复到指定分支\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch list")).append(" 列出所有分支\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch delete <name>")).append(" 删除指定分支\n");
|
||||
sb.append(AnsiStyle.bold("\n 🌿 Branch — Conversation branch management\n\n"));
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch save <name>")).append(" Save current conversation as branch\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch load <name>")).append(" Restore to specified branch\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch list")).append(" List all branches\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/branch delete <name>")).append(" Delete specified branch\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CommitCommand implements SlashCommand {
|
||||
public String execute(String args, CommandContext context) {
|
||||
Path projectDir = Path.of(System.getProperty("user.dir"));
|
||||
if (!Files.isDirectory(projectDir.resolve(".git"))) {
|
||||
return AnsiStyle.yellow(" ⚠ 当前目录不是 Git 仓库");
|
||||
return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository");
|
||||
}
|
||||
|
||||
args = args == null ? "" : args.strip();
|
||||
@@ -49,7 +49,7 @@ public class CommitCommand implements SlashCommand {
|
||||
if (addAll) {
|
||||
String addResult = runGit(projectDir, "add", "-A");
|
||||
if (addResult == null) {
|
||||
return AnsiStyle.red(" ✗ git add 失败");
|
||||
return AnsiStyle.red(" ✗ git add failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,29 +58,29 @@ public class CommitCommand implements SlashCommand {
|
||||
if (staged == null || staged.isBlank()) {
|
||||
String status = runGit(projectDir, "status", "--short");
|
||||
if (status != null && !status.isBlank()) {
|
||||
return AnsiStyle.yellow(" ⚠ 没有已暂存的变更\n")
|
||||
+ AnsiStyle.dim(" 使用 /commit --all 自动添加所有文件\n")
|
||||
+ AnsiStyle.dim(" 或先手动执行 git add");
|
||||
return AnsiStyle.yellow(" ⚠ No staged changes\n")
|
||||
+ AnsiStyle.dim(" Use /commit --all to add all files\n")
|
||||
+ AnsiStyle.dim(" Or run git add manually first");
|
||||
}
|
||||
return AnsiStyle.green(" ✓ 工作区干净,无需提交");
|
||||
return AnsiStyle.green(" ✓ Working directory clean, nothing to commit");
|
||||
}
|
||||
|
||||
// 如果没有指定 message,使用 AI 生成
|
||||
if (message.isEmpty()) {
|
||||
message = generateCommitMessage(projectDir, context);
|
||||
if (message == null || message.isBlank()) {
|
||||
return AnsiStyle.red(" ✗ 无法生成 commit message");
|
||||
return AnsiStyle.red(" ✗ Failed to generate commit message");
|
||||
}
|
||||
}
|
||||
|
||||
// 执行 git commit
|
||||
String commitResult = runGit(projectDir, "commit", "-m", message);
|
||||
if (commitResult == null) {
|
||||
return AnsiStyle.red(" ✗ git commit 失败");
|
||||
return AnsiStyle.red(" ✗ git commit failed");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n").append(AnsiStyle.green(" ✓ Commit 成功\n"));
|
||||
sb.append("\n").append(AnsiStyle.green(" ✓ Commit successful\n"));
|
||||
sb.append(" ").append("─".repeat(50)).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("Message: ")).append(message).append("\n");
|
||||
|
||||
@@ -90,7 +90,7 @@ public class CommitCommand implements SlashCommand {
|
||||
return sb.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ 提交失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Commit failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,12 +108,12 @@ public class CommitCommand implements SlashCommand {
|
||||
|
||||
// 使用 ChatModel 生成 commit message
|
||||
String prompt = """
|
||||
分析以下 git diff,生成一个简洁的 commit message。
|
||||
要求:
|
||||
1. 使用 conventional commits 格式(feat/fix/docs/refactor/chore等前缀)
|
||||
2. 第一行不超过 72 个字符
|
||||
3. 如果有多个变更,可以在第一行后空一行添加详细说明
|
||||
4. 只返回 commit message 文本,不要添加其他说明
|
||||
Analyze the following git diff and generate a concise commit message.
|
||||
Requirements:
|
||||
1. Use conventional commits format (feat/fix/docs/refactor/chore prefix)
|
||||
2. First line should not exceed 72 characters
|
||||
3. For multiple changes, add details after a blank line
|
||||
4. Return only the commit message text, no additional explanation
|
||||
|
||||
Git diff:
|
||||
```
|
||||
|
||||
@@ -22,14 +22,14 @@ import java.util.List;
|
||||
public class CompactCommand implements SlashCommand {
|
||||
|
||||
private static final String COMPACT_PROMPT = """
|
||||
请将以下对话历史压缩为一段简洁的摘要。要求:
|
||||
1. 保留所有关键决策、代码变更和技术细节
|
||||
2. 保留文件路径、函数名等具体信息
|
||||
3. 保留用户的偏好和要求
|
||||
4. 省略重复的讨论和无关的细节
|
||||
5. 用中文输出,控制在500字以内
|
||||
Please compress the following conversation history into a concise summary. Requirements:
|
||||
1. Preserve all key decisions, code changes, and technical details
|
||||
2. Keep file paths, function names, and specific information
|
||||
3. Preserve user preferences and requirements
|
||||
4. Omit repeated discussions and irrelevant details
|
||||
5. Output within 500 words
|
||||
|
||||
对话历史:
|
||||
Conversation history:
|
||||
""";
|
||||
|
||||
@Override
|
||||
@@ -45,14 +45,14 @@ public class CompactCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.yellow(" ⚠ 没有活跃的对话可压缩。");
|
||||
return AnsiStyle.yellow(" ⚠ No active conversation to compact.");
|
||||
}
|
||||
|
||||
List<Message> history = context.agentLoop().getMessageHistory();
|
||||
int before = history.size();
|
||||
|
||||
if (before <= 3) {
|
||||
return AnsiStyle.dim(" 上下文已经很小(" + before + " 条消息),无需压缩。");
|
||||
return AnsiStyle.dim(" Context is already small (" + before + " messages), no compaction needed.");
|
||||
}
|
||||
|
||||
TokenTracker tracker = context.agentLoop().getTokenTracker();
|
||||
@@ -66,7 +66,7 @@ public class CompactCommand implements SlashCommand {
|
||||
compacted.add(history.getFirst()); // 原始系统提示词
|
||||
|
||||
if (summary != null && !summary.isBlank()) {
|
||||
compacted.add(new SystemMessage("[对话历史摘要] " + summary));
|
||||
compacted.add(new SystemMessage("[Conversation Summary] " + summary));
|
||||
}
|
||||
|
||||
// 保留最后一轮用户消息和助手回复(如果有)
|
||||
@@ -78,15 +78,15 @@ public class CompactCommand implements SlashCommand {
|
||||
int after = compacted.size();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.green(" ✅ 上下文已压缩")).append("\n");
|
||||
sb.append(" 消息数: ").append(before).append(" → ").append(after).append("\n");
|
||||
sb.append(AnsiStyle.green(" ✅ Context compacted")).append("\n");
|
||||
sb.append(" Messages: ").append(before).append(" → ").append(after).append("\n");
|
||||
if (tokensBefore > 0) {
|
||||
sb.append(" 压缩前累计 Token: ").append(TokenTracker.formatTokens(tokensBefore)).append("\n");
|
||||
sb.append(" Tokens before compaction: ").append(TokenTracker.formatTokens(tokensBefore)).append("\n");
|
||||
}
|
||||
if (summary != null) {
|
||||
sb.append(AnsiStyle.dim(" 📝 AI 摘要已生成并注入上下文"));
|
||||
sb.append(AnsiStyle.dim(" 📝 AI summary generated and injected into context"));
|
||||
} else {
|
||||
sb.append(AnsiStyle.dim(" ⚠ AI 摘要生成失败,仅保留最近对话"));
|
||||
sb.append(AnsiStyle.dim(" ⚠ AI summary generation failed, keeping recent conversation only"));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@@ -101,17 +101,17 @@ public class CompactCommand implements SlashCommand {
|
||||
StringBuilder dialogText = new StringBuilder();
|
||||
for (Message msg : history) {
|
||||
switch (msg) {
|
||||
case UserMessage um -> dialogText.append("[用户] ").append(um.getText()).append("\n");
|
||||
case UserMessage um -> dialogText.append("[User] ").append(um.getText()).append("\n");
|
||||
case AssistantMessage am -> {
|
||||
if (am.getText() != null && !am.getText().isBlank()) {
|
||||
// 截断过长的助手回复
|
||||
String text = am.getText();
|
||||
if (text.length() > 500) text = text.substring(0, 500) + "...";
|
||||
dialogText.append("[助手] ").append(text).append("\n");
|
||||
dialogText.append("[Assistant] ").append(text).append("\n");
|
||||
}
|
||||
if (am.hasToolCalls()) {
|
||||
for (var tc : am.getToolCalls()) {
|
||||
dialogText.append("[工具调用] ").append(tc.name()).append("\n");
|
||||
dialogText.append("[Tool Call] ").append(tc.name()).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class CopyCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
if (lastResponse == null) {
|
||||
return AnsiStyle.yellow(" ⚠ 暂无 AI 回复可复制");
|
||||
return AnsiStyle.yellow(" ⚠ No AI response to copy");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -56,14 +56,14 @@ public class CopyCommand implements SlashCommand {
|
||||
|
||||
int charCount = lastResponse.length();
|
||||
int lineCount = (int) lastResponse.lines().count();
|
||||
return AnsiStyle.green(" ✓ 已复制到剪贴板")
|
||||
+ AnsiStyle.dim(" (" + charCount + " 字符, " + lineCount + " 行)");
|
||||
return AnsiStyle.green(" ✓ Copied to clipboard")
|
||||
+ AnsiStyle.dim(" (" + charCount + " chars, " + lineCount + " lines)");
|
||||
} catch (java.awt.HeadlessException e) {
|
||||
// 无头环境(如 SSH)无法使用 AWT 剪贴板
|
||||
return AnsiStyle.yellow(" ⚠ 当前环境不支持剪贴板(Headless 模式)\n")
|
||||
+ AnsiStyle.dim(" 提示:在有图形界面的终端中运行可使用此功能");
|
||||
return AnsiStyle.yellow(" ⚠ Clipboard not supported (headless mode)\n")
|
||||
+ AnsiStyle.dim(" Tip: Run in a graphical terminal to use this feature");
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ 复制失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Copy failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class DiffCommand implements SlashCommand {
|
||||
public String execute(String args, CommandContext context) {
|
||||
Path projectDir = Path.of(System.getProperty("user.dir"));
|
||||
if (!Files.isDirectory(projectDir.resolve(".git"))) {
|
||||
return AnsiStyle.yellow(" ⚠ 当前目录不是 Git 仓库");
|
||||
return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository");
|
||||
}
|
||||
|
||||
args = args == null ? "" : args.strip();
|
||||
@@ -72,7 +72,7 @@ public class DiffCommand implements SlashCommand {
|
||||
long lineCount = unstaged.lines().count();
|
||||
if (lineCount > 100) {
|
||||
unstaged.lines().limit(100).forEach(l -> sb.append(" ").append(l).append("\n"));
|
||||
sb.append(AnsiStyle.dim(" ... (共 " + lineCount + " 行,截断显示前100行)\n"));
|
||||
sb.append(AnsiStyle.dim(" ... (" + lineCount + " lines total, showing first 100)\n"));
|
||||
} else {
|
||||
unstaged.lines().forEach(l -> sb.append(" ").append(l).append("\n"));
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class DiffCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
if (staged.isBlank() && unstaged.isBlank() && untracked.isBlank()) {
|
||||
sb.append("\n").append(AnsiStyle.green(" ✓ 工作区干净,无变更\n"));
|
||||
sb.append("\n").append(AnsiStyle.green(" ✓ Working directory clean, no changes\n"));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@@ -96,12 +96,12 @@ public class DiffCommand implements SlashCommand {
|
||||
sb.append(" ").append("─".repeat(50)).append("\n\n");
|
||||
|
||||
if (diffOutput.isBlank()) {
|
||||
sb.append(AnsiStyle.green(" ✓ 无变更\n"));
|
||||
sb.append(AnsiStyle.green(" ✓ No changes\n"));
|
||||
} else {
|
||||
long lineCount = diffOutput.lines().count();
|
||||
if (lineCount > 100) {
|
||||
diffOutput.lines().limit(100).forEach(l -> sb.append(" ").append(l).append("\n"));
|
||||
sb.append(AnsiStyle.dim(" ... (共 " + lineCount + " 行)\n"));
|
||||
sb.append(AnsiStyle.dim(" ... (" + lineCount + " lines)\n"));
|
||||
} else {
|
||||
diffOutput.lines().forEach(l -> sb.append(" ").append(l).append("\n"));
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class DiffCommand implements SlashCommand {
|
||||
return sb.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ Git diff 执行失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Git diff failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class ExportCommand implements SlashCommand {
|
||||
|
||||
// 至少需要系统提示 + 用户消息 + 助手回复
|
||||
if (history.size() < 3) {
|
||||
return AnsiStyle.yellow(" ⚠ 暂无足够的对话内容可导出");
|
||||
return AnsiStyle.yellow(" ⚠ Not enough conversation content to export");
|
||||
}
|
||||
|
||||
// 确定输出路径
|
||||
@@ -67,10 +67,10 @@ public class ExportCommand implements SlashCommand {
|
||||
|
||||
int msgCount = history.size();
|
||||
int lineCount = (int) markdown.lines().count();
|
||||
return AnsiStyle.green(" ✓ 对话已导出: " + outputPath)
|
||||
return AnsiStyle.green(" ✓ Conversation exported: " + outputPath)
|
||||
+ AnsiStyle.dim(" (" + msgCount + " messages, " + lineCount + " lines)");
|
||||
} catch (IOException e) {
|
||||
return AnsiStyle.red(" ✗ 导出失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Export failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ public class HistoryCommand implements SlashCommand {
|
||||
var conversations = persistence.listConversations();
|
||||
|
||||
if (conversations.isEmpty()) {
|
||||
return AnsiStyle.dim(" 📂 暂无保存的对话历史");
|
||||
return AnsiStyle.dim(" 📂 No saved conversation history");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.bold(" 📂 对话历史") + AnsiStyle.dim(" (")
|
||||
+ conversations.size() + AnsiStyle.dim(" 条记录)\n"));
|
||||
sb.append(AnsiStyle.bold(" 📂 Conversation History") + AnsiStyle.dim(" (")
|
||||
+ conversations.size() + AnsiStyle.dim(" records)\n"));
|
||||
sb.append(AnsiStyle.dim(" " + "─".repeat(50)) + "\n");
|
||||
|
||||
int shown = Math.min(conversations.size(), 10);
|
||||
@@ -46,10 +46,10 @@ public class HistoryCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
if (conversations.size() > 10) {
|
||||
sb.append(AnsiStyle.dim(" ... 还有 " + (conversations.size() - 10) + " 条更早的记录\n"));
|
||||
sb.append(AnsiStyle.dim(" ... and " + (conversations.size() - 10) + " older records\n"));
|
||||
}
|
||||
|
||||
sb.append(AnsiStyle.dim("\n 对话存储位置: " + persistence.getConversationsDir()));
|
||||
sb.append(AnsiStyle.dim("\n Storage location: " + persistence.getConversationsDir()));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class HooksCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null || context.agentLoop().getHookManager() == null) {
|
||||
return AnsiStyle.yellow(" ⚠ Hook 管理器不可用。");
|
||||
return AnsiStyle.yellow(" ⚠ Hook manager unavailable.");
|
||||
}
|
||||
|
||||
HookManager hookManager = context.agentLoop().getHookManager();
|
||||
@@ -86,10 +86,10 @@ public class HooksCommand implements SlashCommand {
|
||||
*/
|
||||
private String formatTypeName(HookType type) {
|
||||
return switch (type) {
|
||||
case PRE_TOOL_USE -> "PRE_TOOL_USE (工具执行前)";
|
||||
case POST_TOOL_USE -> "POST_TOOL_USE (工具执行后)";
|
||||
case PRE_PROMPT -> "PRE_PROMPT (发送提示前)";
|
||||
case POST_RESPONSE -> "POST_RESPONSE (收到响应后)";
|
||||
case PRE_TOOL_USE -> "PRE_TOOL_USE (before tool execution)";
|
||||
case POST_TOOL_USE -> "POST_TOOL_USE (after tool execution)";
|
||||
case PRE_PROMPT -> "PRE_PROMPT (before sending prompt)";
|
||||
case POST_RESPONSE -> "POST_RESPONSE (after receiving response)";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class McpCommand implements SlashCommand {
|
||||
// 暂时通过静态持有者或类似机制获取。
|
||||
McpManager manager = McpManagerHolder.getInstance();
|
||||
if (manager == null) {
|
||||
return AnsiStyle.red(" ❌ MCP 管理器未初始化");
|
||||
return AnsiStyle.red(" ❌ MCP manager not initialized");
|
||||
}
|
||||
|
||||
String trimmed = args.strip();
|
||||
@@ -63,7 +63,7 @@ public class McpCommand implements SlashCommand {
|
||||
case "resources" -> handleResources(manager, subArgs);
|
||||
case "reload" -> handleReload(manager, context);
|
||||
case "help" -> showHelp();
|
||||
default -> AnsiStyle.red(" 未知子命令: " + subCommand) + "\n" + showHelp();
|
||||
default -> AnsiStyle.red(" Unknown subcommand: " + subCommand) + "\n" + showHelp();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ public class McpCommand implements SlashCommand {
|
||||
private String showStatus(McpManager manager) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.bold(" 🔌 MCP 服务器状态\n"));
|
||||
sb.append(AnsiStyle.bold(" 🔌 MCP Server Status\n"));
|
||||
sb.append(" ").append("─".repeat(50)).append("\n\n");
|
||||
|
||||
Map<String, McpClient> clients = manager.getClients();
|
||||
if (clients.isEmpty()) {
|
||||
sb.append(" 无已连接的 MCP 服务器\n\n");
|
||||
sb.append(AnsiStyle.dim(" 提示: 使用 /mcp connect <name> <command> [args] 连接服务器\n"));
|
||||
sb.append(AnsiStyle.dim(" 或在 .mcp.json 配置文件中定义服务器\n"));
|
||||
sb.append(" No connected MCP servers\n\n");
|
||||
sb.append(AnsiStyle.dim(" Tip: Use /mcp connect <name> <command> [args] to connect\n"));
|
||||
sb.append(AnsiStyle.dim(" Or define servers in .mcp.json config file\n"));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -92,13 +92,13 @@ public class McpCommand implements SlashCommand {
|
||||
String statusText;
|
||||
if (client.isConnected() && client.isInitialized()) {
|
||||
statusIcon = "✅";
|
||||
statusText = AnsiStyle.green("已连接");
|
||||
statusText = AnsiStyle.green("Connected");
|
||||
} else if (client.isConnected()) {
|
||||
statusIcon = "🔄";
|
||||
statusText = AnsiStyle.yellow("连接中");
|
||||
statusText = AnsiStyle.yellow("Connecting");
|
||||
} else {
|
||||
statusIcon = "❌";
|
||||
statusText = AnsiStyle.red("已断开");
|
||||
statusText = AnsiStyle.red("Disconnected");
|
||||
}
|
||||
|
||||
sb.append(String.format(" %s %-18s %s%n", statusIcon, AnsiStyle.bold(name), statusText));
|
||||
@@ -107,7 +107,7 @@ public class McpCommand implements SlashCommand {
|
||||
int toolCount = client.getTools().size();
|
||||
int resCount = client.getResources().size();
|
||||
sb.append(String.format(" %s%n",
|
||||
AnsiStyle.dim(toolCount + " 工具, " + resCount + " 资源")));
|
||||
AnsiStyle.dim(toolCount + " tools, " + resCount + " resources")));
|
||||
|
||||
// 显示服务器信息
|
||||
if (client.getServerInfo() != null) {
|
||||
@@ -117,8 +117,8 @@ public class McpCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.dim(" 共 " + clients.size() + " 个服务器, "
|
||||
+ manager.getAllTools().size() + " 个工具\n"));
|
||||
sb.append(AnsiStyle.dim(" Total " + clients.size() + " servers, "
|
||||
+ manager.getAllTools().size() + " tools\n"));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -128,12 +128,12 @@ public class McpCommand implements SlashCommand {
|
||||
*/
|
||||
private String handleConnect(McpManager manager, String args, CommandContext context) {
|
||||
if (args.isEmpty()) {
|
||||
return AnsiStyle.red(" 用法: /mcp connect <name> <command> [args...]");
|
||||
return AnsiStyle.red(" Usage: /mcp connect <name> <command> [args...]");
|
||||
}
|
||||
|
||||
String[] parts = args.split("\\s+");
|
||||
if (parts.length < 2) {
|
||||
return AnsiStyle.red(" 用法: /mcp connect <name> <command> [args...]");
|
||||
return AnsiStyle.red(" Usage: /mcp connect <name> <command> [args...]");
|
||||
}
|
||||
|
||||
String name = parts[0];
|
||||
@@ -149,13 +149,13 @@ public class McpCommand implements SlashCommand {
|
||||
registerBridgedTools(client, name, context);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.green(" ✅ 已连接 MCP 服务器: " + name)).append("\n");
|
||||
sb.append(AnsiStyle.dim(" " + client.getTools().size() + " 个工具, "
|
||||
+ client.getResources().size() + " 个资源")).append("\n");
|
||||
sb.append(AnsiStyle.green(" ✅ Connected to MCP server: " + name)).append("\n");
|
||||
sb.append(AnsiStyle.dim(" " + client.getTools().size() + " tools, "
|
||||
+ client.getResources().size() + " resources")).append("\n");
|
||||
|
||||
// 列出发现的工具
|
||||
if (!client.getTools().isEmpty()) {
|
||||
sb.append("\n 工具:\n");
|
||||
sb.append("\n Tools:\n");
|
||||
for (McpClient.McpTool tool : client.getTools()) {
|
||||
sb.append(" • ").append(tool.name());
|
||||
if (!tool.description().isEmpty()) {
|
||||
@@ -167,7 +167,7 @@ public class McpCommand implements SlashCommand {
|
||||
|
||||
return sb.toString();
|
||||
} catch (McpException e) {
|
||||
return AnsiStyle.red(" ❌ 连接失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ❌ Connection failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,15 +176,15 @@ public class McpCommand implements SlashCommand {
|
||||
*/
|
||||
private String handleDisconnect(McpManager manager, String args) {
|
||||
if (args.isEmpty()) {
|
||||
return AnsiStyle.red(" 用法: /mcp disconnect <name>");
|
||||
return AnsiStyle.red(" Usage: /mcp disconnect <name>");
|
||||
}
|
||||
|
||||
String name = args.split("\\s+")[0];
|
||||
try {
|
||||
manager.disconnect(name);
|
||||
return AnsiStyle.green(" ✅ 已断开 MCP 服务器: " + name);
|
||||
return AnsiStyle.green(" ✅ Disconnected MCP server: " + name);
|
||||
} catch (McpException e) {
|
||||
return AnsiStyle.red(" ❌ 断开失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ❌ Disconnect failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public class McpCommand implements SlashCommand {
|
||||
private String handleTools(McpManager manager, String args) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.bold(" 🛠️ MCP 工具列表\n"));
|
||||
sb.append(AnsiStyle.bold(" 🛠️ MCP Tools\n"));
|
||||
sb.append(" ").append("─".repeat(50)).append("\n\n");
|
||||
|
||||
String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0];
|
||||
@@ -203,13 +203,13 @@ public class McpCommand implements SlashCommand {
|
||||
if (serverFilter != null) {
|
||||
tools = manager.getServerTools(serverFilter);
|
||||
if (tools.isEmpty()) {
|
||||
return sb + " 服务器 '" + serverFilter + "' 无工具或不存在\n";
|
||||
return sb + " Server '" + serverFilter + "' has no tools or does not exist\n";
|
||||
}
|
||||
sb.append(AnsiStyle.dim(" 服务器: " + serverFilter)).append("\n\n");
|
||||
sb.append(AnsiStyle.dim(" Server: " + serverFilter)).append("\n\n");
|
||||
} else {
|
||||
tools = manager.getAllTools();
|
||||
if (tools.isEmpty()) {
|
||||
return sb + " 无可用的 MCP 工具\n";
|
||||
return sb + " No available MCP tools\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ public class McpCommand implements SlashCommand {
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
sb.append(AnsiStyle.dim(" 共 " + tools.size() + " 个工具")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" Total " + tools.size() + " tools")).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class McpCommand implements SlashCommand {
|
||||
private String handleResources(McpManager manager, String args) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.bold(" 📦 MCP 资源列表\n"));
|
||||
sb.append(AnsiStyle.bold(" 📦 MCP Resources\n"));
|
||||
sb.append(" ").append("─".repeat(50)).append("\n\n");
|
||||
|
||||
String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0];
|
||||
@@ -244,13 +244,13 @@ public class McpCommand implements SlashCommand {
|
||||
if (serverFilter != null) {
|
||||
resourceList = manager.getServerResources(serverFilter);
|
||||
if (resourceList.isEmpty()) {
|
||||
return sb + " 服务器 '" + serverFilter + "' 无资源或不存在\n";
|
||||
return sb + " Server '" + serverFilter + "' has no resources or does not exist\n";
|
||||
}
|
||||
sb.append(AnsiStyle.dim(" 服务器: " + serverFilter)).append("\n\n");
|
||||
sb.append(AnsiStyle.dim(" Server: " + serverFilter)).append("\n\n");
|
||||
} else {
|
||||
resourceList = manager.getAllResources();
|
||||
if (resourceList.isEmpty()) {
|
||||
return sb + " 无可用的 MCP 资源\n";
|
||||
return sb + " No available MCP resources\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ public class McpCommand implements SlashCommand {
|
||||
sb.append(" MIME: ").append(AnsiStyle.dim(resource.mimeType())).append("\n\n");
|
||||
}
|
||||
|
||||
sb.append(AnsiStyle.dim(" 共 " + resourceList.size() + " 个资源")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" Total " + resourceList.size() + " resources")).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -279,11 +279,11 @@ public class McpCommand implements SlashCommand {
|
||||
registerBridgedTools(entry.getValue(), entry.getKey(), context);
|
||||
}
|
||||
|
||||
return AnsiStyle.green(" ✅ MCP 配置已重新加载: "
|
||||
+ manager.getClients().size() + " 个服务器, "
|
||||
+ manager.getAllTools().size() + " 个工具");
|
||||
return AnsiStyle.green(" ✅ MCP config reloaded: "
|
||||
+ manager.getClients().size() + " servers, "
|
||||
+ manager.getAllTools().size() + " tools");
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ❌ 重载失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ❌ Reload failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,20 +308,20 @@ public class McpCommand implements SlashCommand {
|
||||
private String showHelp() {
|
||||
return """
|
||||
|
||||
\033[1m🔌 MCP 命令帮助\033[0m
|
||||
\033[1m🔌 MCP Command Help\033[0m
|
||||
──────────────────────────────────────
|
||||
|
||||
/mcp 列出所有 MCP 服务器状态
|
||||
/mcp connect <name> <cmd> [args] 连接到 MCP 服务器
|
||||
/mcp disconnect <name> 断开 MCP 服务器
|
||||
/mcp tools [server] 列出 MCP 工具
|
||||
/mcp resources [server] 列出 MCP 资源
|
||||
/mcp reload 从配置文件重新加载
|
||||
/mcp help 显示此帮助信息
|
||||
/mcp List all MCP server status
|
||||
/mcp connect <name> <cmd> [args] Connect to MCP server
|
||||
/mcp disconnect <name> Disconnect MCP server
|
||||
/mcp tools [server] List MCP tools
|
||||
/mcp resources [server] List MCP resources
|
||||
/mcp reload Reload from config file
|
||||
/mcp help Show this help
|
||||
|
||||
配置文件:
|
||||
项目级: .mcp.json
|
||||
全局: ~/.claude-code-java/mcp.json
|
||||
Config files:
|
||||
Project: .mcp.json
|
||||
Global: ~/.claude-code-java/mcp.json
|
||||
""";
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,13 @@ public class MemoryCommand implements SlashCommand {
|
||||
/** 显示项目级 CLAUDE.md */
|
||||
private String showProjectMemory() {
|
||||
Path projectClaudeMd = Path.of(System.getProperty("user.dir"), "CLAUDE.md");
|
||||
return showMemoryFile(projectClaudeMd, "项目级");
|
||||
return showMemoryFile(projectClaudeMd, "Project");
|
||||
}
|
||||
|
||||
/** 显示用户级 CLAUDE.md */
|
||||
private String showUserMemory() {
|
||||
Path userClaudeMd = Path.of(System.getProperty("user.home"), ".claude", "CLAUDE.md");
|
||||
return showMemoryFile(userClaudeMd, "用户级");
|
||||
return showMemoryFile(userClaudeMd, "User");
|
||||
}
|
||||
|
||||
private String showMemoryFile(Path path, String level) {
|
||||
@@ -76,17 +76,17 @@ public class MemoryCommand implements SlashCommand {
|
||||
try {
|
||||
String content = Files.readString(path, StandardCharsets.UTF_8);
|
||||
if (content.isBlank()) {
|
||||
sb.append(AnsiStyle.dim(" (文件为空)\n"));
|
||||
sb.append(AnsiStyle.dim(" (File is empty)\n"));
|
||||
} else {
|
||||
content.lines().forEach(line -> sb.append(" ").append(line).append("\n"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sb.append(AnsiStyle.red(" ✗ 读取失败: " + e.getMessage() + "\n"));
|
||||
sb.append(AnsiStyle.red(" ✗ Read failed: " + e.getMessage() + "\n"));
|
||||
}
|
||||
} else {
|
||||
sb.append(AnsiStyle.dim(" (文件不存在)\n\n"));
|
||||
sb.append(AnsiStyle.dim(" 使用 /memory add <内容> 创建并添加内容\n"));
|
||||
sb.append(AnsiStyle.dim(" 或使用 /init 命令初始化\n"));
|
||||
sb.append(AnsiStyle.dim(" (File does not exist)\n\n"));
|
||||
sb.append(AnsiStyle.dim(" Use /memory add <content> to create and add content\n"));
|
||||
sb.append(AnsiStyle.dim(" Or use /init command to initialize\n"));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@@ -95,7 +95,7 @@ public class MemoryCommand implements SlashCommand {
|
||||
/** 追加内容到项目级 CLAUDE.md */
|
||||
private String handleAdd(String content) {
|
||||
if (content.isEmpty()) {
|
||||
return AnsiStyle.yellow(" ⚠ 请提供要添加的内容:/memory add <内容>");
|
||||
return AnsiStyle.yellow(" ⚠ Please provide content: /memory add <content>");
|
||||
}
|
||||
|
||||
Path projectClaudeMd = Path.of(System.getProperty("user.dir"), "CLAUDE.md");
|
||||
@@ -105,7 +105,7 @@ public class MemoryCommand implements SlashCommand {
|
||||
Files.writeString(projectClaudeMd,
|
||||
"# CLAUDE.md\n\n" + content + "\n",
|
||||
StandardCharsets.UTF_8);
|
||||
return AnsiStyle.green(" ✓ 已创建 CLAUDE.md 并添加内容");
|
||||
return AnsiStyle.green(" ✓ Created CLAUDE.md and added content");
|
||||
}
|
||||
|
||||
// 追加内容
|
||||
@@ -113,9 +113,9 @@ public class MemoryCommand implements SlashCommand {
|
||||
String newContent = existing.endsWith("\n") ? existing + "\n" + content + "\n" : existing + "\n\n" + content + "\n";
|
||||
Files.writeString(projectClaudeMd, newContent, StandardCharsets.UTF_8);
|
||||
|
||||
return AnsiStyle.green(" ✓ 已追加内容到 CLAUDE.md");
|
||||
return AnsiStyle.green(" ✓ Content appended to CLAUDE.md");
|
||||
} catch (IOException e) {
|
||||
return AnsiStyle.red(" ✗ 写入失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Write failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,20 +138,20 @@ public class MemoryCommand implements SlashCommand {
|
||||
pb.inheritIO();
|
||||
Process p = pb.start();
|
||||
p.waitFor();
|
||||
return AnsiStyle.green(" ✓ 编辑器已关闭");
|
||||
return AnsiStyle.green(" ✓ Editor closed");
|
||||
}
|
||||
|
||||
// Windows: 尝试 notepad
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
ProcessBuilder pb = new ProcessBuilder("notepad", projectClaudeMd.toString());
|
||||
pb.start(); // 不等待
|
||||
return AnsiStyle.green(" ✓ 已用记事本打开 CLAUDE.md");
|
||||
return AnsiStyle.green(" ✓ Opened CLAUDE.md with Notepad");
|
||||
}
|
||||
|
||||
return AnsiStyle.yellow(" ⚠ 未找到编辑器。请设置 EDITOR 环境变量,或手动编辑:\n " + projectClaudeMd);
|
||||
return AnsiStyle.yellow(" ⚠ No editor found. Set EDITOR environment variable, or manually edit:\n " + projectClaudeMd);
|
||||
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ 打开编辑器失败: " + e.getMessage());
|
||||
return AnsiStyle.red(" ✗ Failed to open editor: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class PluginCommand implements SlashCommand {
|
||||
public String execute(String args, CommandContext context) {
|
||||
PluginManager manager = getPluginManager(context);
|
||||
if (manager == null) {
|
||||
return AnsiStyle.red(" ✗ 插件系统未初始化");
|
||||
return AnsiStyle.red(" ✗ Plugin system not initialized");
|
||||
}
|
||||
|
||||
String trimmed = (args == null) ? "" : args.trim();
|
||||
@@ -67,7 +67,7 @@ public class PluginCommand implements SlashCommand {
|
||||
case "unload" -> unloadPlugin(manager, subArgs);
|
||||
case "reload" -> reloadPlugins(manager);
|
||||
case "info" -> pluginInfo(manager, subArgs);
|
||||
default -> AnsiStyle.yellow(" 未知子命令: " + subCommand) + "\n"
|
||||
default -> AnsiStyle.yellow(" Unknown subcommand: " + subCommand) + "\n"
|
||||
+ usageHelp();
|
||||
};
|
||||
}
|
||||
@@ -96,12 +96,12 @@ public class PluginCommand implements SlashCommand {
|
||||
sb.append(String.format(" ID: %s | %s%n",
|
||||
AnsiStyle.cyan(p.id()),
|
||||
p.description()));
|
||||
sb.append(String.format(" 工具: %d | 命令: %d%n",
|
||||
sb.append(String.format(" Tools: %d | Commands: %d%n",
|
||||
p.getTools().size(),
|
||||
p.getCommands().size()));
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append(AnsiStyle.dim(String.format(" 共 %d 个插件", plugins.size()))).append("\n");
|
||||
sb.append(AnsiStyle.dim(String.format(" Total %d plugins", plugins.size()))).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -111,15 +111,15 @@ public class PluginCommand implements SlashCommand {
|
||||
*/
|
||||
private String loadPlugin(PluginManager manager, String pathStr) {
|
||||
if (pathStr.isEmpty()) {
|
||||
return AnsiStyle.yellow(" 用法: /plugin load <jar-path>");
|
||||
return AnsiStyle.yellow(" Usage: /plugin load <jar-path>");
|
||||
}
|
||||
Path jarPath = Path.of(pathStr);
|
||||
boolean success = manager.loadPlugin(jarPath);
|
||||
if (success) {
|
||||
return AnsiStyle.green(" ✓ 插件加载成功: " + jarPath.getFileName());
|
||||
return AnsiStyle.green(" ✓ Plugin loaded: " + jarPath.getFileName());
|
||||
} else {
|
||||
return AnsiStyle.red(" ✗ 插件加载失败: " + jarPath.getFileName())
|
||||
+ "\n" + AnsiStyle.dim(" 请检查 JAR 是否包含有效的 Plugin-Class 属性");
|
||||
return AnsiStyle.red(" ✗ Plugin load failed: " + jarPath.getFileName())
|
||||
+ "\n" + AnsiStyle.dim(" Please check if JAR contains a valid Plugin-Class attribute");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,13 +128,13 @@ public class PluginCommand implements SlashCommand {
|
||||
*/
|
||||
private String unloadPlugin(PluginManager manager, String pluginId) {
|
||||
if (pluginId.isEmpty()) {
|
||||
return AnsiStyle.yellow(" 用法: /plugin unload <plugin-id>");
|
||||
return AnsiStyle.yellow(" Usage: /plugin unload <plugin-id>");
|
||||
}
|
||||
boolean success = manager.unload(pluginId);
|
||||
if (success) {
|
||||
return AnsiStyle.green(" ✓ 插件已卸载: " + pluginId);
|
||||
return AnsiStyle.green(" ✓ Plugin unloaded: " + pluginId);
|
||||
} else {
|
||||
return AnsiStyle.red(" ✗ 未找到插件: " + pluginId);
|
||||
return AnsiStyle.red(" ✗ Plugin not found: " + pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class PluginCommand implements SlashCommand {
|
||||
manager.loadAll();
|
||||
int afterCount = manager.getPlugins().size();
|
||||
return AnsiStyle.green(
|
||||
String.format(" ✓ 插件已重载(之前: %d,现在: %d)", beforeCount, afterCount));
|
||||
String.format(" ✓ Plugins reloaded (before: %d, now: %d)", beforeCount, afterCount));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,12 +155,12 @@ public class PluginCommand implements SlashCommand {
|
||||
*/
|
||||
private String pluginInfo(PluginManager manager, String pluginId) {
|
||||
if (pluginId.isEmpty()) {
|
||||
return AnsiStyle.yellow(" 用法: /plugin info <plugin-id>");
|
||||
return AnsiStyle.yellow(" Usage: /plugin info <plugin-id>");
|
||||
}
|
||||
|
||||
PluginInfo info = manager.findPlugin(pluginId);
|
||||
if (info == null) {
|
||||
return AnsiStyle.red(" ✗ 未找到插件: " + pluginId);
|
||||
return AnsiStyle.red(" ✗ Plugin not found: " + pluginId);
|
||||
}
|
||||
|
||||
Plugin p = info.plugin();
|
||||
@@ -245,11 +245,11 @@ public class PluginCommand implements SlashCommand {
|
||||
*/
|
||||
private String usageHelp() {
|
||||
return AnsiStyle.dim("""
|
||||
用法:
|
||||
/plugin 列出所有插件
|
||||
/plugin load <path> 加载 JAR 插件
|
||||
/plugin unload <id> 卸载插件
|
||||
/plugin reload 重载所有插件
|
||||
/plugin info <id> 查看插件详情""");
|
||||
Usage:
|
||||
/plugin List all plugins
|
||||
/plugin load <path> Load JAR plugin
|
||||
/plugin unload <id> Unload plugin
|
||||
/plugin reload Reload all plugins
|
||||
/plugin info <id> View plugin details""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ public class ResumeCommand implements SlashCommand {
|
||||
args = args == null ? "" : args.strip();
|
||||
|
||||
if (conversations.isEmpty()) {
|
||||
return AnsiStyle.yellow(" ⚠ 没有已保存的对话\n")
|
||||
+ AnsiStyle.dim(" 对话在退出时自动保存到 ~/.claude-code-java/conversations/");
|
||||
return AnsiStyle.yellow(" ⚠ No saved conversations\n")
|
||||
+ AnsiStyle.dim(" Conversations are auto-saved on exit to ~/.claude-code-java/conversations/");
|
||||
}
|
||||
|
||||
// /resume list —— 列出所有对话
|
||||
@@ -56,10 +56,10 @@ public class ResumeCommand implements SlashCommand {
|
||||
try {
|
||||
index = Integer.parseInt(args) - 1;
|
||||
if (index < 0 || index >= conversations.size()) {
|
||||
return AnsiStyle.red(" ✗ 无效序号(范围 1-" + conversations.size() + ")");
|
||||
return AnsiStyle.red(" ✗ Invalid index (range 1-" + conversations.size() + ")");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return AnsiStyle.yellow(" ⚠ 用法: /resume [序号] 或 /resume list");
|
||||
return AnsiStyle.yellow(" ⚠ Usage: /resume [index] or /resume list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ResumeCommand implements SlashCommand {
|
||||
List<Message> messages = persistence.loadFromFile(file);
|
||||
|
||||
if (messages.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 加载对话失败: " + summary.filename());
|
||||
return AnsiStyle.red(" ✗ Failed to load conversation: " + summary.filename());
|
||||
}
|
||||
|
||||
// 替换当前消息历史
|
||||
@@ -77,12 +77,12 @@ public class ResumeCommand implements SlashCommand {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.green(" ✓ 对话已恢复\n"));
|
||||
sb.append(AnsiStyle.green(" ✓ Conversation restored\n"));
|
||||
sb.append(" ").append("─".repeat(50)).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("摘要: ")).append(summary.summary()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("时间: ")).append(summary.savedAt()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("消息数: ")).append(summary.messageCount()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("目录: ")).append(AnsiStyle.dim(summary.workingDir())).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("Summary: ")).append(summary.summary()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("Time: ")).append(summary.savedAt()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("Messages: ")).append(summary.messageCount()).append("\n");
|
||||
sb.append(" ").append(AnsiStyle.bold("Dir: ")).append(AnsiStyle.dim(summary.workingDir())).append("\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -104,10 +104,10 @@ public class ResumeCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
if (conversations.size() > maxShow) {
|
||||
sb.append(AnsiStyle.dim("\n ... 还有 " + (conversations.size() - maxShow) + " 个对话\n"));
|
||||
sb.append(AnsiStyle.dim("\n ... and " + (conversations.size() - maxShow) + " more conversations\n"));
|
||||
}
|
||||
|
||||
sb.append(AnsiStyle.dim("\n 使用 /resume [序号] 恢复指定对话\n"));
|
||||
sb.append(AnsiStyle.dim("\n Use /resume [index] to restore a conversation\n"));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ReviewCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用,无法执行代码审查。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform code review.");
|
||||
}
|
||||
|
||||
String trimmedArgs = args != null ? args.trim() : "";
|
||||
@@ -55,16 +55,16 @@ public class ReviewCommand implements SlashCommand {
|
||||
|
||||
// 检查 diff 是否为空
|
||||
if (diffOutput.isBlank()) {
|
||||
return AnsiStyle.yellow(" ⚠ 没有检测到代码变更。") + "\n"
|
||||
+ AnsiStyle.dim(" 提示: 使用 --staged 审查已暂存的变更,或指定文件路径。");
|
||||
return AnsiStyle.yellow(" ⚠ No code changes detected.") + "\n"
|
||||
+ AnsiStyle.dim(" Tip: Use --staged to review staged changes, or specify a file path.");
|
||||
}
|
||||
|
||||
// 构建审查提示
|
||||
String reviewPrompt = buildReviewPrompt(trimmedArgs, diffOutput);
|
||||
|
||||
// 输出审查进行中的提示
|
||||
context.out().println(AnsiStyle.cyan(" 🔍 正在审查代码变更..."));
|
||||
context.out().println(AnsiStyle.dim(" diff 大小: " + diffOutput.lines().count() + " 行"));
|
||||
context.out().println(AnsiStyle.cyan(" 🔍 Reviewing code changes..."));
|
||||
context.out().println(AnsiStyle.dim(" diff size: " + diffOutput.lines().count() + " lines"));
|
||||
context.out().println();
|
||||
|
||||
// 发送给 AI 进行审查
|
||||
@@ -72,8 +72,8 @@ public class ReviewCommand implements SlashCommand {
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ 代码审查失败: " + e.getMessage()) + "\n"
|
||||
+ AnsiStyle.dim(" 请确保当前目录是一个 Git 仓库。");
|
||||
return AnsiStyle.red(" ✗ Code review failed: " + e.getMessage()) + "\n"
|
||||
+ AnsiStyle.dim(" Please ensure the current directory is a Git repository.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public class ReviewCommand implements SlashCommand {
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new RuntimeException("git diff 执行失败 (exit=" + exitCode + "): " + errorOutput);
|
||||
throw new RuntimeException("git diff failed (exit=" + exitCode + "): " + errorOutput);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
@@ -38,21 +38,21 @@ public class RewindCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable.");
|
||||
}
|
||||
|
||||
// 解析要回滚的消息对数量
|
||||
int pairsToRemove = parseRewindCount(args);
|
||||
if (pairsToRemove < 0) {
|
||||
return AnsiStyle.red(" ✗ 无效的回滚数量。请输入一个正整数。") + "\n"
|
||||
+ AnsiStyle.dim(" 用法: /rewind [n] (n 为要移除的消息对数,默认为 1)");
|
||||
return AnsiStyle.red(" ✗ Invalid rewind count. Please enter a positive integer.") + "\n"
|
||||
+ AnsiStyle.dim(" Usage: /rewind [n] (n = number of message pairs to remove, default 1)");
|
||||
}
|
||||
|
||||
List<Message> currentHistory = context.agentLoop().getMessageHistory();
|
||||
int currentSize = currentHistory.size();
|
||||
|
||||
if (currentSize == 0) {
|
||||
return AnsiStyle.yellow(" ⚠ 对话历史为空,无法回滚。");
|
||||
return AnsiStyle.yellow(" ⚠ Conversation history is empty, cannot rewind.");
|
||||
}
|
||||
|
||||
// 计算需要移除的消息数量
|
||||
@@ -61,8 +61,8 @@ public class RewindCommand implements SlashCommand {
|
||||
// 如果要移除的消息数超过总消息数,则清除所有消息
|
||||
if (messagesToRemove >= currentSize) {
|
||||
context.agentLoop().replaceHistory(new ArrayList<>());
|
||||
return AnsiStyle.green(" ✓ 已清除全部 " + currentSize + " 条消息。") + "\n"
|
||||
+ AnsiStyle.dim(" (请求移除 " + pairsToRemove + " 对,实际清除全部消息)");
|
||||
return AnsiStyle.green(" ✓ Cleared all " + currentSize + " messages.") + "\n"
|
||||
+ AnsiStyle.dim(" (Requested " + pairsToRemove + " pairs, cleared all messages)");
|
||||
}
|
||||
|
||||
// 截断消息历史
|
||||
@@ -72,9 +72,9 @@ public class RewindCommand implements SlashCommand {
|
||||
|
||||
// 构建结果输出
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.green(" ✓ 已回滚 " + pairsToRemove + " 个消息对")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" 移除: " + messagesToRemove + " 条消息")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" 剩余: " + newSize + " 条消息")).append("\n");
|
||||
sb.append(AnsiStyle.green(" ✓ Rewound " + pairsToRemove + " message pairs")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" Removed: " + messagesToRemove + " messages")).append("\n");
|
||||
sb.append(AnsiStyle.dim(" Remaining: " + newSize + " messages")).append("\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class SecurityReviewCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用,无法执行安全审查。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform security review.");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -53,13 +53,13 @@ public class SecurityReviewCommand implements SlashCommand {
|
||||
String diffOutput = executeGitDiff();
|
||||
|
||||
if (diffOutput.isBlank()) {
|
||||
return AnsiStyle.yellow(" ⚠ 没有检测到代码变更。") + "\n"
|
||||
+ AnsiStyle.dim(" git diff HEAD 未返回任何内容。请确认是否有提交记录。");
|
||||
return AnsiStyle.yellow(" ⚠ No code changes detected.") + "\n"
|
||||
+ AnsiStyle.dim(" git diff HEAD returned nothing. Please verify there are commits.");
|
||||
}
|
||||
|
||||
// 输出审查进行中的提示
|
||||
context.out().println(AnsiStyle.magenta(" 🔒 正在进行安全审查..."));
|
||||
context.out().println(AnsiStyle.dim(" diff 大小: " + diffOutput.lines().count() + " 行"));
|
||||
context.out().println(AnsiStyle.magenta(" 🔒 Performing security review..."));
|
||||
context.out().println(AnsiStyle.dim(" diff size: " + diffOutput.lines().count() + " lines"));
|
||||
context.out().println();
|
||||
|
||||
// 构建安全审查提示词
|
||||
@@ -70,8 +70,8 @@ public class SecurityReviewCommand implements SlashCommand {
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
return AnsiStyle.red(" ✗ 安全审查失败: " + e.getMessage()) + "\n"
|
||||
+ AnsiStyle.dim(" 请确保当前目录是一个 Git 仓库且有提交历史。");
|
||||
return AnsiStyle.red(" ✗ Security review failed: " + e.getMessage()) + "\n"
|
||||
+ AnsiStyle.dim(" Please ensure the current directory is a Git repository with commit history.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class SecurityReviewCommand implements SlashCommand {
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new RuntimeException("git diff HEAD 执行失败 (exit=" + exitCode + "): " + errorOutput);
|
||||
throw new RuntimeException("git diff HEAD failed (exit=" + exitCode + "): " + errorOutput);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
@@ -37,20 +37,20 @@ public class SkillsCommand implements SlashCommand {
|
||||
sb.append(" ").append("─".repeat(50)).append("\n\n");
|
||||
|
||||
if (skills.isEmpty()) {
|
||||
sb.append(AnsiStyle.dim(" (无可用技能)\n\n"));
|
||||
sb.append(AnsiStyle.dim(" 技能文件放置位置:\n"));
|
||||
sb.append(AnsiStyle.dim(" 用户级: ~/.claude/skills/*.md\n"));
|
||||
sb.append(AnsiStyle.dim(" 项目级: ./.claude/skills/*.md\n"));
|
||||
sb.append(AnsiStyle.dim(" 命令级: ./.claude/commands/*.md\n"));
|
||||
sb.append(AnsiStyle.dim(" (No available skills)\n\n"));
|
||||
sb.append(AnsiStyle.dim(" Skill file locations:\n"));
|
||||
sb.append(AnsiStyle.dim(" User: ~/.claude/skills/*.md\n"));
|
||||
sb.append(AnsiStyle.dim(" Project: ./.claude/skills/*.md\n"));
|
||||
sb.append(AnsiStyle.dim(" Command: ./.claude/commands/*.md\n"));
|
||||
} else {
|
||||
for (SkillLoader.Skill skill : skills) {
|
||||
sb.append(" ").append(AnsiStyle.cyan("▸ ")).append(AnsiStyle.bold(skill.name()));
|
||||
|
||||
// 来源标签
|
||||
String sourceLabel = switch (skill.source()) {
|
||||
case "user" -> AnsiStyle.dim(" [用户级]");
|
||||
case "project" -> AnsiStyle.dim(" [项目级]");
|
||||
case "command" -> AnsiStyle.dim(" [命令]");
|
||||
case "user" -> AnsiStyle.dim(" [user]");
|
||||
case "project" -> AnsiStyle.dim(" [project]");
|
||||
case "command" -> AnsiStyle.dim(" [command]");
|
||||
default -> AnsiStyle.dim(" [" + skill.source() + "]");
|
||||
};
|
||||
sb.append(sourceLabel).append("\n");
|
||||
@@ -64,7 +64,7 @@ public class SkillsCommand implements SlashCommand {
|
||||
sb.append(" ").append(AnsiStyle.dim("File: " + skill.filePath())).append("\n");
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append(AnsiStyle.dim(" 共 " + skills.size() + " 个技能\n"));
|
||||
sb.append(AnsiStyle.dim(" Total " + skills.size() + " skills\n"));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -38,12 +38,12 @@ public class StatsCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable.");
|
||||
}
|
||||
|
||||
TokenTracker tracker = context.agentLoop().getTokenTracker();
|
||||
if (tracker == null) {
|
||||
return AnsiStyle.yellow(" ⚠ Token 追踪器不可用。");
|
||||
return AnsiStyle.yellow(" ⚠ Token tracker unavailable.");
|
||||
}
|
||||
|
||||
// 收集统计数据
|
||||
|
||||
@@ -43,7 +43,7 @@ public class TagCommand implements SlashCommand {
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() == null) {
|
||||
return AnsiStyle.red(" ✗ AgentLoop 不可用。");
|
||||
return AnsiStyle.red(" ✗ AgentLoop unavailable.");
|
||||
}
|
||||
|
||||
String trimmedArgs = args != null ? args.trim() : "";
|
||||
@@ -78,7 +78,7 @@ public class TagCommand implements SlashCommand {
|
||||
*/
|
||||
private String createTag(String tagName, CommandContext context) {
|
||||
if (tagName.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 请指定标签名称。");
|
||||
return AnsiStyle.red(" ✗ Please specify tag name.");
|
||||
}
|
||||
|
||||
// 标签名称不能与子命令冲突(虽然 list/goto 已在 switch 中处理)
|
||||
@@ -88,9 +88,9 @@ public class TagCommand implements SlashCommand {
|
||||
boolean isOverwrite = tags.containsKey(tagName);
|
||||
tags.put(tagName, new TagInfo(position, timestamp));
|
||||
|
||||
String action = isOverwrite ? "已更新" : "已创建";
|
||||
return AnsiStyle.green(" ✓ 标签" + action + ": ") + AnsiStyle.bold(tagName) + "\n"
|
||||
+ AnsiStyle.dim(" 位置: 第 " + position + " 条消息 时间: " + timestamp);
|
||||
String action = isOverwrite ? "updated" : "created";
|
||||
return AnsiStyle.green(" ✓ Tag " + action + ": ") + AnsiStyle.bold(tagName) + "\n"
|
||||
+ AnsiStyle.dim(" Position: message " + position + " Time: " + timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,8 +101,8 @@ public class TagCommand implements SlashCommand {
|
||||
*/
|
||||
private String listTags(CommandContext context) {
|
||||
if (tags.isEmpty()) {
|
||||
return AnsiStyle.dim(" 没有保存的标签。") + "\n"
|
||||
+ AnsiStyle.dim(" 使用 /tag <name> 为当前位置打标签。");
|
||||
return AnsiStyle.dim(" No saved tags.") + "\n"
|
||||
+ AnsiStyle.dim(" Use /tag <name> to tag the current position.");
|
||||
}
|
||||
|
||||
int currentPosition = context.agentLoop().getMessageHistory().size();
|
||||
@@ -127,7 +127,7 @@ public class TagCommand implements SlashCommand {
|
||||
}
|
||||
|
||||
sb.append("\n")
|
||||
.append(AnsiStyle.dim(" 当前位置: " + currentPosition + " 条消息 | 共 " + tags.size() + " 个标签"))
|
||||
.append(AnsiStyle.dim(" Current position: " + currentPosition + " messages | Total " + tags.size() + " tags"))
|
||||
.append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -144,14 +144,14 @@ public class TagCommand implements SlashCommand {
|
||||
*/
|
||||
private String gotoTag(String tagName, CommandContext context) {
|
||||
if (tagName.isEmpty()) {
|
||||
return AnsiStyle.red(" ✗ 请指定标签名称。") + "\n"
|
||||
+ AnsiStyle.dim(" 用法: /tag goto <name>");
|
||||
return AnsiStyle.red(" ✗ Please specify tag name.") + "\n"
|
||||
+ AnsiStyle.dim(" Usage: /tag goto <name>");
|
||||
}
|
||||
|
||||
TagInfo info = tags.get(tagName);
|
||||
if (info == null) {
|
||||
return AnsiStyle.red(" ✗ 标签不存在: " + tagName) + "\n"
|
||||
+ AnsiStyle.dim(" 使用 /tag list 查看所有可用标签。");
|
||||
return AnsiStyle.red(" ✗ Tag not found: " + tagName) + "\n"
|
||||
+ AnsiStyle.dim(" Use /tag list to see all available tags.");
|
||||
}
|
||||
|
||||
List<Message> currentHistory = context.agentLoop().getMessageHistory();
|
||||
@@ -159,8 +159,8 @@ public class TagCommand implements SlashCommand {
|
||||
int targetPosition = info.position();
|
||||
|
||||
if (targetPosition >= currentSize) {
|
||||
return AnsiStyle.yellow(" ⚠ 标签位置 (" + targetPosition + ") 不小于当前消息数 ("
|
||||
+ currentSize + "),无需回溯。");
|
||||
return AnsiStyle.yellow(" ⚠ Tag position (" + targetPosition + ") is not less than current message count ("
|
||||
+ currentSize + "), no rewind needed.");
|
||||
}
|
||||
|
||||
// 截断到标签位置
|
||||
@@ -168,8 +168,8 @@ public class TagCommand implements SlashCommand {
|
||||
context.agentLoop().replaceHistory(truncated);
|
||||
|
||||
int removedCount = currentSize - targetPosition;
|
||||
return AnsiStyle.green(" ✓ 已回溯到标签: ") + AnsiStyle.bold(tagName) + "\n"
|
||||
+ AnsiStyle.dim(" 移除了 " + removedCount + " 条消息,当前消息数: " + targetPosition);
|
||||
return AnsiStyle.green(" ✓ Rewound to tag: ") + AnsiStyle.bold(tagName) + "\n"
|
||||
+ AnsiStyle.dim(" Removed " + removedCount + " messages, current count: " + targetPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,10 +179,10 @@ public class TagCommand implements SlashCommand {
|
||||
*/
|
||||
private String showUsage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.bold("\n 🏷️ Tag — 对话位置标签\n\n"));
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag <name>")).append(" 为当前位置打标签\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag list")).append(" 列出所有标签\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag goto <name>")).append(" 回溯到指定标签位置\n");
|
||||
sb.append(AnsiStyle.bold("\n 🏷️ Tag — Conversation position tags\n\n"));
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag <name>")).append(" Tag current position\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag list")).append(" List all tags\n");
|
||||
sb.append(" ").append(AnsiStyle.cyan("/tag goto <name>")).append(" Rewind to tag position\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class AppConfig {
|
||||
try {
|
||||
manager.loadFromConfig();
|
||||
} catch (Exception e) {
|
||||
log.warn("MCP 配置加载失败(可忽略): {}", e.getMessage());
|
||||
log.warn("MCP config loading failed (ignorable): {}", e.getMessage());
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
@@ -168,10 +168,10 @@ public class AppConfig {
|
||||
@Qualifier("anthropicChatModel") ChatModel anthropicModel) {
|
||||
|
||||
if ("anthropic".equalsIgnoreCase(provider)) {
|
||||
log.info("使用 Anthropic 原生 API");
|
||||
log.info("Using Anthropic native API");
|
||||
return anthropicModel;
|
||||
} else {
|
||||
log.info("使用 OpenAI 兼容 API");
|
||||
log.info("Using OpenAI compatible API");
|
||||
return openAiModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class ClaudeMdLoader {
|
||||
.sorted()
|
||||
.forEach(p -> loadFile(p, "rule").ifPresent(sections::add));
|
||||
} catch (IOException e) {
|
||||
log.debug("加载规则目录失败: {}", e.getMessage());
|
||||
log.debug("Failed to load rules directory: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +80,11 @@ public class ClaudeMdLoader {
|
||||
try {
|
||||
String content = Files.readString(path, StandardCharsets.UTF_8).strip();
|
||||
if (!content.isEmpty()) {
|
||||
log.debug("已加载 {} 级 CLAUDE.md: {}", level, path);
|
||||
log.debug("Loaded {} level CLAUDE.md: {}", level, path);
|
||||
return java.util.Optional.of(content);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("读取 {} 失败: {}", path, e.getMessage());
|
||||
log.warn("Failed to read {}: {}", path, e.getMessage());
|
||||
}
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GitContext {
|
||||
*/
|
||||
public GitContext collect() {
|
||||
if (!isGitRepo) {
|
||||
log.debug("当前目录不是 Git 仓库: {}", projectDir);
|
||||
log.debug("Current directory is not a Git repository: {}", projectDir);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public class GitContext {
|
||||
return output.toString().stripTrailing();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.debug("Git 命令执行失败: {}", e.getMessage());
|
||||
log.debug("Git command execution failed: {}", e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class SkillLoader {
|
||||
Path commandsDir = projectDir.resolve(".claude").resolve("commands");
|
||||
loadFromDirectory(commandsDir, "command");
|
||||
|
||||
log.debug("共加载 {} 个技能", skills.size());
|
||||
log.debug("Loaded {} skills in total", skills.size());
|
||||
return Collections.unmodifiableList(skills);
|
||||
}
|
||||
|
||||
@@ -77,13 +77,13 @@ public class SkillLoader {
|
||||
try {
|
||||
Skill skill = parseSkillFile(p, source);
|
||||
skills.add(skill);
|
||||
log.debug("加载技能: {} [{}] from {}", skill.name(), source, p.getFileName());
|
||||
log.debug("Loaded skill: {} [{}] from {}", skill.name(), source, p.getFileName());
|
||||
} catch (IOException e) {
|
||||
log.warn("加载技能文件失败: {}: {}", p, e.getMessage());
|
||||
log.warn("Failed to load skill file: {}: {}", p, e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.debug("扫描技能目录失败: {}: {}", dir, e.getMessage());
|
||||
log.debug("Failed to scan skill directory: {}: {}", dir, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ public class AgentLoop {
|
||||
|
||||
while (iteration < MAX_ITERATIONS) {
|
||||
iteration++;
|
||||
log.debug("Agent 循环 第{}轮 ({})", iteration, streaming ? "流式" : "阻塞");
|
||||
log.debug("Agent loop iteration {} ({})", iteration, streaming ? "streaming" : "blocking");
|
||||
|
||||
Prompt prompt = new Prompt(List.copyOf(messageHistory), options);
|
||||
|
||||
@@ -177,7 +177,7 @@ public class AgentLoop {
|
||||
|
||||
// 无工具调用 → 结束
|
||||
if (!result.assistant.hasToolCalls()) {
|
||||
log.debug("无工具调用,循环结束(共{}轮)", iteration);
|
||||
log.debug("No tool calls, loop ended (total {} iterations)", iteration);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -186,8 +186,8 @@ public class AgentLoop {
|
||||
}
|
||||
|
||||
if (iteration >= MAX_ITERATIONS) {
|
||||
log.warn("Agent 循环已达最大迭代次数 {},强制终止", MAX_ITERATIONS);
|
||||
lastAssistantText += "\n\n[WARNING: 达到最大循环次数限制]";
|
||||
log.warn("Agent loop reached max iterations {}, force stopping", MAX_ITERATIONS);
|
||||
lastAssistantText += "\n\n[WARNING: Maximum loop iteration limit reached]";
|
||||
}
|
||||
|
||||
return lastAssistantText;
|
||||
@@ -256,7 +256,7 @@ public class AgentLoop {
|
||||
|
||||
} catch (Exception e) {
|
||||
// 流式调用失败 → 降级到阻塞模式
|
||||
log.warn("流式调用失败,降级到阻塞模式: {}", e.getMessage());
|
||||
log.warn("Streaming call failed, falling back to blocking mode: {}", e.getMessage());
|
||||
return blockingIteration(prompt);
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public class AgentLoop {
|
||||
// PreToolUse Hook
|
||||
var preHookCtx = new HookManager.HookContext(toolName, parsedArgs);
|
||||
if (hookManager.execute(HookManager.HookType.PRE_TOOL_USE, preHookCtx) == HookManager.HookResult.ABORT) {
|
||||
log.info("[{}] PreToolUse Hook 中止了执行", toolName);
|
||||
log.info("[{}] PreToolUse Hook aborted execution", toolName);
|
||||
toolResponses.add(new ToolResponseMessage.ToolResponse(callId, toolName, "Aborted by hook"));
|
||||
continue;
|
||||
}
|
||||
@@ -313,12 +313,12 @@ public class AgentLoop {
|
||||
if (permitted) {
|
||||
result = adapter.call(toolArgs);
|
||||
} else {
|
||||
result = "Permission denied: 用户拒绝了此操作";
|
||||
log.info("[{}] 用户拒绝工具执行", toolName);
|
||||
result = "Permission denied: User rejected this operation";
|
||||
log.info("[{}] User denied tool execution", toolName);
|
||||
}
|
||||
} else {
|
||||
result = "Error: Unknown tool '" + toolName + "'";
|
||||
log.warn("未知工具: {}", toolName);
|
||||
log.warn("Unknown tool: {}", toolName);
|
||||
}
|
||||
|
||||
// PostToolUse Hook
|
||||
@@ -433,7 +433,7 @@ public class AgentLoop {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// thinking 提取失败不影响主流程
|
||||
log.debug("Thinking 内容提取异常(可忽略): {}", e.getMessage());
|
||||
log.debug("Thinking content extraction exception (can be ignored): {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ConversationPersistence {
|
||||
try {
|
||||
Files.createDirectories(conversationsDir);
|
||||
} catch (IOException e) {
|
||||
log.warn("无法创建对话存储目录: {}", e.getMessage());
|
||||
log.warn("Failed to create conversation storage directory: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ public class ConversationPersistence {
|
||||
);
|
||||
|
||||
MAPPER.writeValue(file.toFile(), conv);
|
||||
log.info("对话已保存: {}", file.getFileName());
|
||||
log.info("Conversation saved: {}", file.getFileName());
|
||||
return file;
|
||||
} catch (IOException e) {
|
||||
log.error("保存对话失败: {}", e.getMessage());
|
||||
log.error("Failed to save conversation: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public class ConversationPersistence {
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
log.error("加载对话失败: {}", e.getMessage());
|
||||
log.error("Failed to load conversation: {}", e.getMessage());
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -131,11 +131,11 @@ public class ConversationPersistence {
|
||||
conv.messages().size()
|
||||
));
|
||||
} catch (IOException e) {
|
||||
log.debug("跳过无效对话文件: {}", file.getFileName());
|
||||
log.debug("Skipping invalid conversation file: {}", file.getFileName());
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.warn("列出对话失败: {}", e.getMessage());
|
||||
log.warn("Failed to list conversations: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return summaries;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class HookManager {
|
||||
*/
|
||||
public void register(HookType type, String name, HookHandler handler) {
|
||||
hooks.add(new HookRegistration(type, name, handler, 0));
|
||||
log.debug("注册 Hook: {} [{}]", name, type);
|
||||
log.debug("Registered Hook: {} [{}]", name, type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ public class HookManager {
|
||||
*/
|
||||
public void register(HookType type, String name, HookHandler handler, int priority) {
|
||||
hooks.add(new HookRegistration(type, name, handler, priority));
|
||||
log.debug("注册 Hook: {} [{}] priority={}", name, type, priority);
|
||||
log.debug("Registered Hook: {} [{}] priority={}", name, type, priority);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,15 +72,15 @@ public class HookManager {
|
||||
|
||||
for (HookRegistration reg : matching) {
|
||||
try {
|
||||
log.debug("执行 Hook: {} [{}]", reg.name(), type);
|
||||
log.debug("Executing Hook: {} [{}]", reg.name(), type);
|
||||
HookResult result = reg.handler().handle(context);
|
||||
|
||||
if (result == HookResult.ABORT) {
|
||||
log.info("Hook [{}] 中止了操作", reg.name());
|
||||
log.info("Hook [{}] aborted the operation", reg.name());
|
||||
return HookResult.ABORT;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Hook [{}] 执行异常: {}", reg.name(), e.getMessage());
|
||||
log.warn("Hook [{}] execution exception: {}", reg.name(), e.getMessage());
|
||||
// Hook 异常不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,8 @@ public class TaskManager {
|
||||
* @throws NullPointerException 若 description 或 work 为 null
|
||||
*/
|
||||
public String createTask(String description, Callable<String> work) {
|
||||
Objects.requireNonNull(description, "任务描述不能为 null");
|
||||
Objects.requireNonNull(work, "任务工作体不能为 null");
|
||||
Objects.requireNonNull(description, "Task description cannot be null");
|
||||
Objects.requireNonNull(work, "Task work body cannot be null");
|
||||
|
||||
String taskId = generateId();
|
||||
Instant now = Instant.now();
|
||||
@@ -162,7 +162,7 @@ public class TaskManager {
|
||||
*/
|
||||
public String createTask(String description, Callable<String> work,
|
||||
Map<String, String> metadata) {
|
||||
Objects.requireNonNull(metadata, "元数据不能为 null");
|
||||
Objects.requireNonNull(metadata, "Metadata cannot be null");
|
||||
String taskId = createTask(description, work);
|
||||
// 把元数据补充进去(创建时尚处于 PENDING/RUNNING 初期阶段)
|
||||
tasks.computeIfPresent(taskId, (id, old) -> new TaskInfo(
|
||||
@@ -194,7 +194,7 @@ public class TaskManager {
|
||||
* @return 生成的任务 ID
|
||||
*/
|
||||
public String createManualTask(String description, Map<String, String> metadata) {
|
||||
Objects.requireNonNull(description, "任务描述不能为 null");
|
||||
Objects.requireNonNull(description, "Task description cannot be null");
|
||||
|
||||
String taskId = generateId();
|
||||
Instant now = Instant.now();
|
||||
@@ -332,14 +332,14 @@ public class TaskManager {
|
||||
*/
|
||||
public String getSummary() {
|
||||
if (tasks.isEmpty()) {
|
||||
return "当前没有任何任务。";
|
||||
return "No tasks at the moment.";
|
||||
}
|
||||
|
||||
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");
|
||||
sb.append("Task summary (total ").append(tasks.size()).append("):\n");
|
||||
for (TaskStatus status : TaskStatus.values()) {
|
||||
long count = counts.getOrDefault(status, 0L);
|
||||
if (count > 0) {
|
||||
|
||||
@@ -61,8 +61,8 @@ public class McpClient implements AutoCloseable {
|
||||
* @param transport 传输层实现
|
||||
*/
|
||||
public McpClient(String serverName, McpTransport transport) {
|
||||
this.serverName = Objects.requireNonNull(serverName, "服务器名称不能为空");
|
||||
this.transport = Objects.requireNonNull(transport, "传输层不能为空");
|
||||
this.serverName = Objects.requireNonNull(serverName, "Server name cannot be null");
|
||||
this.transport = Objects.requireNonNull(transport, "Transport cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +79,7 @@ public class McpClient implements AutoCloseable {
|
||||
* @throws McpException 初始化失败
|
||||
*/
|
||||
public void initialize() throws McpException {
|
||||
log.info("正在初始化 MCP 服务器 '{}'...", serverName);
|
||||
log.info("Initializing MCP server '{}'...", serverName);
|
||||
|
||||
// 1. 发送 initialize 请求
|
||||
int initId = nextId();
|
||||
@@ -101,7 +101,7 @@ public class McpClient implements AutoCloseable {
|
||||
try {
|
||||
response = transport.sendRequest(MAPPER.writeValueAsString(initRequest));
|
||||
} catch (Exception e) {
|
||||
throw new McpException("MCP initialize 请求失败: " + e.getMessage(), e);
|
||||
throw new McpException("MCP initialize request failed: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 2. 解析服务器能力
|
||||
@@ -111,9 +111,9 @@ public class McpClient implements AutoCloseable {
|
||||
serverInfo = result.get("serverInfo");
|
||||
String serverVersion = result.has("protocolVersion")
|
||||
? result.get("protocolVersion").asText() : "unknown";
|
||||
log.info("MCP 服务器 '{}' 协议版本: {}", serverName, serverVersion);
|
||||
log.info("MCP server '{}' protocol version: {}", serverName, serverVersion);
|
||||
if (serverInfo != null) {
|
||||
log.info("MCP 服务器信息: {}", serverInfo);
|
||||
log.info("MCP server info: {}", serverInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public class McpClient implements AutoCloseable {
|
||||
try {
|
||||
transport.sendNotification(MAPPER.writeValueAsString(initializedNotif));
|
||||
} catch (Exception e) {
|
||||
throw new McpException("发送 initialized 通知失败: " + e.getMessage(), e);
|
||||
throw new McpException("Failed to send initialized notification: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 4. 发现工具
|
||||
@@ -135,7 +135,7 @@ public class McpClient implements AutoCloseable {
|
||||
discoverResources();
|
||||
|
||||
initialized = true;
|
||||
log.info("MCP 服务器 '{}' 初始化完成: {} 个工具, {} 个资源",
|
||||
log.info("MCP server '{}' initialization complete: {} tools, {} resources",
|
||||
serverName, tools.size(), resources.size());
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public class McpClient implements AutoCloseable {
|
||||
&& serverCapabilities.get("tools").isObject()) {
|
||||
// 服务器声明支持工具
|
||||
} else if (serverCapabilities != null && !serverCapabilities.has("tools")) {
|
||||
log.debug("MCP 服务器 '{}' 未声明 tools 能力,尝试发现工具", serverName);
|
||||
log.debug("MCP server '{}' did not declare tools capability, attempting discovery", serverName);
|
||||
}
|
||||
|
||||
int id = nextId();
|
||||
@@ -173,19 +173,19 @@ public class McpClient implements AutoCloseable {
|
||||
JsonNode inputSchema = toolNode.get("inputSchema");
|
||||
|
||||
tools.put(name, new McpTool(name, description, inputSchema));
|
||||
log.debug("发现 MCP 工具: {} - {}", name, description);
|
||||
log.debug("Discovered MCP tool: {} - {}", name, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (McpException e) {
|
||||
// tools/list 可能不被支持,记录警告但不中断初始化
|
||||
if (e.isJsonRpcError() && e.getErrorCode() == -32601) {
|
||||
log.debug("MCP 服务器 '{}' 不支持 tools/list", serverName);
|
||||
log.debug("MCP server '{}' does not support tools/list", serverName);
|
||||
} else {
|
||||
log.warn("发现 MCP 工具失败: {}", e.getMessage());
|
||||
log.warn("Failed to discover MCP tools: {}", e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("发现 MCP 工具时序列化异常: {}", e.getMessage());
|
||||
log.warn("MCP tool discovery serialization exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public class McpClient implements AutoCloseable {
|
||||
&& serverCapabilities.get("resources").isObject()) {
|
||||
// 服务器声明支持资源
|
||||
} else if (serverCapabilities != null && !serverCapabilities.has("resources")) {
|
||||
log.debug("MCP 服务器 '{}' 未声明 resources 能力,跳过资源发现", serverName);
|
||||
log.debug("MCP server '{}' did not declare resources capability, skipping discovery", serverName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -226,18 +226,18 @@ public class McpClient implements AutoCloseable {
|
||||
? resNode.get("mimeType").asText() : "text/plain";
|
||||
|
||||
resources.put(uri, new McpResource(uri, name, description, mimeType));
|
||||
log.debug("发现 MCP 资源: {} ({})", name, uri);
|
||||
log.debug("Discovered MCP resource: {} ({})", name, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (McpException e) {
|
||||
if (e.isJsonRpcError() && e.getErrorCode() == -32601) {
|
||||
log.debug("MCP 服务器 '{}' 不支持 resources/list", serverName);
|
||||
log.debug("MCP server '{}' does not support resources/list", serverName);
|
||||
} else {
|
||||
log.warn("发现 MCP 资源失败: {}", e.getMessage());
|
||||
log.warn("Failed to discover MCP resources: {}", e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("发现 MCP 资源时序列化异常: {}", e.getMessage());
|
||||
log.warn("MCP resource discovery serialization exception: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +251,10 @@ public class McpClient implements AutoCloseable {
|
||||
*/
|
||||
public String callTool(String toolName, Map<String, Object> arguments) throws McpException {
|
||||
if (!initialized) {
|
||||
throw new McpException("MCP 客户端尚未初始化");
|
||||
throw new McpException("MCP client not yet initialized");
|
||||
}
|
||||
if (!tools.containsKey(toolName)) {
|
||||
throw new McpException("MCP 工具不存在: " + toolName);
|
||||
throw new McpException("MCP tool does not exist: " + toolName);
|
||||
}
|
||||
|
||||
int id = nextId();
|
||||
@@ -269,7 +269,7 @@ public class McpClient implements AutoCloseable {
|
||||
);
|
||||
|
||||
try {
|
||||
log.debug("调用 MCP 工具: {} (参数: {})", toolName, arguments);
|
||||
log.debug("Calling MCP tool: {} (args: {})", toolName, arguments);
|
||||
JsonNode response = transport.sendRequest(MAPPER.writeValueAsString(request));
|
||||
JsonNode result = response.get("result");
|
||||
|
||||
@@ -292,7 +292,7 @@ public class McpClient implements AutoCloseable {
|
||||
|
||||
// 检查 isError 标志
|
||||
if (result.has("isError") && result.get("isError").asBoolean()) {
|
||||
throw new McpException("MCP 工具 '" + toolName + "' 执行出错: " + sb);
|
||||
throw new McpException("MCP tool '" + toolName + "' execution error: " + sb);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@@ -304,7 +304,7 @@ public class McpClient implements AutoCloseable {
|
||||
} catch (McpException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new McpException("调用 MCP 工具 '" + toolName + "' 失败: " + e.getMessage(), e);
|
||||
throw new McpException("Failed to call MCP tool '" + toolName + "': " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ public class McpClient implements AutoCloseable {
|
||||
*/
|
||||
public String readResource(String uri) throws McpException {
|
||||
if (!initialized) {
|
||||
throw new McpException("MCP 客户端尚未初始化");
|
||||
throw new McpException("MCP client not yet initialized");
|
||||
}
|
||||
|
||||
int id = nextId();
|
||||
@@ -329,7 +329,7 @@ public class McpClient implements AutoCloseable {
|
||||
);
|
||||
|
||||
try {
|
||||
log.debug("读取 MCP 资源: {}", uri);
|
||||
log.debug("Reading MCP resource: {}", uri);
|
||||
JsonNode response = transport.sendRequest(MAPPER.writeValueAsString(request));
|
||||
JsonNode result = response.get("result");
|
||||
|
||||
@@ -356,7 +356,7 @@ public class McpClient implements AutoCloseable {
|
||||
} catch (McpException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new McpException("读取 MCP 资源 '" + uri + "' 失败: " + e.getMessage(), e);
|
||||
throw new McpException("Failed to read MCP resource '" + uri + "': " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ public class McpClient implements AutoCloseable {
|
||||
tools.clear();
|
||||
resources.clear();
|
||||
transport.close();
|
||||
log.info("MCP 客户端 '{}' 已关闭", serverName);
|
||||
log.info("MCP client '{}' closed", serverName);
|
||||
}
|
||||
|
||||
/** 生成下一个 JSON-RPC 请求 ID */
|
||||
|
||||
@@ -69,17 +69,17 @@ public class McpManager implements AutoCloseable {
|
||||
// 项目级配置
|
||||
Path projectConfig = Path.of(System.getProperty("user.dir"), PROJECT_CONFIG);
|
||||
if (Files.exists(projectConfig)) {
|
||||
loadConfigFile(projectConfig, "项目级");
|
||||
loadConfigFile(projectConfig, "project");
|
||||
}
|
||||
|
||||
// 全局配置
|
||||
Path globalConfig = Path.of(System.getProperty("user.home"), GLOBAL_CONFIG);
|
||||
if (Files.exists(globalConfig)) {
|
||||
loadConfigFile(globalConfig, "全局");
|
||||
loadConfigFile(globalConfig, "global");
|
||||
}
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
log.debug("未找到 MCP 配置文件或无服务器定义");
|
||||
log.debug("No MCP config file found or no server definitions");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class McpManager implements AutoCloseable {
|
||||
* 加载单个配置文件中的 MCP 服务器定义。
|
||||
*/
|
||||
private void loadConfigFile(Path configPath, String label) {
|
||||
log.info("加载 {} MCP 配置: {}", label, configPath);
|
||||
log.info("Loading {} MCP config: {}", label, configPath);
|
||||
|
||||
try {
|
||||
String content = Files.readString(configPath);
|
||||
@@ -95,7 +95,7 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
JsonNode serversNode = root.get("servers");
|
||||
if (serversNode == null || !serversNode.isObject()) {
|
||||
log.warn("{} 配置文件缺少 'servers' 字段: {}", label, configPath);
|
||||
log.warn("{} config file missing 'servers' field: {}", label, configPath);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
// 跳过已存在的服务器(项目级优先于全局)
|
||||
if (clients.containsKey(name)) {
|
||||
log.debug("MCP 服务器 '{}' 已连接,跳过 {} 配置中的重复定义", name, label);
|
||||
log.debug("MCP server '{}' already connected, skipping duplicate definition in {} config", name, label);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -132,11 +132,11 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
connect(name, command, args, env);
|
||||
} catch (Exception e) {
|
||||
log.error("从配置连接 MCP 服务器 '{}' 失败: {}", name, e.getMessage());
|
||||
log.error("Failed to connect MCP server '{}' from config: {}", name, e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("读取 MCP 配置文件失败: {}", configPath, e);
|
||||
log.error("Failed to read MCP config file: {}", configPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,15 +154,15 @@ public class McpManager implements AutoCloseable {
|
||||
throws McpException {
|
||||
// 如果已存在,先断开
|
||||
if (clients.containsKey(name)) {
|
||||
log.info("MCP 服务器 '{}' 已存在,先断开旧连接", name);
|
||||
log.info("MCP server '{}' already exists, disconnecting old connection", name);
|
||||
try {
|
||||
disconnect(name);
|
||||
} catch (Exception e) {
|
||||
log.warn("断开旧 MCP 连接 '{}' 时异常: {}", name, e.getMessage());
|
||||
log.warn("Exception disconnecting old MCP connection '{}': {}", name, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("连接 MCP 服务器 '{}': {} {}", name, command, String.join(" ", args));
|
||||
log.info("Connecting MCP server '{}': {} {}", name, command, String.join(" ", args));
|
||||
|
||||
// 创建传输层并启动(确保初始化失败时清理资源)
|
||||
StdioTransport transport = new StdioTransport(command, args, env);
|
||||
@@ -179,7 +179,7 @@ public class McpManager implements AutoCloseable {
|
||||
e.addSuppressed(suppressed);
|
||||
}
|
||||
throw (e instanceof McpException mcp) ? mcp
|
||||
: new McpException("连接 MCP 服务器 '" + name + "' 失败: " + e.getMessage(), e);
|
||||
: new McpException("Failed to connect MCP server '" + name + "': " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 注册客户端
|
||||
@@ -189,13 +189,13 @@ public class McpManager implements AutoCloseable {
|
||||
for (McpClient.McpTool tool : client.getTools()) {
|
||||
String existingServer = toolToServer.get(tool.name());
|
||||
if (existingServer != null) {
|
||||
log.warn("MCP 工具名称冲突: '{}' 同时存在于服务器 '{}' 和 '{}',使用后者",
|
||||
log.warn("MCP tool name conflict: '{}' exists in both server '{}' and '{}', using latter",
|
||||
tool.name(), existingServer, name);
|
||||
}
|
||||
toolToServer.put(tool.name(), name);
|
||||
}
|
||||
|
||||
log.info("MCP 服务器 '{}' 连接成功", name);
|
||||
log.info("MCP server '{}' connected successfully", name);
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class McpManager implements AutoCloseable {
|
||||
public void disconnect(String name) throws McpException {
|
||||
McpClient client = clients.remove(name);
|
||||
if (client == null) {
|
||||
throw new McpException("MCP 服务器 '" + name + "' 不存在");
|
||||
throw new McpException("MCP server '" + name + "' does not exist");
|
||||
}
|
||||
|
||||
// 清理工具映射
|
||||
@@ -216,9 +216,9 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
try {
|
||||
client.close();
|
||||
log.info("MCP 服务器 '{}' 已断开", name);
|
||||
log.info("MCP server '{}' disconnected", name);
|
||||
} catch (Exception e) {
|
||||
throw new McpException("断开 MCP 服务器 '" + name + "' 时异常: " + e.getMessage(), e);
|
||||
throw new McpException("Exception disconnecting MCP server '" + name + "': " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ public class McpManager implements AutoCloseable {
|
||||
public String callTool(String toolName, Map<String, Object> args) throws McpException {
|
||||
String serverName = toolToServer.get(toolName);
|
||||
if (serverName == null) {
|
||||
throw new McpException("未找到 MCP 工具: " + toolName);
|
||||
throw new McpException("MCP tool not found: " + toolName);
|
||||
}
|
||||
return callTool(serverName, toolName, args);
|
||||
}
|
||||
@@ -320,10 +320,10 @@ public class McpManager implements AutoCloseable {
|
||||
throws McpException {
|
||||
McpClient client = clients.get(serverName);
|
||||
if (client == null) {
|
||||
throw new McpException("MCP 服务器 '" + serverName + "' 不存在");
|
||||
throw new McpException("MCP server '" + serverName + "' does not exist");
|
||||
}
|
||||
if (!client.isInitialized()) {
|
||||
throw new McpException("MCP 服务器 '" + serverName + "' 尚未初始化");
|
||||
throw new McpException("MCP server '" + serverName + "' not yet initialized");
|
||||
}
|
||||
return client.callTool(toolName, args);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ public class McpManager implements AutoCloseable {
|
||||
* 先断开所有已有连接,再重新加载配置。
|
||||
*/
|
||||
public void reload() {
|
||||
log.info("重新加载 MCP 配置...");
|
||||
log.info("Reloading MCP config...");
|
||||
|
||||
// 断开所有现有连接
|
||||
List<String> serverNames = new ArrayList<>(clients.keySet());
|
||||
@@ -352,13 +352,13 @@ public class McpManager implements AutoCloseable {
|
||||
try {
|
||||
disconnect(name);
|
||||
} catch (Exception e) {
|
||||
log.warn("重载时断开 MCP 服务器 '{}' 失败: {}", name, e.getMessage());
|
||||
log.warn("Failed to disconnect MCP server '{}' during reload: {}", name, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
loadFromConfig();
|
||||
log.info("MCP 配置重载完成: {} 个服务器已连接", clients.size());
|
||||
log.info("MCP config reload complete: {} servers connected", clients.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +368,7 @@ public class McpManager implements AutoCloseable {
|
||||
*/
|
||||
public String getSummary() {
|
||||
if (clients.isEmpty()) {
|
||||
return " 无已连接的 MCP 服务器";
|
||||
return " No connected MCP servers";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -378,14 +378,14 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
String status;
|
||||
if (client.isConnected() && client.isInitialized()) {
|
||||
status = "✅ 已连接";
|
||||
status = "✅ Connected";
|
||||
} else if (client.isConnected()) {
|
||||
status = "🔄 连接中";
|
||||
status = "🔄 Connecting";
|
||||
} else {
|
||||
status = "❌ 已断开";
|
||||
status = "❌ Disconnected";
|
||||
}
|
||||
|
||||
sb.append(String.format(" %-20s %s (%d 工具, %d 资源)%n",
|
||||
sb.append(String.format(" %-20s %s (%d tools, %d resources)%n",
|
||||
name, status, client.getTools().size(), client.getResources().size()));
|
||||
}
|
||||
return sb.toString().stripTrailing();
|
||||
@@ -393,7 +393,7 @@ public class McpManager implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
log.info("关闭所有 MCP 连接...");
|
||||
log.info("Closing all MCP connections...");
|
||||
List<Exception> errors = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, McpClient> entry : clients.entrySet()) {
|
||||
@@ -401,18 +401,18 @@ public class McpManager implements AutoCloseable {
|
||||
entry.getValue().close();
|
||||
} catch (Exception e) {
|
||||
errors.add(e);
|
||||
log.error("关闭 MCP 服务器 '{}' 时异常: {}", entry.getKey(), e.getMessage());
|
||||
log.error("Exception closing MCP server '{}': {}", entry.getKey(), e.getMessage());
|
||||
}
|
||||
}
|
||||
clients.clear();
|
||||
toolToServer.clear();
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
McpException ex = new McpException("关闭 MCP 管理器时有 " + errors.size() + " 个错误");
|
||||
McpException ex = new McpException("Errors closing MCP manager: " + errors.size() + " errors");
|
||||
errors.forEach(ex::addSuppressed);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
log.info("所有 MCP 连接已关闭");
|
||||
log.info("All MCP connections closed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class StdioTransport implements McpTransport {
|
||||
cmdList.add(command);
|
||||
cmdList.addAll(args);
|
||||
|
||||
log.info("启动 MCP 服务器进程: {}", String.join(" ", cmdList));
|
||||
log.info("Starting MCP server process: {}", String.join(" ", cmdList));
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdList);
|
||||
pb.redirectErrorStream(false); // stderr 单独处理
|
||||
@@ -108,10 +108,10 @@ public class StdioTransport implements McpTransport {
|
||||
stderrThread = Thread.ofVirtual().name("mcp-stdio-stderr").start(this::stderrLoop);
|
||||
|
||||
connected = true;
|
||||
log.info("MCP 服务器进程已启动 (PID: {})", process.pid());
|
||||
log.info("MCP server process started (PID: {})", process.pid());
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new McpException("启动 MCP 服务器进程失败: " + e.getMessage(), e);
|
||||
throw new McpException("Failed to start MCP server process: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,18 +133,18 @@ public class StdioTransport implements McpTransport {
|
||||
JsonNode message = MAPPER.readTree(line);
|
||||
handleMessage(message);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析 MCP 响应失败: {}", line, e);
|
||||
log.warn("Failed to parse MCP response: {}", line, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (connected) {
|
||||
log.warn("MCP stdout 读取中断: {}", e.getMessage());
|
||||
log.warn("MCP stdout read interrupted: {}", e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
connected = false;
|
||||
// 清理所有等待中的请求
|
||||
pendingRequests.forEach((id, future) ->
|
||||
future.completeExceptionally(new McpException("MCP 连接已断开")));
|
||||
future.completeExceptionally(new McpException("MCP connection disconnected")));
|
||||
pendingRequests.clear();
|
||||
}
|
||||
}
|
||||
@@ -182,19 +182,19 @@ public class StdioTransport implements McpTransport {
|
||||
if (future != null) {
|
||||
future.complete(message);
|
||||
} else {
|
||||
log.warn("收到未匹配的 MCP 响应 (id={}): {}", id, message);
|
||||
log.warn("Received unmatched MCP response (id={}): {}", id, message);
|
||||
}
|
||||
} else {
|
||||
// 服务器主动通知(如 notifications/tools/list_changed)
|
||||
String method = message.has("method") ? message.get("method").asText() : "unknown";
|
||||
log.debug("收到 MCP 服务器通知: {}", method);
|
||||
log.debug("Received MCP server notification: {}", method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode sendRequest(String jsonRpcRequest) throws McpException {
|
||||
if (!connected) {
|
||||
throw new McpException("MCP 传输层未连接");
|
||||
throw new McpException("MCP transport not connected");
|
||||
}
|
||||
|
||||
String id = null;
|
||||
@@ -203,7 +203,7 @@ public class StdioTransport implements McpTransport {
|
||||
JsonNode requestNode = MAPPER.readTree(jsonRpcRequest);
|
||||
JsonNode idNode = requestNode.get("id");
|
||||
if (idNode == null || idNode.isNull()) {
|
||||
throw new McpException("JSON-RPC 请求缺少 id 字段");
|
||||
throw new McpException("JSON-RPC request missing id field");
|
||||
}
|
||||
id = idNode.asText();
|
||||
|
||||
@@ -218,7 +218,7 @@ public class StdioTransport implements McpTransport {
|
||||
processStdin.flush();
|
||||
}
|
||||
|
||||
log.debug("发送 MCP 请求 (id={}): {}", id, truncate(jsonRpcRequest, 200));
|
||||
log.debug("Sent MCP request (id={}): {}", id, truncate(jsonRpcRequest, 200));
|
||||
|
||||
// 等待响应
|
||||
JsonNode response = future.get(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
@@ -227,8 +227,8 @@ public class StdioTransport implements McpTransport {
|
||||
JsonNode errorNode = response.get("error");
|
||||
if (errorNode != null && !errorNode.isNull()) {
|
||||
int code = errorNode.has("code") ? errorNode.get("code").asInt() : -1;
|
||||
String msg = errorNode.has("message") ? errorNode.get("message").asText() : "未知错误";
|
||||
throw new McpException("MCP 服务器返回错误: " + msg, code);
|
||||
String msg = errorNode.has("message") ? errorNode.get("message").asText() : "Unknown error";
|
||||
throw new McpException("MCP server returned error: " + msg, code);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -236,15 +236,15 @@ public class StdioTransport implements McpTransport {
|
||||
} catch (McpException e) {
|
||||
throw e;
|
||||
} catch (TimeoutException e) {
|
||||
throw new McpException("MCP 请求超时 (" + DEFAULT_TIMEOUT_SECONDS + "s)", e);
|
||||
throw new McpException("MCP request timeout (" + DEFAULT_TIMEOUT_SECONDS + "s)", e);
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof McpException mcp) {
|
||||
throw mcp;
|
||||
}
|
||||
throw new McpException("MCP 请求执行异常: " + cause.getMessage(), cause);
|
||||
throw new McpException("MCP request execution exception: " + cause.getMessage(), cause);
|
||||
} catch (Exception e) {
|
||||
throw new McpException("MCP 请求发送失败: " + e.getMessage(), e);
|
||||
throw new McpException("MCP request send failed: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// 无论成功、超时或异常,都清理待处理请求,防止内存泄漏
|
||||
if (id != null) {
|
||||
@@ -256,7 +256,7 @@ public class StdioTransport implements McpTransport {
|
||||
@Override
|
||||
public void sendNotification(String jsonRpcNotification) throws McpException {
|
||||
if (!connected) {
|
||||
throw new McpException("MCP 传输层未连接");
|
||||
throw new McpException("MCP transport not connected");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -265,9 +265,9 @@ public class StdioTransport implements McpTransport {
|
||||
processStdin.newLine();
|
||||
processStdin.flush();
|
||||
}
|
||||
log.debug("发送 MCP 通知: {}", truncate(jsonRpcNotification, 200));
|
||||
log.debug("Sent MCP notification: {}", truncate(jsonRpcNotification, 200));
|
||||
} catch (IOException e) {
|
||||
throw new McpException("MCP 通知发送失败: " + e.getMessage(), e);
|
||||
throw new McpException("MCP notification send failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,14 +279,14 @@ public class StdioTransport implements McpTransport {
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
connected = false;
|
||||
log.info("关闭 MCP StdIO 传输...");
|
||||
log.info("Closing MCP StdIO transport...");
|
||||
|
||||
// 关闭 stdin(通知服务器退出)
|
||||
if (processStdin != null) {
|
||||
try {
|
||||
processStdin.close();
|
||||
} catch (IOException e) {
|
||||
log.debug("关闭 stdin 时异常: {}", e.getMessage());
|
||||
log.debug("Exception closing stdin: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ public class StdioTransport implements McpTransport {
|
||||
if (process != null && process.isAlive()) {
|
||||
boolean exited = process.waitFor(5, TimeUnit.SECONDS);
|
||||
if (!exited) {
|
||||
log.warn("MCP 服务器进程未在 5s 内退出,强制终止");
|
||||
log.warn("MCP server process did not exit within 5s, force terminating");
|
||||
process.destroyForcibly();
|
||||
process.waitFor(3, TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -310,10 +310,10 @@ public class StdioTransport implements McpTransport {
|
||||
|
||||
// 清理待处理请求
|
||||
pendingRequests.forEach((id, future) ->
|
||||
future.completeExceptionally(new McpException("MCP 传输已关闭")));
|
||||
future.completeExceptionally(new McpException("MCP transport closed")));
|
||||
pendingRequests.clear();
|
||||
|
||||
log.info("MCP StdIO 传输已关闭");
|
||||
log.info("MCP StdIO transport closed");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,13 +55,13 @@ public class OutputStylePlugin implements Plugin {
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "自定义输出样式";
|
||||
return "Custom output styles";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(PluginContext context) {
|
||||
this.context = context;
|
||||
context.getLogger().info("输出样式插件已初始化,当前样式: {}", currentStyle.get());
|
||||
context.getLogger().info("Output style plugin initialized, current style: {}", currentStyle.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,7 +95,7 @@ public class OutputStylePlugin implements Plugin {
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (context != null) {
|
||||
context.getLogger().info("输出样式插件已销毁");
|
||||
context.getLogger().info("Output style plugin destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ public class OutputStylePlugin implements Plugin {
|
||||
|
||||
// 切换样式
|
||||
if (!SUPPORTED_STYLES.contains(trimmed)) {
|
||||
return AnsiStyle.red(" ✗ 未知样式: " + trimmed) + "\n"
|
||||
+ AnsiStyle.dim(" 可用样式: default, minimal, verbose, markdown");
|
||||
return AnsiStyle.red(" ✗ Unknown style: " + trimmed) + "\n"
|
||||
+ AnsiStyle.dim(" Available styles: default, minimal, verbose, markdown");
|
||||
}
|
||||
|
||||
String oldStyle = currentStyle.getAndSet(trimmed);
|
||||
@@ -151,10 +151,10 @@ public class OutputStylePlugin implements Plugin {
|
||||
}
|
||||
|
||||
if (context != null) {
|
||||
context.getLogger().info("输出样式切换: {} → {}", oldStyle, trimmed);
|
||||
context.getLogger().info("Output style switched: {} → {}", oldStyle, trimmed);
|
||||
}
|
||||
|
||||
return AnsiStyle.green(" ✓ 输出样式已切换: ")
|
||||
return AnsiStyle.green(" ✓ Output style switched: ")
|
||||
+ AnsiStyle.bold(oldStyle)
|
||||
+ " → "
|
||||
+ AnsiStyle.bold(AnsiStyle.cyan(trimmed))
|
||||
@@ -182,7 +182,7 @@ public class OutputStylePlugin implements Plugin {
|
||||
.append(AnsiStyle.dim(" - " + desc)).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\n").append(AnsiStyle.dim(" 用法: /style <name>")).append("\n");
|
||||
sb.append("\n").append(AnsiStyle.dim(" Usage: /style <name>")).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -191,11 +191,11 @@ public class OutputStylePlugin implements Plugin {
|
||||
*/
|
||||
private String getStyleBrief(String style) {
|
||||
return switch (style) {
|
||||
case "default" -> "默认彩色输出";
|
||||
case "minimal" -> "精简输出,无颜色";
|
||||
case "verbose" -> "详细输出,含调试信息";
|
||||
case "markdown" -> "纯 Markdown 输出";
|
||||
default -> "未知样式";
|
||||
case "default" -> "Default colorful output";
|
||||
case "minimal" -> "Minimal output, no colors";
|
||||
case "verbose" -> "Verbose output with debug info";
|
||||
case "markdown" -> "Pure Markdown output";
|
||||
default -> "Unknown style";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -204,10 +204,10 @@ public class OutputStylePlugin implements Plugin {
|
||||
*/
|
||||
private String getStyleDescription(String style) {
|
||||
return switch (style) {
|
||||
case "default" -> AnsiStyle.dim(" 使用 ANSI 颜色和格式的标准输出模式");
|
||||
case "minimal" -> AnsiStyle.dim(" 无颜色无装饰的精简输出,适合管道和日志");
|
||||
case "verbose" -> AnsiStyle.dim(" 包含时间戳、调试信息的详细输出模式");
|
||||
case "markdown" -> AnsiStyle.dim(" 纯 Markdown 格式,适合导出到文档");
|
||||
case "default" -> AnsiStyle.dim(" Standard output mode with ANSI colors and formatting");
|
||||
case "minimal" -> AnsiStyle.dim(" Minimal output without colors, suitable for pipes and logs");
|
||||
case "verbose" -> AnsiStyle.dim(" Verbose output mode with timestamps and debug info");
|
||||
case "markdown" -> AnsiStyle.dim(" Pure Markdown format, suitable for export to documents");
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ public class PluginContext {
|
||||
* @throws NullPointerException 如果任何参数为 null
|
||||
*/
|
||||
public PluginContext(ToolContext toolContext, String workDir, String pluginId) {
|
||||
this.toolContext = Objects.requireNonNull(toolContext, "toolContext 不能为 null");
|
||||
this.workDir = Objects.requireNonNull(workDir, "workDir 不能为 null");
|
||||
Objects.requireNonNull(pluginId, "pluginId 不能为 null");
|
||||
this.toolContext = Objects.requireNonNull(toolContext, "toolContext cannot be null");
|
||||
this.workDir = Objects.requireNonNull(workDir, "workDir cannot be null");
|
||||
Objects.requireNonNull(pluginId, "pluginId cannot be null");
|
||||
this.pluginLogger = LoggerFactory.getLogger("plugin." + pluginId);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public class PluginManager {
|
||||
* @throws NullPointerException 如果 toolContext 为 null
|
||||
*/
|
||||
public PluginManager(ToolContext toolContext) {
|
||||
this.toolContext = Objects.requireNonNull(toolContext, "toolContext 不能为 null");
|
||||
this.toolContext = Objects.requireNonNull(toolContext, "toolContext cannot be null");
|
||||
this.globalPluginDir = Path.of(
|
||||
System.getProperty("user.home"), ".claude-code-java", "plugins");
|
||||
this.projectPluginDir = toolContext.getWorkDir().resolve(".claude-code").resolve("plugins");
|
||||
@@ -84,7 +84,7 @@ public class PluginManager {
|
||||
public void loadAll() {
|
||||
loadFromDirectory(globalPluginDir, "global");
|
||||
loadFromDirectory(projectPluginDir, "project");
|
||||
log.info("共加载 {} 个插件", plugins.size());
|
||||
log.info("Loaded {} plugins in total", plugins.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,14 +95,14 @@ public class PluginManager {
|
||||
*/
|
||||
private void loadFromDirectory(Path dir, String scope) {
|
||||
if (!Files.isDirectory(dir)) {
|
||||
log.debug("插件目录不存在,跳过: {}", dir);
|
||||
log.debug("Plugin directory does not exist, skipping: {}", dir);
|
||||
return;
|
||||
}
|
||||
try (var stream = Files.list(dir)) {
|
||||
stream.filter(p -> p.toString().endsWith(".jar"))
|
||||
.forEach(jar -> loadJarPlugin(jar, scope));
|
||||
} catch (IOException e) {
|
||||
log.warn("扫描插件目录失败: {}", dir, e);
|
||||
log.warn("Failed to scan plugin directory: {}", dir, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +130,12 @@ public class PluginManager {
|
||||
? manifest.getMainAttributes().getValue("Plugin-Class")
|
||||
: null;
|
||||
} catch (IOException e) {
|
||||
log.error("读取 JAR 清单失败: {}", jarPath.getFileName(), e);
|
||||
log.error("Failed to read JAR manifest: {}", jarPath.getFileName(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginClassName == null) {
|
||||
log.warn("JAR {} 缺少 Plugin-Class 属性,跳过", jarPath.getFileName());
|
||||
log.warn("JAR {} missing Plugin-Class attribute, skipping", jarPath.getFileName());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public class PluginManager {
|
||||
|
||||
Class<?> clazz = loader.loadClass(pluginClassName);
|
||||
if (!Plugin.class.isAssignableFrom(clazz)) {
|
||||
log.warn("{} 未实现 Plugin 接口,跳过", pluginClassName);
|
||||
log.warn("{} does not implement Plugin interface, skipping", pluginClassName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class PluginManager {
|
||||
|
||||
// 检查插件 ID 是否重复
|
||||
if (findPlugin(plugin.id()) != null) {
|
||||
log.warn("插件 ID '{}' 已存在,跳过重复加载: {}", plugin.id(), jarPath.getFileName());
|
||||
log.warn("Plugin ID '{}' already exists, skipping duplicate load: {}", plugin.id(), jarPath.getFileName());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,11 +169,11 @@ public class PluginManager {
|
||||
plugin.initialize(ctx);
|
||||
|
||||
plugins.add(new PluginInfo(plugin, scope, jarPath, loader));
|
||||
log.info("加载插件: {} v{} [{}] ({})", plugin.name(), plugin.version(), plugin.id(), scope);
|
||||
log.info("Loaded plugin: {} v{} [{}] ({})", plugin.name(), plugin.version(), plugin.id(), scope);
|
||||
success = true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("加载插件失败: {}", jarPath.getFileName(), e);
|
||||
log.error("Failed to load plugin: {}", jarPath.getFileName(), e);
|
||||
} finally {
|
||||
// 仅在加载失败时关闭类加载器;成功时由 PluginInfo 持有
|
||||
if (!success) {
|
||||
@@ -190,7 +190,7 @@ public class PluginManager {
|
||||
*/
|
||||
public boolean loadPlugin(Path jarPath) {
|
||||
if (!Files.isRegularFile(jarPath) || !jarPath.toString().endsWith(".jar")) {
|
||||
log.warn("无效的插件路径: {}", jarPath);
|
||||
log.warn("Invalid plugin path: {}", jarPath);
|
||||
return false;
|
||||
}
|
||||
loadJarPlugin(jarPath, "dynamic");
|
||||
@@ -207,7 +207,7 @@ public class PluginManager {
|
||||
for (PluginInfo info : plugins) {
|
||||
for (Tool tool : info.plugin().getTools()) {
|
||||
toolRegistry.register(tool);
|
||||
log.debug("注册插件工具: {} (来自 {})", tool.name(), info.plugin().name());
|
||||
log.debug("Registered plugin tool: {} (from {})", tool.name(), info.plugin().name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ public class PluginManager {
|
||||
for (PluginInfo info : plugins) {
|
||||
for (SlashCommand cmd : info.plugin().getCommands()) {
|
||||
commandRegistry.register(cmd);
|
||||
log.debug("注册插件命令: /{} (来自 {})", cmd.name(), info.plugin().name());
|
||||
log.debug("Registered plugin command: /{} (from {})", cmd.name(), info.plugin().name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,15 +244,15 @@ public class PluginManager {
|
||||
try {
|
||||
info.plugin().destroy();
|
||||
} catch (Exception e) {
|
||||
log.warn("插件 {} 销毁时异常", pluginId, e);
|
||||
log.warn("Plugin {} exception during destroy", pluginId, e);
|
||||
}
|
||||
safeClose(info.classLoader());
|
||||
plugins.remove(info); // CopyOnWriteArrayList.remove(Object) 是安全的
|
||||
log.info("已卸载插件: {} ({})", info.plugin().name(), pluginId);
|
||||
log.info("Unloaded plugin: {} ({})", info.plugin().name(), pluginId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
log.warn("未找到插件: {}", pluginId);
|
||||
log.warn("Plugin not found: {}", pluginId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -306,17 +306,17 @@ public class PluginManager {
|
||||
* 然后关闭对应的类加载器。此方法应在应用关闭时调用。
|
||||
*/
|
||||
public void shutdown() {
|
||||
log.info("正在关闭 {} 个插件...", plugins.size());
|
||||
log.info("Shutting down {} plugins...", plugins.size());
|
||||
for (PluginInfo info : plugins) {
|
||||
try {
|
||||
info.plugin().destroy();
|
||||
} catch (Exception e) {
|
||||
log.warn("插件 {} 销毁异常", info.plugin().id(), e);
|
||||
log.warn("Plugin {} exception during destroy", info.plugin().id(), e);
|
||||
}
|
||||
safeClose(info.classLoader());
|
||||
}
|
||||
plugins.clear();
|
||||
log.info("所有插件已关闭");
|
||||
log.info("All plugins shut down");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +329,7 @@ public class PluginManager {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
log.debug("关闭资源时异常 ({}): {}",
|
||||
log.debug("Exception closing resource ({}): {}",
|
||||
closeable.getClass().getSimpleName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class ReplSession {
|
||||
try {
|
||||
startWithJLine();
|
||||
} catch (Exception e) {
|
||||
log.warn("JLine 初始化失败,降级到 Scanner 模式: {}", e.getMessage());
|
||||
log.warn("JLine initialization failed, downgrading to Scanner mode: {}", e.getMessage());
|
||||
startWithScanner();
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class ReplSession {
|
||||
|
||||
boolean isDumb = "dumb".equals(terminal.getType());
|
||||
if (isDumb) {
|
||||
log.info("当前为 dumb 终端模式,建议使用 Windows Terminal / PowerShell / cmd 获得完整体验");
|
||||
log.info("Dumb terminal mode, use Windows Terminal / PowerShell / cmd for full experience");
|
||||
}
|
||||
|
||||
// 配置 Parser:支持反斜杠续行 (\) 和 三引号块 (""")
|
||||
@@ -172,7 +172,7 @@ public class ReplSession {
|
||||
String vimMode = System.getenv("CLAUDE_CODE_VIM");
|
||||
if ("1".equals(vimMode) || "true".equalsIgnoreCase(vimMode)) {
|
||||
reader.setVariable(LineReader.EDITING_MODE, "vi");
|
||||
log.info("已启用 Vim 编辑模式");
|
||||
log.info("Vim editing mode enabled");
|
||||
}
|
||||
|
||||
// 主提示符
|
||||
@@ -241,7 +241,7 @@ public class ReplSession {
|
||||
+ AnsiStyle.dim(" Model: ") + AnsiStyle.cyan(providerInfo.model()));
|
||||
out.println(AnsiStyle.dim(" Work Dir: " + System.getProperty("user.dir")));
|
||||
if (isDumb) {
|
||||
out.println(AnsiStyle.yellow(" ⚠ Dumb 终端模式:建议在 Windows Terminal / PowerShell 中运行"));
|
||||
out.println(AnsiStyle.yellow(" ⚠ Dumb terminal mode: run in Windows Terminal / PowerShell for best experience"));
|
||||
}
|
||||
} else {
|
||||
// 标准终端用带边框的 Banner
|
||||
@@ -343,7 +343,7 @@ public class ReplSession {
|
||||
} catch (Exception e) {
|
||||
spinner.stop();
|
||||
out.println(AnsiStyle.RED + "\n ● Error: " + AnsiStyle.RESET + e.getMessage());
|
||||
log.error("Agent 循环异常", e);
|
||||
log.error("Agent loop exception", e);
|
||||
out.println();
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ public class ReplSession {
|
||||
if (history.size() > 2) {
|
||||
var file = persistence.save(history, conversationSummary);
|
||||
if (file != null) {
|
||||
out.println(AnsiStyle.dim(" 💾 对话已保存: " + file.getFileName()));
|
||||
out.println(AnsiStyle.dim(" 💾 Conversation saved: " + file.getFileName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,10 +377,10 @@ public class ReplSession {
|
||||
*/
|
||||
private boolean promptPermission(AgentLoop.PermissionRequest request) {
|
||||
out.println();
|
||||
out.println(AnsiStyle.yellow(" ⚠ 权限确认"));
|
||||
out.println(AnsiStyle.yellow(" ⚠ Permission Required"));
|
||||
out.println(" " + "─".repeat(50));
|
||||
out.println(" " + AnsiStyle.bold("工具: ") + AnsiStyle.cyan(request.toolName()));
|
||||
out.println(" " + AnsiStyle.bold("操作: ") + request.activityDescription());
|
||||
out.println(" " + AnsiStyle.bold("Tool: ") + AnsiStyle.cyan(request.toolName()));
|
||||
out.println(" " + AnsiStyle.bold("Action: ") + request.activityDescription());
|
||||
|
||||
// 显示参数摘要(截断过长的参数)
|
||||
String argsPreview = request.arguments();
|
||||
@@ -388,11 +388,11 @@ public class ReplSession {
|
||||
argsPreview = argsPreview.substring(0, 200) + "...";
|
||||
}
|
||||
if (argsPreview != null && !argsPreview.isBlank()) {
|
||||
out.println(" " + AnsiStyle.dim("参数: " + argsPreview));
|
||||
out.println(" " + AnsiStyle.dim("Args: " + argsPreview));
|
||||
}
|
||||
|
||||
out.println(" " + "─".repeat(50));
|
||||
out.print(" " + AnsiStyle.bold("允许执行?") + AnsiStyle.dim(" [Y/n/always] ") + AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN + "→ " + AnsiStyle.RESET);
|
||||
out.print(" " + AnsiStyle.bold("Allow execution?") + AnsiStyle.dim(" [Y/n/always] ") + AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN + "→ " + AnsiStyle.RESET);
|
||||
out.flush();
|
||||
|
||||
String answer = readLineForPermission();
|
||||
@@ -403,7 +403,7 @@ public class ReplSession {
|
||||
// "always" → 禁用后续权限确认
|
||||
if (answer.equals("always") || answer.equals("a")) {
|
||||
agentLoop.setOnPermissionRequest(null); // 移除权限回调
|
||||
out.println(AnsiStyle.green(" ✓ 已授权所有后续操作"));
|
||||
out.println(AnsiStyle.green(" ✓ All subsequent operations authorized"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -413,7 +413,7 @@ public class ReplSession {
|
||||
}
|
||||
|
||||
// 其他输入 → 拒绝
|
||||
out.println(AnsiStyle.red(" ✗ 操作已拒绝"));
|
||||
out.println(AnsiStyle.red(" ✗ Operation denied"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ public class ReplSession {
|
||||
return activeScanner.nextLine();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("读取权限确认输入异常: {}", e.getMessage());
|
||||
log.debug("Permission confirmation input exception: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -450,9 +450,9 @@ public class ReplSession {
|
||||
return activeScanner.nextLine();
|
||||
}
|
||||
} catch (UserInterruptException e) {
|
||||
return "(用户取消)";
|
||||
return "(User cancelled)";
|
||||
} catch (Exception e) {
|
||||
log.debug("读取用户输入异常: {}", e.getMessage());
|
||||
log.debug("User input read exception: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -51,17 +51,17 @@ public class ToolCallbackAdapter implements ToolCallback {
|
||||
// 权限前置检查
|
||||
PermissionResult perm = tool.checkPermission(input, context);
|
||||
if (!perm.allowed()) {
|
||||
log.warn("[{}] 权限拒绝: {}", tool.name(), perm.message());
|
||||
log.warn("[{}] Permission denied: {}", tool.name(), perm.message());
|
||||
return "Permission denied: " + perm.message();
|
||||
}
|
||||
|
||||
log.debug("[{}] {}", tool.name(), tool.activityDescription(input));
|
||||
return tool.execute(input, context);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("[{}] JSON 解析失败: {}", tool.name(), e.getMessage());
|
||||
log.warn("[{}] JSON parse failed: {}", tool.name(), e.getMessage());
|
||||
return "Error: Invalid JSON input: " + e.getMessage();
|
||||
} catch (Exception e) {
|
||||
log.warn("[{}] 执行异常: {}", tool.name(), e.getMessage());
|
||||
log.warn("[{}] Execution exception: {}", tool.name(), e.getMessage());
|
||||
return "Error: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ public class ToolRegistry {
|
||||
*/
|
||||
public void register(Tool tool) {
|
||||
if (!tool.isEnabled()) {
|
||||
log.debug("工具 [{}] 未启用,跳过注册", tool.name());
|
||||
log.debug("Tool [{}] not enabled, skipping registration", tool.name());
|
||||
return;
|
||||
}
|
||||
if (tools.containsKey(tool.name())) {
|
||||
log.warn("工具 [{}] 已注册,将被覆盖", tool.name());
|
||||
log.warn("Tool [{}] already registered, will be overridden", tool.name());
|
||||
}
|
||||
tools.put(tool.name(), tool);
|
||||
log.debug("注册工具: [{}]", tool.name());
|
||||
log.debug("Registered tool: [{}]", tool.name());
|
||||
}
|
||||
|
||||
/** 批量注册 */
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AgentTool implements Tool {
|
||||
context.getOrDefault(AGENT_FACTORY_KEY, null);
|
||||
|
||||
if (agentFactory == null) {
|
||||
log.warn("AgentTool: 未配置 Agent 工厂,无法创建子 Agent");
|
||||
log.warn("AgentTool: Agent factory not configured, cannot create sub-agent");
|
||||
return "Error: Sub-agent capability is not configured. "
|
||||
+ "The Agent tool requires an agent factory to be registered in the ToolContext.";
|
||||
}
|
||||
@@ -85,14 +85,14 @@ public class AgentTool implements Tool {
|
||||
// 构建完整的子 Agent 提示
|
||||
String fullPrompt = buildSubAgentPrompt(prompt, additionalContext);
|
||||
|
||||
log.info("启动子 Agent,任务: {}", truncate(prompt, 80));
|
||||
log.info("Starting sub-agent, task: {}", truncate(prompt, 80));
|
||||
|
||||
try {
|
||||
String result = agentFactory.apply(fullPrompt);
|
||||
log.info("子 Agent 完成,结果长度: {} chars", result.length());
|
||||
log.info("Sub-agent completed, result length: {} chars", result.length());
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("子 Agent 执行失败", e);
|
||||
log.error("Sub-agent execution failed", e);
|
||||
return "Error: Sub-agent failed: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class AskUserQuestionTool implements Tool {
|
||||
// 获取用户输入回调
|
||||
Object callback = context.get(USER_INPUT_CALLBACK);
|
||||
if (callback == null) {
|
||||
log.warn("未注册用户输入回调(USER_INPUT_CALLBACK),返回默认回复");
|
||||
log.warn("User input callback not registered (USER_INPUT_CALLBACK), returning default response");
|
||||
return "Error: User input not available in current environment";
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class AskUserQuestionTool implements Tool {
|
||||
|
||||
// 构建提问文本
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("\n 🤔 AI 正在向你提问:\n");
|
||||
prompt.append("\n 🤔 AI is asking you a question:\n");
|
||||
prompt.append(" ").append("─".repeat(50)).append("\n");
|
||||
prompt.append(" ").append(question).append("\n");
|
||||
|
||||
@@ -99,7 +99,7 @@ public class AskUserQuestionTool implements Tool {
|
||||
if (input.containsKey("options")) {
|
||||
var options = (java.util.List<String>) input.get("options");
|
||||
if (options != null && !options.isEmpty()) {
|
||||
prompt.append("\n 可选项:\n");
|
||||
prompt.append("\n Options:\n");
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
prompt.append(" ").append(i + 1).append(". ").append(options.get(i)).append("\n");
|
||||
}
|
||||
@@ -115,11 +115,11 @@ public class AskUserQuestionTool implements Tool {
|
||||
return "(User provided no response)";
|
||||
}
|
||||
|
||||
log.debug("用户回答: {}", userResponse);
|
||||
log.debug("User response: {}", userResponse);
|
||||
return "User response: " + userResponse;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户输入失败", e);
|
||||
log.error("Failed to get user input", e);
|
||||
return "Error: Failed to get user input - " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,16 +49,16 @@ public class ConfigTool implements Tool {
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "操作类型:get(获取)或 set(设置)",
|
||||
"description": "Action type: get or set",
|
||||
"enum": ["get", "set"]
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "配置项的键名"
|
||||
"description": "Configuration key name"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "配置项的值(仅 set 操作时需要)"
|
||||
"description": "Configuration value (required for set operation)"
|
||||
}
|
||||
},
|
||||
"required": ["action", "key"]
|
||||
@@ -79,14 +79,14 @@ public class ConfigTool implements Tool {
|
||||
// 解析必填参数: action
|
||||
String action = (String) input.get("action");
|
||||
if (action == null || action.isBlank()) {
|
||||
return errorJson("参数 'action' 是必填项,可选值: get, set");
|
||||
return errorJson("Parameter 'action' is required, valid values: get, set");
|
||||
}
|
||||
action = action.trim().toLowerCase();
|
||||
|
||||
// 解析必填参数: key
|
||||
String key = (String) input.get("key");
|
||||
if (key == null || key.isBlank()) {
|
||||
return errorJson("参数 'key' 是必填项且不能为空");
|
||||
return errorJson("Parameter 'key' is required and cannot be empty");
|
||||
}
|
||||
|
||||
// 获取或初始化配置存储
|
||||
@@ -102,7 +102,7 @@ public class ConfigTool implements Tool {
|
||||
return switch (action) {
|
||||
case "get" -> executeGet(key, configStore);
|
||||
case "set" -> executeSet(key, input, configStore);
|
||||
default -> errorJson("无效的 action 值: '" + action + "'。可选值: get, set");
|
||||
default -> errorJson("Invalid action value: '" + action + "'. Valid values: get, set");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public class ConfigTool implements Tool {
|
||||
"key": "%s",
|
||||
"value": null,
|
||||
"found": false,
|
||||
"message": "配置项 '%s' 未找到"
|
||||
"message": "Config key '%s' not found"
|
||||
}""".formatted(escapeJson(key), escapeJson(key));
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class ConfigTool implements Tool {
|
||||
ConcurrentHashMap<String, String> configStore) {
|
||||
String value = (String) input.get("value");
|
||||
if (value == null) {
|
||||
return errorJson("set 操作需要提供 'value' 参数");
|
||||
return errorJson("set operation requires 'value' parameter");
|
||||
}
|
||||
|
||||
// 获取旧值(用于返回信息)
|
||||
@@ -200,7 +200,7 @@ public class ConfigTool implements Tool {
|
||||
}
|
||||
|
||||
sb.append(" \"success\": true,\n");
|
||||
sb.append(" \"message\": \"配置项 '").append(escapeJson(key)).append("' 已设置\"\n");
|
||||
sb.append(" \"message\": \"Config key '").append(escapeJson(key)).append("' has been set\"\n");
|
||||
sb.append("}");
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -55,8 +55,8 @@ public class McpToolBridge implements Tool {
|
||||
* @param mcpTool MCP 工具定义
|
||||
*/
|
||||
public McpToolBridge(String serverName, McpClient.McpTool mcpTool) {
|
||||
this.serverName = Objects.requireNonNull(serverName, "服务器名称不能为空");
|
||||
Objects.requireNonNull(mcpTool, "MCP 工具定义不能为空");
|
||||
this.serverName = Objects.requireNonNull(serverName, "Server name cannot be null");
|
||||
Objects.requireNonNull(mcpTool, "MCP tool definition cannot be null");
|
||||
|
||||
this.mcpToolName = mcpTool.name();
|
||||
this.mcpDescription = mcpTool.description();
|
||||
@@ -100,13 +100,13 @@ public class McpToolBridge implements Tool {
|
||||
// 从上下文获取 McpManager
|
||||
McpManager mcpManager = context.get(MCP_MANAGER_KEY);
|
||||
if (mcpManager == null) {
|
||||
return "错误: MCP 管理器未在上下文中注册 (key=" + MCP_MANAGER_KEY + ")";
|
||||
return "Error: MCP manager not registered in context (key=" + MCP_MANAGER_KEY + ")";
|
||||
}
|
||||
|
||||
try {
|
||||
return mcpManager.callTool(serverName, mcpToolName, input);
|
||||
} catch (McpException e) {
|
||||
return "MCP 工具调用失败 [" + serverName + "/" + mcpToolName + "]: " + e.getMessage();
|
||||
return "MCP tool call failed [" + serverName + "/" + mcpToolName + "]: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ public class TaskCreateTool implements Tool {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "任务描述,说明这个任务要做什么"
|
||||
"description": "Task description, what this task should accomplish"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string",
|
||||
"description": "可选的 JSON 格式元数据字符串,例如 {\\"priority\\":\\"high\\"}"
|
||||
"description": "Optional JSON metadata string, e.g. {\\"priority\\":\\"high\\"}"
|
||||
}
|
||||
},
|
||||
"required": ["description"]
|
||||
@@ -68,13 +68,13 @@ public class TaskCreateTool implements Tool {
|
||||
// 获取 TaskManager 实例
|
||||
TaskManager manager = context.get(TASK_MANAGER_KEY);
|
||||
if (manager == null) {
|
||||
return errorJson("TaskManager 未初始化,请检查上下文配置");
|
||||
return errorJson("TaskManager not initialized, check context configuration");
|
||||
}
|
||||
|
||||
// 解析必填参数: description
|
||||
String desc = (String) input.get("description");
|
||||
if (desc == null || desc.isBlank()) {
|
||||
return errorJson("参数 'description' 是必填项且不能为空");
|
||||
return errorJson("Parameter 'description' is required and cannot be empty");
|
||||
}
|
||||
|
||||
// 解析可选参数: metadata
|
||||
@@ -94,7 +94,7 @@ public class TaskCreateTool implements Tool {
|
||||
"task_id": "%s",
|
||||
"description": "%s",
|
||||
"status": "PENDING",
|
||||
"message": "任务已创建"
|
||||
"message": "Task created"
|
||||
}""".formatted(escapeJson(taskId), escapeJson(desc));
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class TaskGetTool implements Tool {
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "要查询的任务 ID"
|
||||
"description": "Task ID to query"
|
||||
}
|
||||
},
|
||||
"required": ["task_id"]
|
||||
@@ -63,19 +63,19 @@ public class TaskGetTool implements Tool {
|
||||
// 获取 TaskManager 实例
|
||||
TaskManager manager = context.get(TASK_MANAGER_KEY);
|
||||
if (manager == null) {
|
||||
return errorJson("TaskManager 未初始化,请检查上下文配置");
|
||||
return errorJson("TaskManager not initialized, check context configuration");
|
||||
}
|
||||
|
||||
// 解析必填参数: task_id
|
||||
String taskId = (String) input.get("task_id");
|
||||
if (taskId == null || taskId.isBlank()) {
|
||||
return errorJson("参数 'task_id' 是必填项且不能为空");
|
||||
return errorJson("Parameter 'task_id' is required and cannot be empty");
|
||||
}
|
||||
|
||||
// 查询任务
|
||||
Optional<TaskInfo> taskOpt = manager.getTask(taskId);
|
||||
if (taskOpt.isEmpty()) {
|
||||
return errorJson("未找到 ID 为 '" + taskId + "' 的任务");
|
||||
return errorJson("Task with ID '" + taskId + "' not found");
|
||||
}
|
||||
|
||||
// 返回任务详情 JSON
|
||||
|
||||
@@ -47,7 +47,7 @@ public class TaskListTool implements Tool {
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "按状态过滤:PENDING / RUNNING / COMPLETED / FAILED / CANCELLED",
|
||||
"description": "Filter by status: PENDING / RUNNING / COMPLETED / FAILED / CANCELLED",
|
||||
"enum": ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELLED"]
|
||||
}
|
||||
},
|
||||
@@ -65,7 +65,7 @@ public class TaskListTool implements Tool {
|
||||
// 获取 TaskManager 实例
|
||||
TaskManager manager = context.get(TASK_MANAGER_KEY);
|
||||
if (manager == null) {
|
||||
return errorJson("TaskManager 未初始化,请检查上下文配置");
|
||||
return errorJson("TaskManager not initialized, check context configuration");
|
||||
}
|
||||
|
||||
// 解析可选参数: status
|
||||
@@ -75,8 +75,8 @@ public class TaskListTool implements Tool {
|
||||
try {
|
||||
statusFilter = TaskStatus.valueOf(statusStr.trim().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return errorJson("无效的状态值: '" + statusStr
|
||||
+ "'。可选值: PENDING, RUNNING, COMPLETED, FAILED, CANCELLED");
|
||||
return errorJson("Invalid status value: '" + statusStr
|
||||
+ "'. Valid values: PENDING, RUNNING, COMPLETED, FAILED, CANCELLED");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,16 +52,16 @@ public class TaskUpdateTool implements Tool {
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "要更新的任务 ID"
|
||||
"description": "Task ID to update"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "新状态:PENDING / RUNNING / COMPLETED / FAILED / CANCELLED",
|
||||
"description": "New status: PENDING / RUNNING / COMPLETED / FAILED / CANCELLED",
|
||||
"enum": ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELLED"]
|
||||
},
|
||||
"result": {
|
||||
"type": "string",
|
||||
"description": "任务执行结果或附加信息(可选)"
|
||||
"description": "Task execution result or additional info (optional)"
|
||||
}
|
||||
},
|
||||
"required": ["task_id", "status"]
|
||||
@@ -78,27 +78,27 @@ public class TaskUpdateTool implements Tool {
|
||||
// 获取 TaskManager 实例
|
||||
TaskManager manager = context.get(TASK_MANAGER_KEY);
|
||||
if (manager == null) {
|
||||
return errorJson("TaskManager 未初始化,请检查上下文配置");
|
||||
return errorJson("TaskManager not initialized, check context configuration");
|
||||
}
|
||||
|
||||
// 解析必填参数: task_id
|
||||
String taskId = (String) input.get("task_id");
|
||||
if (taskId == null || taskId.isBlank()) {
|
||||
return errorJson("参数 'task_id' 是必填项且不能为空");
|
||||
return errorJson("Parameter 'task_id' is required and cannot be empty");
|
||||
}
|
||||
|
||||
// 解析必填参数: status
|
||||
String statusStr = (String) input.get("status");
|
||||
if (statusStr == null || statusStr.isBlank()) {
|
||||
return errorJson("参数 'status' 是必填项且不能为空");
|
||||
return errorJson("Parameter 'status' is required and cannot be empty");
|
||||
}
|
||||
|
||||
TaskStatus newStatus;
|
||||
try {
|
||||
newStatus = TaskStatus.valueOf(statusStr.trim().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return errorJson("无效的状态值: '" + statusStr
|
||||
+ "'。可选值: PENDING, RUNNING, COMPLETED, FAILED, CANCELLED");
|
||||
return errorJson("Invalid status value: '" + statusStr
|
||||
+ "'. Valid values: PENDING, RUNNING, COMPLETED, FAILED, CANCELLED");
|
||||
}
|
||||
|
||||
// 解析可选参数: result
|
||||
@@ -107,7 +107,7 @@ public class TaskUpdateTool implements Tool {
|
||||
// 在更新前先获取旧状态(用于返回信息)
|
||||
Optional<TaskInfo> beforeOpt = manager.getTask(taskId);
|
||||
if (beforeOpt.isEmpty()) {
|
||||
return errorJson("未找到 ID 为 '" + taskId + "' 的任务");
|
||||
return errorJson("Task with ID '" + taskId + "' not found");
|
||||
}
|
||||
|
||||
TaskInfo before = beforeOpt.get();
|
||||
@@ -116,15 +116,15 @@ public class TaskUpdateTool implements Tool {
|
||||
// 执行更新
|
||||
boolean success = manager.updateTask(taskId, newStatus, result);
|
||||
if (!success) {
|
||||
return errorJson("更新失败:任务 '" + taskId + "' 当前状态为 "
|
||||
+ oldStatus + ",可能已处于终态,无法再次更新");
|
||||
return errorJson("Update failed: task '" + taskId + "' current status is "
|
||||
+ oldStatus + ", may be in terminal state and cannot be updated");
|
||||
}
|
||||
|
||||
// 获取更新后的任务信息
|
||||
Optional<TaskInfo> afterOpt = manager.getTask(taskId);
|
||||
if (afterOpt.isEmpty()) {
|
||||
// 理论上不会出现,防御性编程
|
||||
return errorJson("更新后未能获取任务信息");
|
||||
return errorJson("Failed to get task info after update");
|
||||
}
|
||||
|
||||
TaskInfo after = afterOpt.get();
|
||||
@@ -144,8 +144,8 @@ public class TaskUpdateTool implements Tool {
|
||||
}
|
||||
|
||||
sb.append(" \"updated_at\": \"").append(after.updatedAt()).append("\",\n");
|
||||
sb.append(" \"message\": \"任务状态已从 ").append(oldStatus)
|
||||
.append(" 更新为 ").append(after.status().name()).append("\"\n");
|
||||
sb.append(" \"message\": \"Task status updated from ").append(oldStatus)
|
||||
.append(" to ").append(after.status().name()).append("\"\n");
|
||||
sb.append("}");
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -92,7 +92,7 @@ public class WebSearchTool implements Tool {
|
||||
String html = fetchSearchPage(query);
|
||||
return parseResults(html, maxResults);
|
||||
} catch (Exception e) {
|
||||
log.error("搜索失败: query={}", query, e);
|
||||
log.error("Search failed: query={}", query, e);
|
||||
return "Error: Search failed - " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user