@ai-zen/agents-sdk 1.0.0-alpha.21 → 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 (47) hide show
  1. package/CHANGELOG.md +223 -171
  2. package/README.md +15 -0
  3. package/README.zh.md +15 -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 +29 -24
  7. package/dist/config/factory-skills.js +56 -56
  8. package/dist/config/migrate.d.ts +10 -2
  9. package/dist/config/migrate.js +23 -2
  10. package/dist/runtime/HandoffGuardPlugin.js +2 -2
  11. package/dist/runtime/HandoffService.js +22 -22
  12. package/dist/runtime/createAgent.d.ts +7 -3
  13. package/dist/runtime/createAgent.js +7 -5
  14. package/dist/runtime/createModel.js +3 -3
  15. package/dist/scope/PermissionEvaluator.js +1 -1
  16. package/dist/scope/Scope.js +1 -1
  17. package/dist/scope/plugins/builtin/BatchEditTool.js +8 -8
  18. package/dist/scope/plugins/builtin/CopyTool.js +4 -4
  19. package/dist/scope/plugins/builtin/CwdTool.js +1 -1
  20. package/dist/scope/plugins/builtin/DownloadFileTool.js +8 -8
  21. package/dist/scope/plugins/builtin/EditTool.js +9 -9
  22. package/dist/scope/plugins/builtin/ExecTool.js +8 -8
  23. package/dist/scope/plugins/builtin/ExistTool.js +2 -2
  24. package/dist/scope/plugins/builtin/FindTextTool.js +10 -10
  25. package/dist/scope/plugins/builtin/GenerateImageTool.js +13 -13
  26. package/dist/scope/plugins/builtin/GlobTool.js +4 -4
  27. package/dist/scope/plugins/builtin/InspectFileTool.js +7 -7
  28. package/dist/scope/plugins/builtin/LsTool.js +5 -5
  29. package/dist/scope/plugins/builtin/MkdirTool.js +3 -3
  30. package/dist/scope/plugins/builtin/ReadTool.js +8 -8
  31. package/dist/scope/plugins/builtin/RenameTool.js +3 -3
  32. package/dist/scope/plugins/builtin/RmTool.js +3 -3
  33. package/dist/scope/plugins/builtin/SleepTool.js +6 -6
  34. package/dist/scope/plugins/builtin/SpawnTool.js +9 -9
  35. package/dist/scope/plugins/builtin/ViewImageTool.js +12 -12
  36. package/dist/scope/plugins/builtin/WriteTool.js +3 -3
  37. package/dist/scope/plugins/mcp/McpConnectionManager.js +7 -7
  38. package/dist/scope/plugins/mcp/discover.js +1 -1
  39. package/dist/scope/plugins/mcp/mcpTools.js +29 -29
  40. package/dist/scope/plugins/skills/discover.js +7 -7
  41. package/dist/scope/plugins/skills/skillTools.js +17 -17
  42. package/dist/scope/plugins/subagents/subAgentTools.js +2 -2
  43. package/dist/scope/plugins/usertools/discover.js +2 -2
  44. package/dist/shared/content-blocks.js +9 -9
  45. package/dist/shared/errors.js +3 -3
  46. package/dist/types/index.d.ts +22 -1
  47. package/package.json +10 -1
@@ -16,7 +16,7 @@ import { HandoffGuardPlugin } from "./HandoffGuardPlugin.js";
16
16
  */
17
17
  export class HandoffAbortedError extends Error {
18
18
  constructor() {
19
- super("交接已取消");
19
+ super("Handoff cancelled");
20
20
  this.name = "HandoffAbortedError";
21
21
  }
22
22
  }
@@ -115,14 +115,14 @@ export class HandoffService {
115
115
  const { skillContent, handoffPath, toolsAvailable } = params;
116
116
  const tail = toolsAvailable
117
117
  ? [
118
- "请立即开始撰写,并用 write 工具把完整文档写入上面给出的路径(路径原样使用,不要改名、不要换目录)。",
119
- "写完后用 read 回读一次确认完整,然后只回一句不超过 40 字的确认,**不要复述文档内容**。",
118
+ "Start writing now: use the write tool to put the complete document at the path given above (use the path exactly as given, do not rename it and do not move it to another directory).",
119
+ "After writing, read it back once with the read tool to confirm it is complete, then reply with a single confirmation of no more than 40 words, **do not restate the document content**.",
120
120
  ]
121
121
  : [
122
- "请直接输出完整文档正文:不要任何解释、不要用代码块包裹,调用方会自行落盘。",
122
+ "Output the complete document body directly: no explanations, do not wrap it in a code block, the caller will persist it to disk itself.",
123
123
  ];
124
124
  return [
125
- "你现在要为本轮对话编写一份任务交接文档。以下是必须遵守的交接规范。",
125
+ "You are now to write a task handoff document for this conversation. The following handoff rules are mandatory.",
126
126
  "",
127
127
  "---",
128
128
  "",
@@ -130,11 +130,11 @@ export class HandoffService {
130
130
  "",
131
131
  "---",
132
132
  "",
133
- "## 本次交接参数",
134
- `- 交接文档绝对路径:${handoffPath}`,
135
- `- 工具能力:${toolsAvailable
136
- ? "可用(read / exec 等,请据此核实真实状态)"
137
- : "不可用(你无法核实磁盘,状态类信息一律写「⚠️ 未核实」,禁止推测)"}`,
133
+ "## Handoff parameters",
134
+ `- Handoff document absolute path: ${handoffPath}`,
135
+ `- Tools available: ${toolsAvailable
136
+ ? "yes (read / exec, etc., use them to verify the actual state)"
137
+ : "no (you cannot inspect the disk, so for any state information always write '⚠️ Unverified', never speculate)"}`,
138
138
  "",
139
139
  ...tail,
140
140
  ].join("\n");
@@ -147,10 +147,10 @@ export class HandoffService {
147
147
  */
148
148
  static createPostMessages(handoffPath) {
149
149
  const content = [
150
- "上一轮对话已交接,交接文档保存在:",
150
+ "The previous conversation has been handed off, the handoff document is saved at:",
151
151
  handoffPath,
152
152
  "",
153
- "请先用 read 工具读取该文件,理解上下文与未完成事项后,再继续协助用户完成任务。",
153
+ "Read that file first with the read tool, and once you understand the context and the unfinished work, continue helping the user with the task.",
154
154
  ].join("\n");
155
155
  return [Message.User(content)];
156
156
  }
@@ -213,7 +213,7 @@ export class HandoffService {
213
213
  /** 串行化 + 防重入:交接与迁移共用同一把锁 */
214
214
  async withBusy(fn) {
215
215
  if (this._busy)
216
- throw new Error("交接流程已在进行中,请等待当前交接完成");
216
+ throw new Error("A handoff is already in progress, wait for it to finish");
217
217
  this._busy = true;
218
218
  try {
219
219
  return await fn();
@@ -298,13 +298,13 @@ export class HandoffService {
298
298
  let handoffDoc;
299
299
  let historyText = "";
300
300
  if (mode === "self" && !this.supportsSelfHandoff(agent)) {
301
- this.logger.warn("[handoff] agent 不具备 write 工具或 send 能力,降级为独立摘要模式");
301
+ this.logger.warn("[handoff] agent has no write tool or send capability, falling back to standalone summary mode");
302
302
  mode = "summary";
303
303
  }
304
304
  if (mode === "self") {
305
305
  const ok = await this.runSelfHandoff(agent, handoffPath, signal);
306
306
  if (!ok) {
307
- this.logger.warn("[handoff] 自交接失败,降级为独立摘要模式");
307
+ this.logger.warn("[handoff] self handoff failed, falling back to standalone summary mode");
308
308
  mode = "summary";
309
309
  }
310
310
  }
@@ -375,10 +375,10 @@ export class HandoffService {
375
375
  this.throwIfAborted(signal);
376
376
  if (await this.isHandoffFileReady(handoffPath))
377
377
  return true;
378
- this.logger.warn(`[handoff] 交接文档缺失或过小,重试一次:${handoffPath}`);
378
+ this.logger.warn(`[handoff] handoff document missing or too small, retrying once: ${handoffPath}`);
379
379
  await agent.send([
380
- `交接文档不存在或内容过少,无法交付:${handoffPath}`,
381
- "请立即用 write 工具把**完整**交接文档写入该路径(路径原样使用),不要做其他任何事。",
380
+ `The handoff document does not exist or is too small to deliver: ${handoffPath}`,
381
+ "Use the write tool right now to write the **complete** handoff document to that path (use the path exactly as given) and do nothing else.",
382
382
  ].join("\n"));
383
383
  this.throwIfAborted(signal);
384
384
  return await this.isHandoffFileReady(handoffPath);
@@ -388,7 +388,7 @@ export class HandoffService {
388
388
  await this.discardPartialFile(handoffPath, existedBefore);
389
389
  throw new HandoffAbortedError();
390
390
  }
391
- this.logger.error(`[handoff] 自交接回合失败:${err?.message ?? err}`);
391
+ this.logger.error(`[handoff] self handoff round failed: ${err?.message ?? err}`);
392
392
  return false;
393
393
  }
394
394
  finally {
@@ -421,7 +421,7 @@ export class HandoffService {
421
421
  }, signal ? { signal } : undefined);
422
422
  const handoffDoc = response.choices[0]?.message?.content;
423
423
  if (!handoffDoc || !handoffDoc.trim()) {
424
- throw new Error("未能生成任务交接文档");
424
+ throw new Error("Failed to generate the handoff document");
425
425
  }
426
426
  return handoffDoc;
427
427
  }
@@ -446,7 +446,7 @@ export class HandoffService {
446
446
  if (skill?.content?.trim())
447
447
  return skill.content;
448
448
  }
449
- this.logger.warn(`[handoff] 未找到 ${HANDOFF_SKILL_ID} Skill,使用内置兜底模板(路径:${this.skillDirs.join(", ") || "未配置"})`);
449
+ this.logger.warn(`[handoff] ${HANDOFF_SKILL_ID} skill not found, using the built-in fallback template (paths: ${this.skillDirs.join(", ") || "none configured"})`);
450
450
  return FALLBACK_HANDOFF_TEMPLATE;
451
451
  }
452
452
  /** 守卫按 agent 注册一次(`Agent.use()` 无移除 API,故复用实例 + 开关) */
@@ -466,7 +466,7 @@ export class HandoffService {
466
466
  await hook(ctx);
467
467
  }
468
468
  catch (err) {
469
- this.logger.error(`[handoff] 钩子执行失败: ${err?.message ?? err}`);
469
+ this.logger.error(`[handoff] hook execution failed: ${err?.message ?? err}`);
470
470
  }
471
471
  }
472
472
  }
@@ -2,7 +2,10 @@ import { SdkAgent } from "./SdkAgent.js";
2
2
  import type { Scope } from "../scope/Scope.js";
3
3
  import type { AbortedMessageStrategy, AbortedToolRoundStrategy } from "@ai-zen/agents-core";
4
4
  /**
5
- * `createAgent` 的可选行为开关(不传则全部沿用 core 默认值)。
5
+ * `createAgent` 的可选行为开关。
6
+ *
7
+ * 不传时先取 `scope.config`(`config.json`)里的同名配置,再往下才是 core 默认值:
8
+ * 中断策略属于**产品配置**,调用方不该自己读配置再转一道参数。
6
9
  */
7
10
  export interface CreateAgentOptions {
8
11
  /** 工具轮次被中断(或结果不完整)时的归一策略(core 默认 `fill`) */
@@ -10,8 +13,9 @@ export interface CreateAgentOptions {
10
13
  /** `fill` 策略下,未完成工具结果的填充原因 */
11
14
  abortedToolRoundReason?: string;
12
15
  /**
13
- * 被中断的 assistant 消息(已输出的内容 / 思考)如何处置(core 默认 `drop`)。
14
- * 传 `keep` 可让模型在下一轮接着半截往下说,而不是忘掉自己说过什么。
16
+ * 被中断的 assistant 消息(已输出的内容 / 思考)如何处置(未传时取 `scope.config`,
17
+ * 再缺则用 core 默认 `drop`)。
18
+ * `keep` 可让模型在下一轮接着半截往下说,而不是忘掉自己说过什么。
15
19
  */
16
20
  abortedMessageStrategy?: AbortedMessageStrategy;
17
21
  }
@@ -14,10 +14,10 @@ import { createModel } from "./createModel.js";
14
14
  export async function createAgent(scope, agentId, options = {}) {
15
15
  const definition = await new AgentRepository(scope.agentsDir).read(agentId);
16
16
  if (!definition)
17
- throw new Error(`Agent "${agentId}" 不存在`);
17
+ throw new Error(`Agent "${agentId}" not found`);
18
18
  const modelId = definition.modelId;
19
19
  if (!modelId) {
20
- throw new Error(`Agent "${agentId}" 未绑定模型(缺少 modelId)。请在配置里给它指定一个模型。`);
20
+ throw new Error(`Agent "${agentId}" has no model bound (modelId is missing). Assign a model to it in the config`);
21
21
  }
22
22
  const { client, model, modelConfig } = createModel(scope, modelId, definition.modelParams);
23
23
  const tools = scope.buildTools(definition, {
@@ -40,10 +40,12 @@ export async function createAgent(scope, agentId, options = {}) {
40
40
  // 快照后,运行时改动只作用于会话副本,模板保持纯净。
41
41
  messages: [...(definition.messages ?? [])],
42
42
  tools,
43
- // 中断相关策略:不传则由 core 取默认值(工具轮次 fill / 消息保留 drop)
44
- abortedToolRoundStrategy: options.abortedToolRoundStrategy,
43
+ // 中断相关策略:**显式参数 > `scope.config` > core 默认值**。
44
+ // 中间那一层是「配置即事实」:策略写在 `config.json` 里,各端(CLI / Desktop)
45
+ // 不必自己读配置再转成参数,子 Agent 创建时也能自然继承同一份策略。
46
+ abortedToolRoundStrategy: options.abortedToolRoundStrategy ?? scope.config?.abortedToolRoundStrategy,
45
47
  abortedToolRoundReason: options.abortedToolRoundReason,
46
- abortedMessageStrategy: options.abortedMessageStrategy,
48
+ abortedMessageStrategy: options.abortedMessageStrategy ?? scope.config?.abortedMessageStrategy,
47
49
  });
48
50
  return agent;
49
51
  }
@@ -26,12 +26,12 @@ export function createModel(scope, modelId, overrides) {
26
26
  const config = scope.config;
27
27
  const modelConfig = config.models.find((m) => m.id === modelId);
28
28
  if (!modelConfig)
29
- throw new Error(`模型 "${modelId}" 不存在`);
29
+ throw new Error(`Model "${modelId}" not found`);
30
30
  const endpointConfig = config.endpoints.find((e) => e.id === modelConfig.endpointId);
31
31
  if (!endpointConfig)
32
- throw new Error(`端点 "${modelConfig.endpointId}" 未配置`);
32
+ throw new Error(`Endpoint "${modelConfig.endpointId}" is not configured`);
33
33
  if (!endpointConfig.apiKey) {
34
- throw new Error(`端点 "${endpointConfig.name}" 的 API Key 未设置`);
34
+ throw new Error(`API key for endpoint "${endpointConfig.name}" is not set`);
35
35
  }
36
36
  const client = new OpenAI({
37
37
  apiKey: endpointConfig.apiKey,
@@ -44,7 +44,7 @@ export class PermissionEvaluator {
44
44
  }
45
45
  validatePolicy(policy) {
46
46
  if ("allow" in policy && "deny" in policy) {
47
- throw new Error("权限维度不能同时配置 allow 和 deny");
47
+ throw new Error("A permission dimension cannot set both allow and deny");
48
48
  }
49
49
  }
50
50
  }
@@ -57,7 +57,7 @@ export class Scope {
57
57
  */
58
58
  use(...plugins) {
59
59
  if (this._initialized) {
60
- throw new Error("Scope.use() 必须在 init() 之前调用");
60
+ throw new Error("Scope.use() must be called before init()");
61
61
  }
62
62
  for (const plugin of plugins) {
63
63
  this._plugins.push(plugin);
@@ -4,33 +4,33 @@ export class BatchEditTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "batch_edit",
6
6
  description: [
7
- "批量编辑文件文本,可以优先使用这个工具对文件进行编辑。",
8
- "对同一个文件的多处修改应在一次调用中全部提交(replacements 数组),而不是并行多次调用 edit——并行编辑同一文件会互相覆盖。",
7
+ "Edit text in a file in batches. Prefer this tool when making edits to a file.",
8
+ "Submit all changes to the same file in a single call (the replacements array) instead of calling edit in parallel multiple times — parallel edits to the same file overwrite each other.",
9
9
  ].join("\n"),
10
10
  parameters: {
11
11
  type: "object",
12
12
  properties: {
13
13
  path: {
14
14
  type: "string",
15
- description: "文件路径",
15
+ description: "File path",
16
16
  },
17
17
  replacements: {
18
18
  type: "array",
19
- description: "要替换的文本数组",
19
+ description: "Array of replacements",
20
20
  items: {
21
21
  type: "object",
22
22
  properties: {
23
23
  oldText: {
24
24
  type: "string",
25
- description: "要替换的文本",
25
+ description: "Text to replace",
26
26
  },
27
27
  newText: {
28
28
  type: "string",
29
- description: "替换后的文本",
29
+ description: "Replacement text",
30
30
  },
31
31
  isReplaceAll: {
32
32
  type: "boolean",
33
- description: "是否替换所有匹配的文本(默认仅替换首次匹配)。使用此功能前应确保你提供的 oldText 足够精确,避免误替换",
33
+ description: "Whether to replace all matches (by default only the first match is replaced). Make sure the oldText you provide is precise enough to avoid unintended replacements",
34
34
  default: false,
35
35
  },
36
36
  },
@@ -60,7 +60,7 @@ export class BatchEditTool extends SdkCallbackTool {
60
60
  failed.push({
61
61
  oldText: replacement.oldText,
62
62
  newText: replacement.newText,
63
- reason: "文件中未精确匹配到要替换的文本",
63
+ reason: "No exact match found for the text to replace",
64
64
  });
65
65
  continue;
66
66
  }
@@ -3,21 +3,21 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class CopyTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "copy",
6
- description: "复制文件或目录(相当于 cp 命令)",
6
+ description: "Copy a file or directory (equivalent to the cp command)",
7
7
  parameters: {
8
8
  type: "object",
9
9
  properties: {
10
10
  src: {
11
11
  type: "string",
12
- description: "源路径",
12
+ description: "Source path",
13
13
  },
14
14
  dest: {
15
15
  type: "string",
16
- description: "目标路径",
16
+ description: "Destination path",
17
17
  },
18
18
  recursive: {
19
19
  type: "boolean",
20
- description: "是否递归复制目录,复制目录时需设为 true",
20
+ description: "Whether to copy directories recursively; must be true when copying a directory",
21
21
  },
22
22
  },
23
23
  required: ["src", "dest"],
@@ -2,7 +2,7 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
2
2
  export class CwdTool extends SdkCallbackTool {
3
3
  function = {
4
4
  name: "cwd",
5
- description: "获取当前工作目录 cwd",
5
+ description: "Get the current working directory (cwd)",
6
6
  parameters: {
7
7
  type: "object",
8
8
  properties: {},
@@ -4,17 +4,17 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
4
4
  export class DownloadFileTool extends SdkCallbackTool {
5
5
  function = {
6
6
  name: "download_file",
7
- description: "从 URL 下载文件(图片、PDF 等二进制文件)并保存到本地磁盘。自动从 URL 推断文件名,也可指定保存路径。",
7
+ description: "Download a file from a URL (images, PDFs and other binary files) and save it to the local disk. The file name is inferred from the URL, or you can specify the save path.",
8
8
  parameters: {
9
9
  type: "object",
10
10
  properties: {
11
11
  url: {
12
12
  type: "string",
13
- description: "文件下载链接",
13
+ description: "URL to download the file from",
14
14
  },
15
15
  outputPath: {
16
16
  type: "string",
17
- description: "保存路径(可选)。可以是目录或完整文件路径。不指定则下载到当前工作目录,自动从 URL 推断文件名。",
17
+ description: "Save path (optional). May be a directory or a full file path. If omitted, the file is saved to the current working directory with the file name inferred from the URL.",
18
18
  },
19
19
  },
20
20
  required: ["url"],
@@ -30,7 +30,7 @@ export class DownloadFileTool extends SdkCallbackTool {
30
30
  try {
31
31
  const url = input.url;
32
32
  if (!url)
33
- throw new Error("url 不能为空");
33
+ throw new Error("url must not be empty");
34
34
  if (input.outputPath) {
35
35
  savePath = this.resolve(input.outputPath);
36
36
  const stat = await fsp.stat(savePath).catch(() => null);
@@ -48,7 +48,7 @@ export class DownloadFileTool extends SdkCallbackTool {
48
48
  await fsp.mkdir(path.dirname(savePath), { recursive: true });
49
49
  const response = await fetch(url, { signal });
50
50
  if (!response.ok) {
51
- throw new Error(`下载失败: HTTP ${response.status} ${response.statusText}`);
51
+ throw new Error(`Download failed: HTTP ${response.status} ${response.statusText}`);
52
52
  }
53
53
  const contentType = response.headers.get("content-type") || "";
54
54
  const arrayBuffer = await response.arrayBuffer();
@@ -61,7 +61,7 @@ export class DownloadFileTool extends SdkCallbackTool {
61
61
  filePath: path.resolve(savePath),
62
62
  fileSize: `${fileSizeKB} KB`,
63
63
  contentType,
64
- note: "文件已保存到本地",
64
+ note: "File saved to local disk",
65
65
  });
66
66
  }
67
67
  catch (error) {
@@ -71,12 +71,12 @@ export class DownloadFileTool extends SdkCallbackTool {
71
71
  return JSON.stringify({
72
72
  success: false,
73
73
  aborted: true,
74
- error: "下载被中断(aborted)",
74
+ error: "Download aborted",
75
75
  });
76
76
  }
77
77
  return JSON.stringify({
78
78
  success: false,
79
- error: error?.message || "下载失败",
79
+ error: error?.message || "Download failed",
80
80
  });
81
81
  }
82
82
  }
@@ -16,29 +16,29 @@ export class EditTool extends SdkCallbackTool {
16
16
  function = {
17
17
  name: "edit",
18
18
  description: [
19
- "编辑文件中的文本,支持精确替换或替换所有匹配项。",
19
+ "Edit text in a file, supporting exact replacement or replacing all matches.",
20
20
  "",
21
- "并发限制(重要):同一个文件禁止并行编辑。不要在同一轮回复中对同一路径发起多个 edit 调用——这些调用会各自读取旧内容再写回,后写者覆盖先写者,导致修改丢失或文件被写坏。",
22
- "如需对同一个文件施加多处修改,请改用 batch_edit,在一次调用里提交全部替换。只有编辑不同文件时才可以并行调用 edit。",
21
+ "Concurrency limit (important): never edit the same file in parallel. Do not issue multiple edit calls for the same path within one reply — each call reads the old content and writes it back, so the later write overwrites the earlier one, losing changes or corrupting the file.",
22
+ "To apply multiple changes to one file, use batch_edit instead and submit all replacements in a single call. Only edit different files in parallel.",
23
23
  ].join("\n"),
24
24
  parameters: {
25
25
  type: "object",
26
26
  properties: {
27
27
  path: {
28
28
  type: "string",
29
- description: "文件路径",
29
+ description: "File path",
30
30
  },
31
31
  oldText: {
32
32
  type: "string",
33
- description: "要替换的文本",
33
+ description: "Text to replace",
34
34
  },
35
35
  newText: {
36
36
  type: "string",
37
- description: "替换后的文本",
37
+ description: "Replacement text",
38
38
  },
39
39
  isReplaceAll: {
40
40
  type: "boolean",
41
- description: "是否替换所有匹配的文本(默认 false,仅替换首次匹配)。使用此功能前应确保你提供的 oldText 足够精确,避免误替换",
41
+ description: "Whether to replace all matches (default false, only the first match is replaced). Make sure the oldText you provide is precise enough to avoid unintended replacements",
42
42
  default: false,
43
43
  },
44
44
  },
@@ -59,7 +59,7 @@ export class EditTool extends SdkCallbackTool {
59
59
  const count = countOccurrences(content, oldText);
60
60
  if (count === 0) {
61
61
  return JSON.stringify({
62
- result: "文件中未精确匹配到要替换的文本",
62
+ result: "No exact match found for the text to replace",
63
63
  matchCount: 0,
64
64
  });
65
65
  }
@@ -69,7 +69,7 @@ export class EditTool extends SdkCallbackTool {
69
69
  await fsp.writeFile(filePath, newContent);
70
70
  if (!isReplaceAll && count > 1) {
71
71
  return JSON.stringify({
72
- result: `警告:未开启替换所有(isReplaceAll=false),但文本 "${oldText}" 在文件中出现了 ${count} 次,仅替换了首次匹配。如果希望替换所有匹配,请将 isReplaceAll 设为 true`,
72
+ result: `Warning: replace-all is off (isReplaceAll=false), but the text "${oldText}" occurs ${count} times in the file; only the first match was replaced. Set isReplaceAll to true to replace all matches`,
73
73
  matchCount: count,
74
74
  replacedCount: 1,
75
75
  });
@@ -4,17 +4,17 @@ import { DEFAULT_MAX_TOOL_OUTPUT, guardOutput, headTailPreview } from "./outputG
4
4
  export class ExecTool extends SdkCallbackTool {
5
5
  function = {
6
6
  name: "exec",
7
- description: "执行命令",
7
+ description: "Execute a shell command",
8
8
  parameters: {
9
9
  type: "object",
10
10
  properties: {
11
11
  command: {
12
12
  type: "string",
13
- description: "要执行的命令",
13
+ description: "Command to execute",
14
14
  },
15
15
  timeout: {
16
16
  type: "number",
17
- description: "超时时间(毫秒),必填。超时后会终止进程;超过该时长仍未完成即视为超时并终止。建议长时间运行的命令使用 spawn 异步执行。",
17
+ description: "Timeout in milliseconds (required). The process is killed on timeout. For long-running commands, prefer spawn to run them asynchronously.",
18
18
  },
19
19
  },
20
20
  required: ["command", "timeout"],
@@ -29,7 +29,7 @@ export class ExecTool extends SdkCallbackTool {
29
29
  const { timeout } = input;
30
30
  // 运行时双校验:timeout 必填且必须为正数
31
31
  if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) {
32
- throw new Error(`exec: 参数 timeout 为必填项,且必须是正数(毫秒),当前值: ${JSON.stringify(timeout)}`);
32
+ throw new Error(`exec: timeout is required and must be a positive number (milliseconds), got: ${JSON.stringify(timeout)}`);
33
33
  }
34
34
  const signal = ctx?.signal;
35
35
  const result = await new Promise((resolve) => {
@@ -37,7 +37,7 @@ export class ExecTool extends SdkCallbackTool {
37
37
  if (signal?.aborted) {
38
38
  resolve({
39
39
  stdout: "",
40
- stderr: "进程已中断(aborted)",
40
+ stderr: "Process aborted",
41
41
  exitCode: null,
42
42
  killed: true,
43
43
  terminated: "aborted",
@@ -72,7 +72,7 @@ export class ExecTool extends SdkCallbackTool {
72
72
  // 即便 exec 回调因信号处理而未能及时触发,也立即返回中断结果,避免挂起
73
73
  settle({
74
74
  stdout: "",
75
- stderr: "进程已中断(aborted)",
75
+ stderr: "Process aborted",
76
76
  exitCode: null,
77
77
  killed: true,
78
78
  terminated: "aborted",
@@ -97,8 +97,8 @@ export class ExecTool extends SdkCallbackTool {
97
97
  const stderr = headTailPreview(result.stderr);
98
98
  return JSON.stringify({
99
99
  outputTooLarge: true,
100
- warning: `输出过大:stdout + stderr 合计 ${result.stdout.length + result.stderr.length} 字符,` +
101
- `超过上限 ${limit} 字符。完整内容已分文件落盘(stdout.log / stderr.log),可用 read 分批读取。`,
100
+ warning: `Output too large: stdout + stderr total ${result.stdout.length + result.stderr.length} characters, ` +
101
+ `exceeds the limit of ${limit} characters. Full content was written to separate files (stdout.log / stderr.log); use read to fetch it in chunks`,
102
102
  files: ctx.files,
103
103
  stdout: { chars: result.stdout.length, lines: countLines(result.stdout), ...stdout },
104
104
  stderr: { chars: result.stderr.length, lines: countLines(result.stderr), ...stderr },
@@ -3,13 +3,13 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class ExistTool extends SdkCallbackTool {
4
4
  function = {
5
5
  name: "exist",
6
- description: "检查文件或目录是否存在",
6
+ description: "Check whether a file or directory exists",
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
  },
15
15
  required: ["path"],
@@ -11,7 +11,7 @@ const DEFAULT_MAX_LINE_LENGTH = 200;
11
11
  export class FindTextTool extends SdkCallbackTool {
12
12
  function = {
13
13
  name: "find_text",
14
- description: "查找文本出现的位置,支持普通文本或正则匹配,返回文件名及具体行号、行内容。为避免结果撑爆上下文,默认限制总匹配数、单文件大小与行内容长度,超限会截断并标记 truncated。",
14
+ description: "Find where text occurs, supporting plain text or regex matching; returns file names with line numbers and line content. To avoid blowing up the context, the total match count, per-file size and line content length are limited by default, and results beyond the limits are truncated and marked truncated.",
15
15
  parameters: {
16
16
  type: "object",
17
17
  properties: {
@@ -25,30 +25,30 @@ export class FindTextTool extends SdkCallbackTool {
25
25
  },
26
26
  text: {
27
27
  type: "string",
28
- description: "要查找的文本(与 regex 二选一)",
28
+ description: "Text to search for (mutually exclusive with regex)",
29
29
  },
30
30
  regex: {
31
31
  type: "string",
32
- description: "正则表达式(与 text 二选一,例如 \\bconst\\s+\\w+)",
32
+ description: "Regular expression (mutually exclusive with text, e.g. \\bconst\\s+\\w+)",
33
33
  },
34
34
  exclude: {
35
35
  type: "array",
36
- description: "要排除的 glob 模式数组",
36
+ description: "Array of glob patterns to exclude",
37
37
  items: {
38
38
  type: "string",
39
39
  },
40
40
  },
41
41
  maxMatches: {
42
42
  type: "number",
43
- description: "总匹配数上限,默认 200",
43
+ description: "Maximum total matches (default 200)",
44
44
  },
45
45
  maxFileSize: {
46
46
  type: "number",
47
- description: "单文件大小上限(字节),超过则跳过该文件,默认 307200(300KB)",
47
+ description: "Maximum file size in bytes; larger files are skipped (default 307200, i.e. 300KB)",
48
48
  },
49
49
  maxLineLength: {
50
50
  type: "number",
51
- description: "行内容与匹配子串的截断长度,默认 200",
51
+ description: "Truncation length for line content and matched substrings (default 200)",
52
52
  },
53
53
  },
54
54
  required: ["path", "pattern"],
@@ -65,7 +65,7 @@ export class FindTextTool extends SdkCallbackTool {
65
65
  const text = input.text;
66
66
  const regexStr = input.regex;
67
67
  if (!text && !regexStr) {
68
- return "请提供 text 或 regex 参数";
68
+ return "Provide either the text or the regex parameter";
69
69
  }
70
70
  const regex = regexStr ? new RegExp(regexStr) : null;
71
71
  const maxMatches = input.maxMatches ?? DEFAULT_MAX_MATCHES;
@@ -153,8 +153,8 @@ export class FindTextTool extends SdkCallbackTool {
153
153
  const preview = headPreview(json);
154
154
  return JSON.stringify({
155
155
  outputTooLarge: true,
156
- warning: `输出过大:结果共 ${json.length} 字符,超过上限 ${limit} 字符。` +
157
- `完整结果已落盘(result.json),可用 read 分批读取。`,
156
+ warning: `Output too large: the results total ${json.length} characters, exceeding the limit of ${limit} characters. ` +
157
+ `The full result has been written to disk (result.json) and can be read in chunks with read.`,
158
158
  files: ctx.files,
159
159
  chars: json.length,
160
160
  ...preview,