@ai-zen/agents-sdk 1.0.0-alpha.22 → 1.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +216 -184
  2. package/README.md +13 -0
  3. package/README.zh.md +13 -0
  4. package/dist/agent-plugins/AutoMigratePlugin.js +1 -1
  5. package/dist/agent-plugins/UnknownToolHintPlugin.js +4 -4
  6. package/dist/config/constants.js +24 -24
  7. package/dist/config/factory-skills.js +56 -56
  8. package/dist/runtime/HandoffGuardPlugin.js +2 -2
  9. package/dist/runtime/HandoffService.js +22 -22
  10. package/dist/runtime/createAgent.js +2 -2
  11. package/dist/runtime/createModel.js +3 -3
  12. package/dist/scope/PermissionEvaluator.js +1 -1
  13. package/dist/scope/Scope.js +1 -1
  14. package/dist/scope/plugins/builtin/BatchEditTool.js +8 -8
  15. package/dist/scope/plugins/builtin/CopyTool.js +4 -4
  16. package/dist/scope/plugins/builtin/CwdTool.js +1 -1
  17. package/dist/scope/plugins/builtin/DownloadFileTool.js +8 -8
  18. package/dist/scope/plugins/builtin/EditTool.js +9 -9
  19. package/dist/scope/plugins/builtin/ExecTool.js +8 -8
  20. package/dist/scope/plugins/builtin/ExistTool.js +2 -2
  21. package/dist/scope/plugins/builtin/FindTextTool.js +10 -10
  22. package/dist/scope/plugins/builtin/GenerateImageTool.js +13 -13
  23. package/dist/scope/plugins/builtin/GlobTool.js +4 -4
  24. package/dist/scope/plugins/builtin/InspectFileTool.js +7 -7
  25. package/dist/scope/plugins/builtin/LsTool.js +5 -5
  26. package/dist/scope/plugins/builtin/MkdirTool.js +3 -3
  27. package/dist/scope/plugins/builtin/ReadTool.js +8 -8
  28. package/dist/scope/plugins/builtin/RenameTool.js +3 -3
  29. package/dist/scope/plugins/builtin/RmTool.js +3 -3
  30. package/dist/scope/plugins/builtin/SleepTool.js +6 -6
  31. package/dist/scope/plugins/builtin/SpawnTool.js +9 -9
  32. package/dist/scope/plugins/builtin/ViewImageTool.js +12 -12
  33. package/dist/scope/plugins/builtin/WriteTool.js +3 -3
  34. package/dist/scope/plugins/mcp/McpConnectionManager.js +7 -7
  35. package/dist/scope/plugins/mcp/discover.js +1 -1
  36. package/dist/scope/plugins/mcp/mcpTools.js +29 -29
  37. package/dist/scope/plugins/skills/discover.js +7 -7
  38. package/dist/scope/plugins/skills/skillTools.js +17 -17
  39. package/dist/scope/plugins/subagents/subAgentTools.js +2 -2
  40. package/dist/scope/plugins/usertools/discover.js +2 -2
  41. package/dist/shared/content-blocks.js +9 -9
  42. package/dist/shared/errors.js +3 -3
  43. package/package.json +10 -1
@@ -3,17 +3,17 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class RenameTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "rename",
6
- description: "重命名或移动文件/目录(相当于 mv 命令)",
6
+ description: "Rename or move a file/directory (equivalent to the mv command)",
7
7
  parameters: {
8
8
  type: "object",
9
9
  properties: {
10
10
  oldPath: {
11
11
  type: "string",
12
- description: "原路径",
12
+ description: "Original path",
13
13
  },
14
14
  newPath: {
15
15
  type: "string",
16
- description: "新路径",
16
+ description: "New path",
17
17
  },
18
18
  },
19
19
  required: ["oldPath", "newPath"],
@@ -3,17 +3,17 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class RmTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "rm",
6
- description: "删除文件或目录",
6
+ description: "Delete a file or directory",
7
7
  parameters: {
8
8
  type: "object",
9
9
  properties: {
10
10
  path: {
11
11
  type: "string",
12
- description: "文件或目录路径",
12
+ description: "File or directory path",
13
13
  },
14
14
  recursive: {
15
15
  type: "boolean",
16
- description: "是否递归删除子目录",
16
+ description: "Whether to delete subdirectories recursively",
17
17
  },
18
18
  },
19
19
  required: ["path"],
@@ -2,13 +2,13 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
2
2
  export class SleepTool extends SdkCallbackTool {
3
3
  function = {
4
4
  name: "sleep",
5
- description: "等待指定的时间(毫秒)。用于需要延迟执行的场景,例如等待其他操作完成、操作间隔控制等。注意:过长的等待时间会影响响应速度,建议不超过 30000 毫秒(30 秒)。",
5
+ description: "Wait for the specified time in milliseconds. Use it when a delay is needed, for example to wait for another operation to finish or to space out actions. Note: long waits slow down responses; keep it at or below 30000 milliseconds (30 seconds).",
6
6
  parameters: {
7
7
  type: "object",
8
8
  properties: {
9
9
  ms: {
10
10
  type: "number",
11
- description: "等待的毫秒数(1 秒 = 1000 毫秒)。建议不超过 30000(30 秒)。",
11
+ description: "Milliseconds to wait (1 second = 1000 milliseconds). Values above 30000 (30 seconds) are not recommended.",
12
12
  },
13
13
  },
14
14
  required: ["ms"],
@@ -23,14 +23,14 @@ export class SleepTool extends SdkCallbackTool {
23
23
  if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) {
24
24
  return JSON.stringify({
25
25
  success: false,
26
- error: "ms 必须是正整数",
26
+ error: "ms must be a positive integer",
27
27
  });
28
28
  }
29
29
  if (ms > 300_000) {
30
30
  // 超过 5 分钟给出警告
31
31
  return JSON.stringify({
32
32
  success: false,
33
- error: `等待时间 ${ms}ms 过长,建议不超过 300000ms(5 分钟)。如需长时间等待请分段执行。`,
33
+ error: `Wait time ${ms}ms is too long, keep it at or below 300000ms (5 minutes). For longer waits, split them into shorter steps`,
34
34
  });
35
35
  }
36
36
  // 若 signal 已处于 aborted,直接返回中断结果,避免不必要的等待
@@ -53,14 +53,14 @@ export class SleepTool extends SdkCallbackTool {
53
53
  return JSON.stringify({
54
54
  success: false,
55
55
  aborted: true,
56
- error: "等待被中断(aborted)",
56
+ error: "Wait aborted",
57
57
  });
58
58
  }
59
59
  return JSON.stringify({
60
60
  success: true,
61
61
  waitedMs: ms,
62
62
  waitedSeconds: (ms / 1000).toFixed(1),
63
- message: `已等待 ${ms}ms(${(ms / 1000).toFixed(1)} 秒)`,
63
+ message: `Waited ${ms}ms (${(ms / 1000).toFixed(1)} seconds)`,
64
64
  });
65
65
  }
66
66
  }
@@ -3,21 +3,21 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class SpawnTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "spawn",
6
- description: "异步执行命令,启动程序后立即返回,不等待执行结果。适用于启动长期运行的进程(如服务器、监听程序、GUI应用等)或不需要等待结果的命令。注意:该工具本身不捕获命令的输出;若需获取/留存输出,可在命令中使用 shell 重定向写入文件(如 `> /path/file`,支持 `>>` 追加、`|` 管道)。命令经 shell 解析执行。",
6
+ description: "Spawn a command asynchronously and return immediately without waiting for the result. Use it for long-running processes (servers, watchers, GUI apps, etc.) or commands whose result you do not need to wait for. Note: this tool does not capture the command's output itself; to retain or inspect output, use shell redirection to a file inside the command (e.g. `> /path/file`; `>>` append and `|` pipes are supported). The command is parsed and executed by the shell.",
7
7
  parameters: {
8
8
  type: "object",
9
9
  properties: {
10
10
  command: {
11
11
  type: "string",
12
- description: "要执行的命令(含参数)",
12
+ description: "Command to execute (including arguments)",
13
13
  },
14
14
  cwd: {
15
15
  type: "string",
16
- description: "工作目录(可选,默认当前工作目录)",
16
+ description: "Working directory (optional, defaults to the current working directory)",
17
17
  },
18
18
  detached: {
19
19
  type: "boolean",
20
- description: "是否完全脱离当前进程组(可选,默认 false)。设为 true 时,即使当前 Agent 进程退出,启动的程序仍会继续运行。适用于需要独立运行的守护进程或 GUI 程序。",
20
+ description: "Whether to fully detach from the current process group (optional, defaults to false). When true, the spawned program keeps running even after the current Agent process exits. Use it for daemons or GUI programs that must run independently.",
21
21
  },
22
22
  },
23
23
  required: ["command"],
@@ -48,7 +48,7 @@ export class SpawnTool extends SdkCallbackTool {
48
48
  resolve(JSON.stringify({
49
49
  success: false,
50
50
  pid: null,
51
- message: `启动失败: ${err.message}`,
51
+ message: `Failed to start: ${err.message}`,
52
52
  }));
53
53
  });
54
54
  child.on("spawn", () => {
@@ -58,7 +58,7 @@ export class SpawnTool extends SdkCallbackTool {
58
58
  resolve(JSON.stringify({
59
59
  success: true,
60
60
  pid: child.pid ?? null,
61
- message: `进程已启动 (PID: ${child.pid ?? "unknown"})`,
61
+ message: `Process started (PID: ${child.pid ?? "unknown"})`,
62
62
  }));
63
63
  });
64
64
  child.on("exit", (code) => {
@@ -70,8 +70,8 @@ export class SpawnTool extends SdkCallbackTool {
70
70
  pid: child.pid ?? null,
71
71
  exitCode: code,
72
72
  message: code === null
73
- ? `进程已退出 (PID: ${child.pid ?? "unknown"})`
74
- : `进程已退出 (PID: ${child.pid ?? "unknown"}, 退出码: ${code})`,
73
+ ? `Process exited (PID: ${child.pid ?? "unknown"})`
74
+ : `Process exited (PID: ${child.pid ?? "unknown"}, exit code: ${code})`,
75
75
  }));
76
76
  });
77
77
  const timeout = setTimeout(() => {
@@ -81,7 +81,7 @@ export class SpawnTool extends SdkCallbackTool {
81
81
  resolve(JSON.stringify({
82
82
  success: true,
83
83
  pid: child.pid ?? null,
84
- message: `进程已启动 (PID: ${child.pid ?? "unknown"})`,
84
+ message: `Process started (PID: ${child.pid ?? "unknown"})`,
85
85
  }));
86
86
  }, 500);
87
87
  const cleanup = () => {
@@ -35,13 +35,13 @@ export class ViewImageTool extends SdkCallbackTool {
35
35
  }
36
36
  function = {
37
37
  name: "view_image",
38
- description: "查看一张图片并分析其内容。传入本地图片路径或 http(s) 图片 URL。本地图片优先上传(端点支持 Files API 时)否则以 base64 内联引用,网络图片直接引用。不支持本地 URL(如 file:// 协议、指向本机 localhost/127.0.0.1 的 http(s) 地址),本地图片请使用文件路径。适合识别图片文字、描述图片内容、分析图表等。",
38
+ description: "View an image and analyze its content. Pass a local image path or an http(s) image URL. Local images are uploaded when the endpoint supports the Files API and are otherwise referenced as inline base64; remote images are referenced directly. Local URLs are not supported (such as the file:// protocol, or http(s) addresses pointing to localhost/127.0.0.1 on this machine); use a file path for local images. Suitable for reading text in images, describing image content and analyzing charts.",
39
39
  parameters: {
40
40
  type: "object",
41
41
  properties: {
42
42
  path_or_url: {
43
43
  type: "string",
44
- description: "本地图片路径(相对/绝对)或 http(s) 图片 URL。支持的格式:JPEG、PNG、GIF、WebP。不支持本地 URL(如 file:// 协议、指向本机 localhost/127.0.0.1 的 http(s) 地址),本地图片请使用文件路径。",
44
+ description: "Local image path (relative or absolute) or an http(s) image URL. Supported formats: JPEG, PNG, GIF, WebP. Local URLs are not supported (such as the file:// protocol, or http(s) addresses pointing to localhost/127.0.0.1 on this machine); use a file path for local images.",
45
45
  },
46
46
  },
47
47
  required: ["path_or_url"],
@@ -55,12 +55,12 @@ export class ViewImageTool extends SdkCallbackTool {
55
55
  const signal = ctx?.signal;
56
56
  const target = input?.path_or_url;
57
57
  if (!target || !target.trim()) {
58
- return "view_image 需要提供 path_or_url 参数(本地图片路径或 http(s) URL)。";
58
+ return "view_image requires the path_or_url parameter (a local image path or an http(s) URL)";
59
59
  }
60
60
  const value = target.trim();
61
61
  // 拒绝服务商无法访问的"本地 URL":file:// 等本地协议,或指向本机回环地址的 http(s) 地址
62
62
  if (isLocalUrl(value)) {
63
- return "不支持本地 URL(API 服务商无法下载访问)。本地图片请使用文件路径,而不是本地链接。";
63
+ return "Local URLs are not supported (the API provider cannot download them). Use a file path for local images, not a local link";
64
64
  }
65
65
  // 按当前模型定位端点:决定走 Files API 上传还是 base64 内联
66
66
  const { model, endpoint } = resolveModelAndEndpoint(this.env.config, ctx?.agent?.model);
@@ -74,11 +74,11 @@ export class ViewImageTool extends SdkCallbackTool {
74
74
  await fs.stat(abs);
75
75
  }
76
76
  catch {
77
- return `本地图片不存在: ${abs}`;
77
+ return `Local image not found: ${abs}`;
78
78
  }
79
79
  const mime = mimeOf(abs);
80
80
  if (!mime || !mime.startsWith("image/")) {
81
- return `不支持的图片格式 "${path.extname(abs) || "(无扩展名)"}"。支持的格式:JPEG、PNG、GIF、WebP。`;
81
+ return `Unsupported image format "${path.extname(abs) || "(no extension)"}". Supported formats: JPEG, PNG, GIF, WebP`;
82
82
  }
83
83
  }
84
84
  const result = await fileToContentSection({
@@ -87,21 +87,21 @@ export class ViewImageTool extends SdkCallbackTool {
87
87
  model,
88
88
  signal,
89
89
  });
90
- const label = isUrl ? `已获取网络图片:${value}` : `已获取本地图片(${path.basename(value)})`;
90
+ const label = isUrl ? `Fetched remote image: ${value}` : `Fetched local image (${path.basename(value)})`;
91
91
  const how = result.transport === "files-api"
92
- ? `,已上传至 Files API(file_id: ${result.section.file_id ?? "?"})`
92
+ ? `, uploaded to the Files API (file_id: ${result.section.file_id ?? "?"})`
93
93
  : result.transport === "inline"
94
- ? `,已按 base64 内联(${(result.bytes / 1024).toFixed(0)} KB)`
94
+ ? `, inlined as base64 (${(result.bytes / 1024).toFixed(0)} KB)`
95
95
  : "";
96
- const extra = result.warning ? `(降级说明:${result.warning})` : "";
96
+ const extra = result.warning ? ` (fallback: ${result.warning})` : "";
97
97
  return [{ type: "text", text: `${label}${how}${extra}` }, result.section];
98
98
  }
99
99
  catch (error) {
100
100
  // 中断(abort)时返回明确的中断结果
101
101
  if (signal?.aborted) {
102
- return "图片查看被中断(aborted)";
102
+ return "Image view aborted";
103
103
  }
104
- return `查看图片失败: ${error instanceof Error ? error.message : String(error)}`;
104
+ return `Failed to view image: ${error instanceof Error ? error.message : String(error)}`;
105
105
  }
106
106
  }
107
107
  }
@@ -4,17 +4,17 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
4
4
  export class WriteTool extends SdkCallbackTool {
5
5
  function = {
6
6
  name: "write",
7
- description: "写入文件",
7
+ description: "Write a file",
8
8
  parameters: {
9
9
  type: "object",
10
10
  properties: {
11
11
  path: {
12
12
  type: "string",
13
- description: "文件路径",
13
+ description: "File path",
14
14
  },
15
15
  content: {
16
16
  type: "string",
17
- description: "文件内容",
17
+ description: "File content",
18
18
  },
19
19
  },
20
20
  required: ["path", "content"],
@@ -141,7 +141,7 @@ export class McpConnectionManager {
141
141
  }));
142
142
  }
143
143
  catch (err) {
144
- getLogger().error(`[McpConnectionManager] 获取 "${name}" 工具列表失败: ${err?.message ?? err}`);
144
+ getLogger().error(`[McpConnectionManager] Failed to list tools for "${name}": ${err?.message ?? err}`);
145
145
  }
146
146
  }
147
147
  if (serverCaps?.resources) {
@@ -155,7 +155,7 @@ export class McpConnectionManager {
155
155
  }));
156
156
  }
157
157
  catch (err) {
158
- getLogger().error(`[McpConnectionManager] 获取 "${name}" 资源列表失败: ${err?.message ?? err}`);
158
+ getLogger().error(`[McpConnectionManager] Failed to list resources for "${name}": ${err?.message ?? err}`);
159
159
  }
160
160
  }
161
161
  if (serverCaps?.prompts) {
@@ -167,7 +167,7 @@ export class McpConnectionManager {
167
167
  }));
168
168
  }
169
169
  catch (err) {
170
- getLogger().error(`[McpConnectionManager] 获取 "${name}" 提示列表失败: ${err?.message ?? err}`);
170
+ getLogger().error(`[McpConnectionManager] Failed to list prompts for "${name}": ${err?.message ?? err}`);
171
171
  }
172
172
  }
173
173
  const entry = this.servers.get(name) ?? { state: "connecting" };
@@ -190,7 +190,7 @@ export class McpConnectionManager {
190
190
  }
191
191
  if (config.transport === "stdio") {
192
192
  if (!config.command) {
193
- throw new Error(`MCP stdio server 缺少 command`);
193
+ throw new Error(`MCP stdio server is missing command`);
194
194
  }
195
195
  return new StdioClientTransport({
196
196
  command: config.command,
@@ -201,7 +201,7 @@ export class McpConnectionManager {
201
201
  // http 使用 Streamable HTTP 传输(POST + 可选 SSE 流)
202
202
  if (config.transport === "http") {
203
203
  if (!config.url) {
204
- throw new Error(`MCP http server 缺少 url`);
204
+ throw new Error(`MCP http server is missing url`);
205
205
  }
206
206
  return new StreamableHTTPClientTransport(new URL(config.url), {
207
207
  requestInit: config.headers ? { headers: config.headers } : undefined,
@@ -210,13 +210,13 @@ export class McpConnectionManager {
210
210
  // sse 使用旧版 HTTP+SSE 传输(GET 建立 SSE 流,POST 发送消息)
211
211
  if (config.transport === "sse") {
212
212
  if (!config.url) {
213
- throw new Error(`MCP sse server 缺少 url`);
213
+ throw new Error(`MCP sse server is missing url`);
214
214
  }
215
215
  return new SSEClientTransport(new URL(config.url), {
216
216
  requestInit: config.headers ? { headers: config.headers } : undefined,
217
217
  });
218
218
  }
219
- throw new Error(`不支持的 MCP transport 类型: ${config.transport}`);
219
+ throw new Error(`Unsupported MCP transport type: ${config.transport}`);
220
220
  }
221
221
  // ---- 断开 ----
222
222
  async disconnect(name) {
@@ -82,7 +82,7 @@ export async function discoverMcpServers(paths) {
82
82
  }
83
83
  }
84
84
  catch (err) {
85
- getLogger().warn(`[sdk] 解析 MCP 配置失败: ${path} — ${err?.message ?? err}`);
85
+ getLogger().warn(`[sdk] Failed to parse MCP config: ${path} — ${err?.message ?? err}`);
86
86
  }
87
87
  }
88
88
  return items;
@@ -3,7 +3,7 @@ import { fileToContentSection, resolveModelAndEndpoint } from "../../../shared/c
3
3
  import { createDisclosureParam } from "../../disclosure.js";
4
4
  import { DEFAULT_MAX_TOOL_OUTPUT, guardOutput, headTailPreview } from "../builtin/outputGuard.js";
5
5
  import { getLogger } from "../../../shared/logger.js";
6
- const EMPTY_HINT = "(当前没有可用的 MCP 服务器,请联系用户添加)";
6
+ const EMPTY_HINT = "(No MCP servers available, ask the user to add one)";
7
7
  /**
8
8
  * 创建 load_mcp 工具。
9
9
  * server 枚举由 filteredMcps (完整 McpServerConfig[])自动推导,
@@ -16,16 +16,16 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
16
16
  configMap.set(s.id, s);
17
17
  }
18
18
  const ids = filteredMcps.map((s) => s.id);
19
- const param = createDisclosureParam(ids, "选择一个 MCP 服务器", EMPTY_HINT);
19
+ const param = createDisclosureParam(ids, "MCP server name", EMPTY_HINT);
20
20
  // 拼接各 MCP 服务器的描述供 LLM 参考(对齐 load_skill 的处理方式)
21
21
  const mcpDescriptions = filteredMcps
22
- .map((s) => ` - ${s.id}: ${s.description || "无描述"}`)
22
+ .map((s) => ` - ${s.id}: ${s.description || "No description"}`)
23
23
  .join("\n");
24
- const serverIdDescription = `${param.description}\n\n各 MCP 服务器说明:\n${mcpDescriptions || " 无可用 MCP 服务器"}`;
24
+ const serverIdDescription = `${param.description}\n\nAvailable MCP servers:\n${mcpDescriptions || " No MCP servers available"}`;
25
25
  return new CallbackTool({
26
26
  function: {
27
27
  name: "load_mcp",
28
- description: "连接到指定 MCP 服务器,获取其可用工具和资源列表。可用 include_manifest=false 仅建立连接、不返回清单(适用于上文中已有该清单的场景)。",
28
+ description: "Connect to the specified MCP server and get the list of tools and resources it exposes. Pass include_manifest=false to only establish the connection without returning the manifest (useful when the manifest is already in context).",
29
29
  parameters: {
30
30
  type: "object",
31
31
  properties: {
@@ -36,7 +36,7 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
36
36
  },
37
37
  include_manifest: {
38
38
  type: "boolean",
39
- description: "是否返回该服务器的完整能力清单(工具与资源)。可选,默认 true。若你的上文中已存在该清单(例如本会话早前加载过,而进程重启后连接已失效),可设为 false 仅建立连接、只返回摘要,以节省上下文 token",
39
+ description: "Whether to return the server's full capability manifest (tools and resources). Optional, defaults to true. If the manifest is already in your context (for example it was loaded earlier in this session but the connection was dropped after a process restart), set it to false to only establish the connection and return a summary, saving context tokens",
40
40
  default: true,
41
41
  },
42
42
  },
@@ -48,7 +48,7 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
48
48
  const serverName = input.server;
49
49
  const config = configMap.get(serverName);
50
50
  if (!config) {
51
- return `❌ MCP 服务器 "${serverName}" 不存在`;
51
+ return `❌ MCP server "${serverName}" is not configured`;
52
52
  }
53
53
  // 披露开关:默认返回完整清单;显式传 false 时仅建立连接、只返回摘要
54
54
  const includeManifest = input.include_manifest !== false;
@@ -64,12 +64,12 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
64
64
  manifest = await mcpManager.connect(serverName, config);
65
65
  }
66
66
  catch (error) {
67
- return `无法连接到 "${serverName}": ${error?.message ?? error}`;
67
+ return `Failed to connect to "${serverName}": ${error?.message ?? error}`;
68
68
  }
69
69
  }
70
70
  // 静默模式:只回吐摘要(清单仍留在管理器内,供 call_mcp_tool 使用)
71
71
  if (!includeManifest) {
72
- getLogger().info(`[load_mcp] ${serverName}: 已连接(未披露清单:工具 ${manifest.tools.length} 个,资源 ${manifest.resources.length} 个)`);
72
+ getLogger().info(`[load_mcp] ${serverName}: connected (manifest not disclosed: ${manifest.tools.length} tools, ${manifest.resources.length} resources)`);
73
73
  return JSON.stringify({
74
74
  server: serverName,
75
75
  connected: true,
@@ -117,7 +117,7 @@ async function mediaSection(data, mime, media) {
117
117
  catch (error) {
118
118
  return {
119
119
  type: "text",
120
- text: `[媒体] 无法转换(${mime}):${error?.message ?? error}`,
120
+ text: `[media] Failed to convert (${mime}): ${error?.message ?? error}`,
121
121
  };
122
122
  }
123
123
  }
@@ -161,11 +161,11 @@ async function toMessageSection(block, media) {
161
161
  const mime = r.mimeType ?? "application/octet-stream";
162
162
  return await mediaSection(r.blob, mime, media);
163
163
  }
164
- return { type: "text", text: `[资源] ${r.uri ?? "(未知 URI)"}` };
164
+ return { type: "text", text: `[resource] ${r.uri ?? "(unknown URI)"}` };
165
165
  }
166
166
  case "resource_link": {
167
167
  const parts = [block.name, block.uri, block.mimeType].filter(Boolean);
168
- return { type: "text", text: `[资源链接] ${parts.join(" · ")}` };
168
+ return { type: "text", text: `[resource_link] ${parts.join(" · ")}` };
169
169
  }
170
170
  case "text":
171
171
  return { type: "text", text: block.text ?? "" };
@@ -200,8 +200,8 @@ async function protectCallOutput(output, env) {
200
200
  return JSON.stringify({
201
201
  outputTooLarge: true,
202
202
  isError: isError || undefined,
203
- warning: `文本输出过大:共 ${chars} 字符,超过上限 ${limit} 字符。` +
204
- `完整文本已落盘(result.txt),可用 read 分批读取。`,
203
+ warning: `Text output too large: ${chars} characters, exceeding the limit of ${limit} characters. ` +
204
+ `The full text has been written to disk (result.txt); use read to page through it.`,
205
205
  files: ctx.files,
206
206
  chars,
207
207
  ...preview,
@@ -228,9 +228,9 @@ async function protectCallOutput(output, env) {
228
228
  // 未超限:无媒体 → 纯字符串;有媒体 → 原序块数组
229
229
  const hasMedia = sections.some((s) => s.type !== "text");
230
230
  if (!hasMedia)
231
- return isError ? `❌ 工具执行出错:\n${fullText}` : fullText;
231
+ return isError ? `❌ Tool execution failed:\n${fullText}` : fullText;
232
232
  return isError
233
- ? [{ type: "text", text: "❌ 工具执行出错:" }, ...sections]
233
+ ? [{ type: "text", text: "❌ Tool execution failed:" }, ...sections]
234
234
  : sections;
235
235
  }
236
236
  /**
@@ -247,13 +247,13 @@ export function createCallMcpTool(mcpManager, env) {
247
247
  return new CallbackTool({
248
248
  function: {
249
249
  name: "call_mcp_tool",
250
- description: "调用已连接 MCP 服务器上的指定工具。需先通过 load_mcp 连接服务器。文本输出过大时会落盘并只返回头尾预览;图片、音频等媒体会保留:端点支持 Files API 时上传后以 file_id 引用,否则 base64 内联。",
250
+ description: "Call a tool exposed by a connected MCP server. Connect the server with load_mcp first. Large text output is written to disk and only a head/tail preview is returned; images, audio and other media are preserved: uploaded and referenced by file_id when the endpoint supports the Files API, otherwise inlined as base64.",
251
251
  parameters: {
252
252
  type: "object",
253
253
  properties: {
254
- server: { type: "string", description: "MCP 服务器名称" },
255
- tool: { type: "string", description: "工具名称" },
256
- arguments: { type: "object", description: "工具参数" },
254
+ server: { type: "string", description: "MCP server name" },
255
+ tool: { type: "string", description: "Tool name" },
256
+ arguments: { type: "object", description: "Tool arguments" },
257
257
  },
258
258
  required: ["server", "tool", "arguments"],
259
259
  additionalProperties: false,
@@ -263,11 +263,11 @@ export function createCallMcpTool(mcpManager, env) {
263
263
  const serverName = input.server;
264
264
  const state = mcpManager.getState(serverName);
265
265
  if (state !== "connected") {
266
- return `请先使用 load_mcp 连接 "${serverName}"`;
266
+ return `Call load_mcp to connect "${serverName}" first`;
267
267
  }
268
268
  const client = mcpManager.getClient(serverName);
269
269
  if (!client) {
270
- return `MCP 服务器 "${serverName}" 的客户端不可用`;
270
+ return `MCP server "${serverName}" client is not available`;
271
271
  }
272
272
  try {
273
273
  mcpManager.touch(serverName);
@@ -294,7 +294,7 @@ export function createCallMcpTool(mcpManager, env) {
294
294
  return await protectCallOutput({ sections, fullText, chars, isError }, env);
295
295
  }
296
296
  catch (error) {
297
- return `❌ 调用 "${input.tool}" 失败: ${error?.message ?? error}`;
297
+ return `❌ Failed to call "${input.tool}": ${error?.message ?? error}`;
298
298
  }
299
299
  },
300
300
  });
@@ -306,12 +306,12 @@ export function createReadMcpResourceTool(mcpManager, env) {
306
306
  return new CallbackTool({
307
307
  function: {
308
308
  name: "read_mcp_resource",
309
- description: "读取已连接 MCP 服务器上的指定资源(文档、数据、图片等)。文本原样返回;图片等媒体按端点能力上传(file_id)或 base64 内联,不会被丢弃。",
309
+ description: "Read a resource (document, data, image, etc.) from a connected MCP server. Text is returned as-is; images and other media are uploaded (file_id) or inlined as base64 depending on endpoint capabilities, and are never dropped.",
310
310
  parameters: {
311
311
  type: "object",
312
312
  properties: {
313
- server: { type: "string", description: "MCP 服务器名称" },
314
- uri: { type: "string", description: "资源 URI" },
313
+ server: { type: "string", description: "MCP server name" },
314
+ uri: { type: "string", description: "Resource URI" },
315
315
  },
316
316
  required: ["server", "uri"],
317
317
  additionalProperties: false,
@@ -321,11 +321,11 @@ export function createReadMcpResourceTool(mcpManager, env) {
321
321
  const serverName = input.server;
322
322
  const state = mcpManager.getState(serverName);
323
323
  if (state !== "connected") {
324
- return `请先使用 load_mcp 连接 "${serverName}"`;
324
+ return `Call load_mcp to connect "${serverName}" first`;
325
325
  }
326
326
  const client = mcpManager.getClient(serverName);
327
327
  if (!client) {
328
- return `MCP 服务器 "${serverName}" 的客户端不可用`;
328
+ return `MCP server "${serverName}" client is not available`;
329
329
  }
330
330
  try {
331
331
  mcpManager.touch(serverName);
@@ -349,7 +349,7 @@ export function createReadMcpResourceTool(mcpManager, env) {
349
349
  return await protectCallOutput({ sections, fullText, chars, isError: false }, env);
350
350
  }
351
351
  catch (error) {
352
- return `❌ 读取资源 "${input.uri}" 失败: ${error?.message ?? error}`;
352
+ return `❌ Failed to read resource "${input.uri}": ${error?.message ?? error}`;
353
353
  }
354
354
  },
355
355
  });
@@ -165,27 +165,27 @@ const VALID_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
165
165
  export function validateSkill(skillId, fm) {
166
166
  const errors = [];
167
167
  if (!fm.name || fm.name.length === 0) {
168
- errors.push({ skillId, field: "name", message: "必须存在且非空" });
168
+ errors.push({ skillId, field: "name", message: "Must be present and non-empty" });
169
169
  }
170
170
  else {
171
171
  if (fm.name.length > 64) {
172
- errors.push({ skillId, field: "name", message: `超过 64 字符(当前 ${fm.name.length})` });
172
+ errors.push({ skillId, field: "name", message: `Exceeds 64 characters (currently ${fm.name.length})` });
173
173
  }
174
174
  if (!VALID_NAME_RE.test(fm.name)) {
175
- errors.push({ skillId, field: "name", message: `"${fm.name}" 不符合规范:仅允许小写字母、数字、连字符,且不能首尾或连续连字符` });
175
+ errors.push({ skillId, field: "name", message: `"${fm.name}" is not valid: only lowercase letters, digits and hyphens are allowed, and it must not start, end or contain consecutive hyphens` });
176
176
  }
177
177
  if (fm.name !== skillId) {
178
- errors.push({ skillId, field: "name", message: `"${fm.name}" 与目录名 "${skillId}" 不一致` });
178
+ errors.push({ skillId, field: "name", message: `"${fm.name}" does not match the directory name "${skillId}"` });
179
179
  }
180
180
  }
181
181
  if (!fm.description || fm.description.length === 0) {
182
- errors.push({ skillId, field: "description", message: "必须存在且非空" });
182
+ errors.push({ skillId, field: "description", message: "Must be present and non-empty" });
183
183
  }
184
184
  else if (fm.description.length > 1024) {
185
- errors.push({ skillId, field: "description", message: `超过 1024 字符(当前 ${fm.description.length})` });
185
+ errors.push({ skillId, field: "description", message: `Exceeds 1024 characters (currently ${fm.description.length})` });
186
186
  }
187
187
  if (fm.compatibility && fm.compatibility.length > 500) {
188
- errors.push({ skillId, field: "compatibility", message: `超过 500 字符(当前 ${fm.compatibility.length})` });
188
+ errors.push({ skillId, field: "compatibility", message: `Exceeds 500 characters (currently ${fm.compatibility.length})` });
189
189
  }
190
190
  return errors;
191
191
  }