i18n: 全部用户可见字符串统一为英文(46个文件)

- 所有Command/Tool/Core/MCP/Plugin中的中文提示改为英文
- Javadoc注释和行内注释保留中文不变
- AI提示词(compact/commit/review等)改为英文
- 编译验证通过

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
liuzh 1 month ago
parent 1172ab6b08
commit ff734d6b0d
  1. 2
      src/main/java/com/claudecode/cli/ClaudeCodeRunner.java
  2. 2
      src/main/java/com/claudecode/command/CommandRegistry.java
  3. 48
      src/main/java/com/claudecode/command/impl/BranchCommand.java
  4. 32
      src/main/java/com/claudecode/command/impl/CommitCommand.java
  5. 36
      src/main/java/com/claudecode/command/impl/CompactCommand.java
  6. 12
      src/main/java/com/claudecode/command/impl/CopyCommand.java
  7. 12
      src/main/java/com/claudecode/command/impl/DiffCommand.java
  8. 6
      src/main/java/com/claudecode/command/impl/ExportCommand.java
  9. 10
      src/main/java/com/claudecode/command/impl/HistoryCommand.java
  10. 10
      src/main/java/com/claudecode/command/impl/HooksCommand.java
  11. 94
      src/main/java/com/claudecode/command/impl/McpCommand.java
  12. 30
      src/main/java/com/claudecode/command/impl/MemoryCommand.java
  13. 40
      src/main/java/com/claudecode/command/impl/PluginCommand.java
  14. 24
      src/main/java/com/claudecode/command/impl/ResumeCommand.java
  15. 16
      src/main/java/com/claudecode/command/impl/ReviewCommand.java
  16. 18
      src/main/java/com/claudecode/command/impl/RewindCommand.java
  17. 16
      src/main/java/com/claudecode/command/impl/SecurityReviewCommand.java
  18. 18
      src/main/java/com/claudecode/command/impl/SkillsCommand.java
  19. 4
      src/main/java/com/claudecode/command/impl/StatsCommand.java
  20. 40
      src/main/java/com/claudecode/command/impl/TagCommand.java
  21. 6
      src/main/java/com/claudecode/config/AppConfig.java
  22. 6
      src/main/java/com/claudecode/context/ClaudeMdLoader.java
  23. 4
      src/main/java/com/claudecode/context/GitContext.java
  24. 8
      src/main/java/com/claudecode/context/SkillLoader.java
  25. 20
      src/main/java/com/claudecode/core/AgentLoop.java
  26. 12
      src/main/java/com/claudecode/core/ConversationPersistence.java
  27. 10
      src/main/java/com/claudecode/core/HookManager.java
  28. 12
      src/main/java/com/claudecode/core/TaskManager.java
  29. 54
      src/main/java/com/claudecode/mcp/McpClient.java
  30. 64
      src/main/java/com/claudecode/mcp/McpManager.java
  31. 48
      src/main/java/com/claudecode/mcp/StdioTransport.java
  32. 34
      src/main/java/com/claudecode/plugin/OutputStylePlugin.java
  33. 6
      src/main/java/com/claudecode/plugin/PluginContext.java
  34. 40
      src/main/java/com/claudecode/plugin/PluginManager.java
  35. 32
      src/main/java/com/claudecode/repl/ReplSession.java
  36. 6
      src/main/java/com/claudecode/tool/ToolCallbackAdapter.java
  37. 6
      src/main/java/com/claudecode/tool/ToolRegistry.java
  38. 8
      src/main/java/com/claudecode/tool/impl/AgentTool.java
  39. 10
      src/main/java/com/claudecode/tool/impl/AskUserQuestionTool.java
  40. 18
      src/main/java/com/claudecode/tool/impl/ConfigTool.java
  41. 8
      src/main/java/com/claudecode/tool/impl/McpToolBridge.java
  42. 10
      src/main/java/com/claudecode/tool/impl/TaskCreateTool.java
  43. 8
      src/main/java/com/claudecode/tool/impl/TaskGetTool.java
  44. 8
      src/main/java/com/claudecode/tool/impl/TaskListTool.java
  45. 28
      src/main/java/com/claudecode/tool/impl/TaskUpdateTool.java
  46. 2
      src/main/java/com/claudecode/tool/impl/WebSearchTool.java

@ -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();
}
}

Loading…
Cancel
Save