@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
package/README.md CHANGED
@@ -136,6 +136,19 @@ Interrupt strategies live in the config: `AppConfig.abortedMessageStrategy` (`ke
136
136
  | `ContextGuardPlugin` | Context safety guard — before each request, throws `ContextOverflowError` (interrupting the conversation) when the previous round's `usage.prompt_tokens` exceeds `maxTokens × ratio` (default 1.5), preventing context runaway from reading oversized files |
137
137
  | `UnknownToolHintPlugin` | Smarter unknown-tool hints — when the LLM calls a nonexistent tool, guides it to `call_mcp_tool` / points out permission issues based on MCP config (opt-in via `agent.use`) |
138
138
 
139
+ ## Language
140
+
141
+ The SDK is **English-only**: every model-facing and user-facing string — tool schema descriptions, factory prompts, runtime-injected guidance, tool results, error messages, factory catalog data — is a fixed English literal. There is no locale field, no message catalog, and no runtime language switch. UI language belongs entirely to each consumer (the CLI's `i18n/`, the Desktop's language setting).
142
+
143
+ This is a performance boundary, not a convenience choice — **SDK behaviour and performance must not vary by the user's language**:
144
+
145
+ - **Thresholds are absolute.** `Model.tokenThresholds.handoff` / `guard` and `AppConfig.maxToolOutput` are absolute token / character counts. A localized tool schema is a different token count, so handoff and context-guard would trip at *different amounts of real content* for the same `config.json` — same config, different behaviour per language.
146
+ - **Schema cost is paid every turn.** Description text is re-sent with every request, so a token delta is a standing cost in request size, context usage and spend rather than a one-off.
147
+ - **Cache prefixes stop being comparable.** Prompt caching matches on the prefix; letting language into the SDK multiplies the prefix shapes of one code path and removes the single variable that benchmarks rely on.
148
+ - **Diagnosis needs one shape.** Performance data, bug reproductions and tool-call success rates have to be comparable across users; once behaviour drifts with locale, every conclusion needs a "what language are you on?" qualifier first.
149
+
150
+ Following the user's language is handled by one language-neutral instruction in the factory system prompt — `Always reply in the same language the user writes in.` — which adapts per **message** rather than per installation, and needs no locale plumbing in the SDK. Full rationale: [`docs/sdk-design-v1.md` §1](docs/sdk-design-v1.md).
151
+
139
152
  ## Design Principles
140
153
 
141
154
  See the project-root [`PRINCIPLES.md`](../../PRINCIPLES.md):
package/README.zh.md CHANGED
@@ -135,6 +135,19 @@ await scope.dispose(); // 断开 MCP、释放插件资源
135
135
  | `ContextGuardPlugin` | 上下文安全护栏 — 每轮发请求前检测上一轮 `usage.prompt_tokens`,超过 `maxTokens × ratio`(默认 1.5,即 +50%)时抛 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控 |
136
136
  | `UnknownToolHintPlugin` | 未知工具智能提示 — LLM 调用不存在的工具时,根据 MCP 配置引导使用 `call_mcp_tool` / 提示权限问题(调用方显式 `agent.use` 注册) |
137
137
 
138
+ ## 语言
139
+
140
+ SDK 是 **English-only**:所有面向模型与面向用户的文案(工具 schema 描述、出厂提示词、运行期注入语、工具返回值、错误信息、出厂 catalog 数据)都是**固定英文字面量**。没有 locale 字段、没有语言包、没有运行期切换。界面语言完全属于各端(CLI 的 `i18n/`、Desktop 的语言设置)。
141
+
142
+ 这是**性能边界**,不是图省事 —— **SDK 的行为与性能不得随用户语言变化**:
143
+
144
+ - **阈值是绝对量。** `Model.tokenThresholds.handoff` / `guard` 与 `AppConfig.maxToolOutput` 都以 token / 字符的绝对值为判据。工具 schema 换个语言就是另一组 token 数 —— 交接与上下文阻断会在**不同的实际内容量**上触发。同一份 `config.json` 在不同语言下跑出不同行为。
145
+ - **schema 成本每轮都付。** 描述文字随每次请求重发,token 差异是摊在每一轮上的持续开销,会改变请求体积、上下文占用与费用,而不是一次性成本。
146
+ - **缓存前缀不再可比。** prompt 缓存按前缀匹配;语言一旦进入 SDK,同一条代码路径就有了多个前缀形态,基准测试失去单一变量。
147
+ - **基准与排查需要单一形态。** 性能数据、bug 复现、工具调用成功率都要能横向比较;行为若随 locale 漂移,任何结论都得先问「你是什么语言」。
148
+
149
+ 「模型跟随用户语言」由一条语言中立的 system prompt 指令承担 —— `Always reply in the same language the user writes in.` —— 按**每条消息**自适应,比按安装实例绑定 locale 更准确,也不需要 SDK 参与。完整论述见 [`docs/sdk-design-v1.md` §1](docs/sdk-design-v1.md)。
150
+
138
151
  ## 设计原则
139
152
 
140
153
  参见项目根 [`PRINCIPLES.md`](../../PRINCIPLES.md):
@@ -43,7 +43,7 @@ export class AutoMigratePlugin {
43
43
  await service.migrate({ agent, promptTokens, handoffTokens, mode: mode ?? undefined });
44
44
  }
45
45
  catch (err) {
46
- getLogger().error(`[autoMigrate] 迁移失败: ${err?.message ?? err}`);
46
+ getLogger().error(`[autoMigrate] migration failed: ${err?.message ?? err}`);
47
47
  }
48
48
  }
49
49
  }
@@ -19,16 +19,16 @@ export class UnknownToolHintPlugin {
19
19
  this.scope = options.scope;
20
20
  }
21
21
  onUnknownTool(ctx) {
22
- const toolName = ctx.toolCall.function?.name ?? "未知";
22
+ const toolName = ctx.toolCall.function?.name ?? "unknown";
23
23
  const mcpPlugin = this.scope.getPluginById(McpScopePlugin.ID);
24
24
  const hasMcpConfig = mcpPlugin?.hasConfig() ?? false;
25
25
  const hasCallMcpTool = ctx.availableTools.some((t) => t.function.name === "call_mcp_tool");
26
26
  if (hasMcpConfig && !hasCallMcpTool) {
27
- return `工具 "${toolName}" 不存在。当前有 MCP 服务器配置,但 call_mcp_tool 权限已被禁用,如需使用 MCP 工具请调整权限。`;
27
+ return `Tool "${toolName}" does not exist. An MCP server is configured, but the call_mcp_tool permission is disabled — adjust permissions if you need to use MCP tools.`;
28
28
  }
29
29
  if (hasMcpConfig && hasCallMcpTool) {
30
- return `工具 "${toolName}" 不存在。如果要调用 MCP 工具,请使用 call_mcp_tool。`;
30
+ return `Tool "${toolName}" does not exist. To call MCP tools, use call_mcp_tool.`;
31
31
  }
32
- return `工具 "${toolName}" 不存在。`;
32
+ return `Tool "${toolName}" does not exist.`;
33
33
  }
34
34
  }
@@ -14,15 +14,15 @@ export const DEFAULT_MODEL_ID = "deepseek-flash";
14
14
  /** 默认 Agent 定义(不含时间戳) */
15
15
  export const DEFAULT_AGENT_DEFINITION = {
16
16
  id: DEFAULT_AGENT_ID,
17
- name: "默认助手",
18
- description: "默认的 AI 助手,适用于日常问答和任务执行。",
17
+ name: "Default Assistant",
18
+ description: "The default AI assistant, suited for everyday Q&A and task execution.",
19
19
  // 模型写在 Agent 定义上,是运行期唯一真相源(无 config.defaultModel 兜底)
20
20
  modelId: DEFAULT_MODEL_ID,
21
21
  messages: [
22
- Message.System(`你是一个幽默风趣、严谨可靠的智能助手,请用中文回复。当前操作系统:${process.platform}。
22
+ Message.System(`You are a witty, rigorous and reliable assistant. Always reply in the same language the user writes in. Current operating system: ${process.platform}.
23
23
 
24
- 一、需要用户决策时,一次只问一个问题。
25
- 二、当前对话基于 Node.js 驱动,你可以编写 Node.js 脚本执行复杂任务,并通过 exec 工具运行。`),
24
+ 1. When user input is needed for a decision, ask only one question at a time.
25
+ 2. This session runs on Node.js — you can write Node.js scripts to carry out complex tasks and run them via the exec tool.`),
26
26
  ],
27
27
  // 出厂默认 Agent:custom 为 false,SDK 初始化时会同步出厂提示词(用户设为 true 则跳过)
28
28
  custom: false,
@@ -41,10 +41,10 @@ export const DEFAULT_SUBAGENT_ID = "sub-agent-default";
41
41
  /** 默认 SubAgent 定义(不含时间戳) */
42
42
  export const DEFAULT_SUBAGENT_DEFINITION = {
43
43
  id: DEFAULT_SUBAGENT_ID,
44
- name: "通用助手",
45
- description: "一个通用的子 Agent,擅长独立完成各类任务。",
44
+ name: "General Assistant",
45
+ description: "A general-purpose sub-agent that is good at completing all kinds of tasks independently.",
46
46
  messages: [
47
- Message.System("你是一个通用助手子 Agent,被父 Agent 委派来独立完成具体任务。请根据给定的任务描述,主动调用你的工具(文件读写、执行命令、搜索等)来分析和完成任务。完成任务后直接返回结果,不需要解释你的思考过程。\n\n⚠️ 强制性要求:\n1. 如果任务描述中存在任何不明确、模糊或缺失的信息(包括但不限于具体目标、文件路径、约束条件、预期产出等),你必须直接拒绝执行,并明确列出哪些信息不明确或缺失,要求父 Agent 提供更完整的任务上下文。不得自行假设、猜测或脑补任何信息。\n2. 如果任务描述中存在自相矛盾的信息(如相互冲突的要求、不一致的文件路径、矛盾的约束条件等),你必须及时指出矛盾之处,并要求父 Agent 澄清后再执行。"),
47
+ Message.System("You are a general-purpose assistant sub-agent, delegated by a parent agent to independently complete a specific task. Based on the given task description, proactively call your tools (file read/write, command execution, search, etc.) to analyze and complete the task. When the task is done, return the result directly; there is no need to explain your thinking process.\n\n⚠️ Mandatory requirements:\n1. If anything in the task description is unclear, vague or missing (including but not limited to the specific goal, file paths, constraints, expected deliverables, etc.), you MUST refuse to execute immediately and explicitly list what is unclear or missing, asking the parent agent to provide more complete task context. Do not assume, guess or make up any information on your own.\n2. If the task description contains contradictory information (such as mutually conflicting requirements, inconsistent file paths, contradictory constraints, etc.), you MUST point out the contradiction promptly and require the parent agent to clarify before executing."),
48
48
  Message.User("{{task}}"),
49
49
  ],
50
50
  permissions: {
@@ -55,11 +55,11 @@ export const DEFAULT_SUBAGENT_DEFINITION = {
55
55
  },
56
56
  function: {
57
57
  name: "sub_agent_default",
58
- description: "通用子 Agent,可独立完成各类任务。⚠️ 委派任务时必须提供完整的任务上下文,包括所有必要的背景信息、文件路径、具体要求和约束条件,任何信息不明确将导致子 Agent 拒绝执行并要求补充信息。",
58
+ description: "General-purpose sub-agent that can independently complete all kinds of tasks. ⚠️ When delegating a task you must provide complete task context, including all necessary background information, file paths, specific requirements and constraints; any unclear information will cause the sub-agent to refuse execution and ask for more information.",
59
59
  parameters: {
60
60
  type: "object",
61
61
  properties: {
62
- task: { type: "string", description: "完整的任务描述,必须包含所有必要的背景、目标、约束条件和上下文信息。任何不明确的信息将导致子 Agent 拒绝执行。" },
62
+ task: { type: "string", description: "Complete task description; must include all necessary background, goals, constraints and context. Any unclear information will cause the sub-agent to refuse execution." },
63
63
  },
64
64
  required: ["task"],
65
65
  additionalProperties: false,
@@ -77,21 +77,21 @@ export const DEFAULT_APP_CONFIG = {
77
77
  name: "OpenAI",
78
78
  apiKey: "",
79
79
  baseUrl: "https://api.openai.com/v1",
80
- description: "OpenAI API 端点",
80
+ description: "OpenAI API endpoint",
81
81
  },
82
82
  {
83
83
  id: "bigmodelcn",
84
- name: "BigModelCN (智谱AI)",
84
+ name: "BigModelCN (Zhipu AI)",
85
85
  apiKey: "",
86
86
  baseUrl: "https://open.bigmodel.cn/api/paas/v4",
87
- description: "智谱AI大模型端点",
87
+ description: "Zhipu AI large-model endpoint",
88
88
  },
89
89
  {
90
90
  id: "deepseek",
91
91
  name: "DeepSeek",
92
92
  apiKey: "",
93
93
  baseUrl: "https://api.deepseek.com/v1",
94
- description: "DeepSeek API 端点(支持 Files API:本地文件上传后以 file_id 引用)",
94
+ description: "DeepSeek API endpoint (supports the Files API: local files are uploaded and then referenced by file_id)",
95
95
  // 唯一声明了 Files API 能力的出厂端点;其余端点本地文件走 base64 内联。
96
96
  // 判断依据是能力位而非端点 id,用户自建端点只要声明即可享受上传。
97
97
  capabilities: { filesApi: true },
@@ -128,7 +128,7 @@ export const DEFAULT_APP_CONFIG = {
128
128
  defaultParams: {},
129
129
  vision: true,
130
130
  reasoningPreset: "openai",
131
- description: "OpenAI 最强旗舰(2026-09):高级分析、软件工程、深度研究与科学工作",
131
+ description: "OpenAI flagship (2026-09): advanced analysis, software engineering, deep research and scientific work",
132
132
  },
133
133
  {
134
134
  id: "gpt-6.1-sol",
@@ -139,7 +139,7 @@ export const DEFAULT_APP_CONFIG = {
139
139
  defaultParams: {},
140
140
  vision: true,
141
141
  reasoningPreset: "openai",
142
- description: "OpenAI 最新旗舰(2026-09-29):Agentic Coding、计算机操作与文档密集型专业任务",
142
+ description: "OpenAI latest flagship (2026-09-29): agentic coding, computer operation and document-intensive professional tasks",
143
143
  },
144
144
  {
145
145
  id: "gpt-6-luna",
@@ -150,7 +150,7 @@ export const DEFAULT_APP_CONFIG = {
150
150
  defaultParams: {},
151
151
  vision: true,
152
152
  reasoningPreset: "openai",
153
- description: "OpenAI GPT-6 系列快速低成本模型:适合高吞吐、低延迟场景",
153
+ description: "OpenAI GPT-6 series fast, low-cost model: suited for high-throughput, low-latency scenarios",
154
154
  },
155
155
  // ---------- 智谱 BigModel ----------
156
156
  {
@@ -161,7 +161,7 @@ export const DEFAULT_APP_CONFIG = {
161
161
  tokenThresholds: { handoff: 250_000, guard: 375_000 },
162
162
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
163
163
  reasoningPreset: "zhipu-5.3",
164
- description: "智谱最新旗舰:复杂软件工程与长程 Agent 任务(强制开启思考,1M 上下文)",
164
+ description: "Zhipu latest flagship: complex software engineering and long-horizon agent tasks (thinking always on, 1M context)",
165
165
  },
166
166
  {
167
167
  id: "glm-5.3-flash",
@@ -172,7 +172,7 @@ export const DEFAULT_APP_CONFIG = {
172
172
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
173
173
  vision: true,
174
174
  reasoningPreset: "zhipu-5.3",
175
- description: "智谱原生多模态模型(图片/视频/文件):视觉 Coding,1M 上下文",
175
+ description: "Zhipu native multimodal model (image/video/file): visual coding, 1M context",
176
176
  },
177
177
  {
178
178
  id: "glm-5.3-flashx",
@@ -183,7 +183,7 @@ export const DEFAULT_APP_CONFIG = {
183
183
  defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
184
184
  vision: true,
185
185
  reasoningPreset: "zhipu-5.3",
186
- description: "GLM-5.3-Flash 高速版(200 tokens/s),原生多模态",
186
+ description: "GLM-5.3-Flash high-speed edition (200 tokens/s), native multimodal",
187
187
  },
188
188
  {
189
189
  id: "glm-4.7-flash",
@@ -193,7 +193,7 @@ export const DEFAULT_APP_CONFIG = {
193
193
  // 该模型真实窗口仅 200K,阈值下调至 100K(约窗口的 50%),留足 response 空间
194
194
  tokenThresholds: { handoff: 100_000, guard: 150_000 },
195
195
  defaultParams: {},
196
- description: "智谱免费文本模型:200K 上下文,适合轻量任务",
196
+ description: "Zhipu free text model: 200K context, suited for lightweight tasks",
197
197
  },
198
198
  // ---------- DeepSeek ----------
199
199
  {
@@ -205,7 +205,7 @@ export const DEFAULT_APP_CONFIG = {
205
205
  defaultParams: { thinking: { type: "enabled" } },
206
206
  vision: true,
207
207
  reasoningPreset: "deepseek",
208
- description: "DeepSeek 旗舰(2026-09-10):552B MoE 非对称架构,原生多模态,1M 上下文",
208
+ description: "DeepSeek flagship (2026-09-10): 552B MoE asymmetric architecture, native multimodal, 1M context",
209
209
  },
210
210
  ],
211
211
  imageModels: [
@@ -260,13 +260,13 @@ export const DEFAULT_MCP_CONFIG = {
260
260
  type: "stdio",
261
261
  command: "npx",
262
262
  args: ["-y", "@ai-zen/socket-pty", "mcp"],
263
- description: "可托管的伪终端(pty):spawn/read/wait/write/resize/status/kill",
263
+ description: "Manageable pseudo-terminal (pty): spawn/read/wait/write/resize/status/kill",
264
264
  },
265
265
  "chrome-devtools": {
266
266
  type: "stdio",
267
267
  command: "npx",
268
268
  args: ["-y", "chrome-devtools-mcp@latest"],
269
- description: "Chrome DevTools 浏览器调试与自动化(Google chrome-devtools-mcp):页面导航、性能追踪、网络与控制台检查、截图等",
269
+ description: "Chrome DevTools browser debugging and automation (Google chrome-devtools-mcp): page navigation, performance tracing, network and console inspection, screenshots, etc.",
270
270
  },
271
271
  },
272
272
  };
@@ -19,94 +19,94 @@ export const HANDOFF_SKILL_ID = "handoff";
19
19
  * 它是**保险丝**,不是第二真相源:正常路径下模板一律来自 SKILL.md。
20
20
  */
21
21
  export const FALLBACK_HANDOFF_TEMPLATE = [
22
- "# 交接文档",
22
+ "# Handoff Document",
23
23
  "",
24
- "立即停工:不再实现任何功能、不继续未完成任务、不向用户提问。",
25
- "唯一任务是:核实当前真实状态 → 把交接文档写到「本次交接参数」给出的路径。",
24
+ "Stop work immediately: implement no further features, do not continue unfinished tasks, and do not ask the user questions.",
25
+ "Your only task is: verify the actual current state → write the handoff document to the path given by the handoff parameters.",
26
26
  "",
27
- "## 💬 对话断点",
28
- "## 🎯 任务目标",
29
- "## 📊 当前状态",
30
- "## ✅ 已完成",
31
- "## 📋 未完成",
32
- "## 🧠 关键决策",
33
- "## ⚠️ 约束与偏好",
34
- "## 📁 文件索引",
35
- "## 🔔 接手第一步",
27
+ "## 💬 Conversation Breakpoint",
28
+ "## 🎯 Task Goal",
29
+ "## 📊 Current State",
30
+ "## ✅ Completed",
31
+ "## 📋 Not Completed",
32
+ "## 🧠 Key Decisions",
33
+ "## ⚠️ Constraints and Preferences",
34
+ "## 📁 File Index",
35
+ "## 🔔 First Step for the Next Agent",
36
36
  "",
37
- "无法核实的信息写「⚠️ 未核实」,禁止推测。",
37
+ 'For anything you cannot verify, write "⚠️ Unverified"; speculation is forbidden.',
38
38
  ].join("\n");
39
39
  /** 出厂 Skill:交接(handoff) */
40
40
  const HANDOFF_SKILL = `---
41
41
  name: handoff
42
- description: 编写任务交接文档——对话停手离场时,把任务目标、当前真实状态、关键决策与下一步写成一份可被他人直接接手的文档。
42
+ description: Write a task handoff document — when you stop work and leave the conversation, put the task goal, the actual current state, key decisions and next steps into a document that someone else can pick up directly.
43
43
  metadata:
44
44
  custom: "false"
45
45
  ---
46
46
 
47
- # 交接文档书写规范
47
+ # Handoff Document Writing Standard
48
48
 
49
- ## 何时适用
49
+ ## When It Applies
50
50
 
51
- 上下文即将超限触发任务迁移、用户明确要求「写交接文档」、或你被要求「交班」时。
51
+ When the context is about to overflow and trigger a task migration, when the user explicitly asks you to "write a handoff document", or when you are asked to "hand over".
52
52
 
53
- ## 铁律(违反即视为失败)
53
+ ## Iron Rules (violating any of them counts as failure)
54
54
 
55
- 1. **立即停工**:不再实现任何功能、不继续未完成的任务、不顺手修 bug、不做重构。
56
- 2. **不向用户提问**,不做方案讨论,不征求确认。
57
- 3. 只做两件事:**核实当前真实状态** → **写文档**。
58
- 4. 允许读取类操作(read / ls / glob / find_text / inspect_file / cwd,以及 git status、git diff、git log 等只读命令)。
59
- 禁止修改工作区:不改源码、不 npm install、不 commit、不 push。唯一允许写入的文件是交接文档本身。
60
- 5. **一次性写完**,不要反复重写;不要读大文件;不要把长代码贴进文档。
55
+ 1. **Stop work immediately**: implement no further features, do not continue unfinished tasks, do not fix bugs in passing, do not refactor.
56
+ 2. **Do not ask the user questions**, do not discuss approaches, do not ask for confirmation.
57
+ 3. Do only two things: **verify the actual current state** → **write the document**.
58
+ 4. Read-only operations are allowed (read / ls / glob / find_text / inspect_file / cwd, plus read-only commands such as git status, git diff, git log).
59
+ Modifying the workspace is forbidden: do not change source code, do not npm install, do not commit, do not push. The only file you are allowed to write is the handoff document itself.
60
+ 5. **Write it in one pass**, do not rewrite it over and over; do not read large files; do not paste long code into the document.
61
61
 
62
- ## 落盘
62
+ ## Writing the File
63
63
 
64
- 路径由「本次交接参数」给出,**原样使用**,不得改名、不得换目录、不得省略。
65
- 用 write 工具写入后,用 read 回读一次,确认内容完整(无 read 能力时跳过此步)。
64
+ The path is given by the handoff parameters — **use it verbatim**; do not rename it, do not move it to another directory, do not omit it.
65
+ After writing with the write tool, read it back once with the read tool to confirm the content is complete (skip this step if you have no read capability).
66
66
 
67
- ## 文档结构(按序书写,标题保持原样)
67
+ ## Document Structure (write in this order, keep the headings exactly as they are)
68
68
 
69
- ## 💬 对话断点
70
- 用户最后要求了什么、你最后回复/交付了什么。各一段,不要复述整段对话。
69
+ ## 💬 Conversation Breakpoint
70
+ What the user last asked for, and what you last replied with or delivered. One paragraph each; do not restate the whole conversation.
71
71
 
72
- ## 🎯 任务目标
73
- 用户到底想要什么(意图与验收标准),而不是你执行过的步骤清单。
72
+ ## 🎯 Task Goal
73
+ What the user actually wants (intent and acceptance criteria), not a checklist of the steps you performed.
74
74
 
75
- ## 📊 当前状态
76
- 分支与 commit、未提交改动、测试是否通过、有无进程/端口在跑、依赖是否装好。
75
+ ## 📊 Current State
76
+ Branch and commit, uncommitted changes, whether tests pass, whether any process/port is running, whether dependencies are installed.
77
77
 
78
- ## ✅ 已完成
79
- 每项写:做了什么 + 产出路径(文件/目录) + 怎么验证它确实生效。
78
+ ## ✅ Completed
79
+ For each item write: what was done + the output path (file/directory) + how to verify that it really took effect.
80
80
 
81
- ## 📋 未完成
82
- 按优先级排列;每项写「下一步最小动作」,具体到文件或可执行命令。
81
+ ## 📋 Not Completed
82
+ Order by priority; for each item write "the smallest next action", down to a specific file or runnable command.
83
83
 
84
- ## 🧠 关键决策
85
- 为什么这样做(取舍理由);以及走过的死路——防止接手者重蹈覆辙。
84
+ ## 🧠 Key Decisions
85
+ Why it was done this way (the trade-off reasoning); and the dead ends you walked into — so the next person does not repeat them.
86
86
 
87
- ## ⚠️ 约束与偏好
88
- 用户强调过的规矩、项目约定、不可改动的部分、必须遵守的风格。
87
+ ## ⚠️ Constraints and Preferences
88
+ Rules the user stressed, project conventions, parts that must not be changed, styles that must be followed.
89
89
 
90
- ## 📁 文件索引
91
- 路径 + 作用 + 本次是否改动(新增 / 修改 / 仅阅读)。
90
+ ## 📁 File Index
91
+ Path + purpose + whether it changed this time (added / modified / read only).
92
92
 
93
- ## 🔔 接手第一步
94
- 3 条以内可直接执行的命令或动作,让接手者立刻能开工。
93
+ ## 🔔 First Step for the Next Agent
94
+ At most 3 commands or actions that can be run directly, so the next person can start working immediately.
95
95
 
96
- ## 状态核实清单
96
+ ## State Verification Checklist
97
97
 
98
- - \`git status\` / \`git diff --stat\`:当前分支、是否有未提交改动
99
- - 最近一次测试结果:跑过就写结果摘要;没跑就写「未运行」,**不要写「应该能过」**
100
- - 交接涉及的关键文件:用 ls / read 确认确实存在于磁盘
101
- - 正在运行的服务与端口:\`ps\` / \`lsof -i\` 或项目自带的启动脚本
98
+ - \`git status\` / \`git diff --stat\`: current branch, whether there are uncommitted changes
99
+ - The latest test result: if you ran tests, write a summary of the result; if you did not, write "not run" — **do not write "it should pass"**
100
+ - Key files involved in the handoff: use ls / read to confirm they really exist on disk
101
+ - Running services and ports: \`ps\` / \`lsof -i\`, or the project's own start script
102
102
 
103
- > ⚠️ 若「本次交接参数」声明工具不可用:以上每一项都写「⚠️ 未核实」,**禁止推测**具体路径、进度与测试状态。
103
+ > ⚠️ If the handoff parameters state that tools are unavailable: write "⚠️ Unverified" for every item above; **speculation is forbidden** about specific paths, progress and test status.
104
104
 
105
- ## 写作要求
105
+ ## Writing Requirements
106
106
 
107
- - 每句话都要让接手者能「立刻开始干活」;禁止「继续开发」「完善功能」「优化一下」这类空话。
108
- - 已无法在当前上下文中确定的信息,写「需向用户确认 XXX」,不要编造。
109
- - 全文控制在 300 行以内。
107
+ - Every sentence must let the next person "start working immediately"; empty phrases such as "continue development", "polish the features", "do some optimization" are forbidden.
108
+ - For information you can no longer determine in the current context, write "needs user confirmation on XXX"; do not fabricate it.
109
+ - Keep the whole document within 300 lines.
110
110
  `;
111
111
  /** 全部出厂 Skill(顺序无意义,按 id 去重) */
112
112
  export const FACTORY_SKILLS = [
@@ -55,10 +55,10 @@ export class HandoffGuardPlugin {
55
55
  if (typeof raw === "string" && resolvePath(this.cwd, raw) === handoffPath) {
56
56
  return;
57
57
  }
58
- return `交接回合只允许写交接文档:${handoffPath}(write 的 path 请原样使用该路径),请立即开始撰写。`;
58
+ return `During the handoff round the only allowed write is the handoff document: ${handoffPath} (pass that path to write exactly as given), start writing now.`;
59
59
  }
60
60
  if (HandoffGuardPlugin.WRITE_TOOLS.has(name)) {
61
- return `交接回合禁止修改工作区(${name}):你的唯一任务是把交接文档写入 ${handoffPath}。`;
61
+ return `Modifying the workspace is not allowed during the handoff round (${name}): your only task is to write the handoff document to ${handoffPath}.`;
62
62
  }
63
63
  return;
64
64
  }
@@ -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
  }
@@ -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, {
@@ -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);