@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
package/README.md CHANGED
@@ -125,6 +125,8 @@ Conditionally injected based on the active model / config:
125
125
 
126
126
  Tool output protection: `AppConfig.maxToolOutput` (in characters, default 32768) is the unified ceiling for tool output. Each tool handles overflow itself — `exec` dumps `stdout.log` / `stderr.log` separately (returning head/tail previews per stream), `find_text` / `glob` / `ls` dump `result.json` (returning a head preview), and `read` only warns and suggests batched reads via `range`. Dumps go to `<tmpdir>/ai-zen/tool-output/<tool>-<timestamp>-<random>/`, one directory per call, with no self-cleanup. See [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md).
127
127
 
128
+ Interrupt strategies live in the config: `AppConfig.abortedMessageStrategy` (`keep` / `drop`, factory `keep` — the model can continue the half-sentence after an interrupt) and `AppConfig.abortedToolRoundStrategy` (`fill` / `drop`, factory `fill` — whole rounds go in or out, because `tool_calls` must pair with results). `createAgent()` reads them from `scope.config` when no explicit option is passed, keeping a single source of truth.
129
+
128
130
  ## Built-in Plugins
129
131
 
130
132
  | Plugin | Description |
@@ -134,6 +136,19 @@ Tool output protection: `AppConfig.maxToolOutput` (in characters, default 32768)
134
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 |
135
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`) |
136
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
+
137
152
  ## Design Principles
138
153
 
139
154
  See the project-root [`PRINCIPLES.md`](../../PRINCIPLES.md):
package/README.zh.md CHANGED
@@ -124,6 +124,8 @@ await scope.dispose(); // 断开 MCP、释放插件资源
124
124
 
125
125
  工具输出保护:`AppConfig.maxToolOutput`(字符数,缺省 32768)是工具输出的统一上限,超限时由各工具自行处置——`exec` 分文件落盘 `stdout.log` / `stderr.log`(返回各流头尾预览),`find_text` / `glob` / `ls` 落盘 `result.json`(返回头部预览),`read` 仅警告并提示用 `range` 分批读取。落盘目录为 `<tmpdir>/ai-zen/tool-output/<工具名>-<时间戳>-<随机串>/`,每次调用独立、不自清理。详见 [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md)。
126
126
 
127
+ 中断策略进配置:`AppConfig.abortedMessageStrategy`(`keep` / `drop`,出厂 `keep`——中断后模型能接着半截往下说)与 `AppConfig.abortedToolRoundStrategy`(`fill` / `drop`,出厂 `fill`——整轮进出,`tool_calls` 必须与结果配对)。`createAgent()` 不传参数时直接取 `scope.config`,于是策略只有一个真相源。
128
+
127
129
  ## 内置插件
128
130
 
129
131
  | 插件 | 说明 |
@@ -133,6 +135,19 @@ await scope.dispose(); // 断开 MCP、释放插件资源
133
135
  | `ContextGuardPlugin` | 上下文安全护栏 — 每轮发请求前检测上一轮 `usage.prompt_tokens`,超过 `maxTokens × ratio`(默认 1.5,即 +50%)时抛 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控 |
134
136
  | `UnknownToolHintPlugin` | 未知工具智能提示 — LLM 调用不存在的工具时,根据 MCP 配置引导使用 `call_mcp_tool` / 提示权限问题(调用方显式 `agent.use` 注册) |
135
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
+
136
151
  ## 设计原则
137
152
 
138
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: [
@@ -237,6 +237,11 @@ export const DEFAULT_APP_CONFIG = {
237
237
  defaultMigrationModel: "deepseek-flash",
238
238
  // 工具输出上限(字符数),与 DEFAULT_MAX_TOOL_OUTPUT 保持一致
239
239
  maxToolOutput: 32_768,
240
+ // 中断策略:出厂取「保留已输出的内容」—— 中断后模型能接着半截往下说,
241
+ // 而不是「失忆重来」(core 的库级默认是 drop,产品层认为那是坑);
242
+ // 工具轮次保持整轮进出(fill),服务端要求 tool_calls 与结果一一配对。
243
+ abortedMessageStrategy: "keep",
244
+ abortedToolRoundStrategy: "fill",
240
245
  };
241
246
  // ---------------------------------------------------------------------------
242
247
  // 默认 MCP 配置(socket-pty 终端 MCP + chrome-devtools 浏览器调试 MCP)
@@ -255,13 +260,13 @@ export const DEFAULT_MCP_CONFIG = {
255
260
  type: "stdio",
256
261
  command: "npx",
257
262
  args: ["-y", "@ai-zen/socket-pty", "mcp"],
258
- description: "可托管的伪终端(pty):spawn/read/wait/write/resize/status/kill",
263
+ description: "Manageable pseudo-terminal (pty): spawn/read/wait/write/resize/status/kill",
259
264
  },
260
265
  "chrome-devtools": {
261
266
  type: "stdio",
262
267
  command: "npx",
263
268
  args: ["-y", "chrome-devtools-mcp@latest"],
264
- 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.",
265
270
  },
266
271
  },
267
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 = [
@@ -58,10 +58,18 @@ export declare function migrateModelReasoning(model: Model): {
58
58
  * 迁移整个配置。只处理对话模型(`models`)——
59
59
  * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
60
60
  *
61
- * 同时清除已废弃的 `defaultModel`:模型的真相源已改为 `AgentDefinition.modelId`,
62
- * 配置级默认值连同字段一起消失(旧值先由 `ConfigManager.bootstrap()` 搬到默认 Agent 上)。
61
+ * 同时做两件字段级收敛:
62
+ * - 清除已废弃的 `defaultModel`(模型真相源已改为 `AgentDefinition.modelId`);
63
+ * - 补齐中断策略(`abortedMessageStrategy` / `abortedToolRoundStrategy`):把缺失或
64
+ * 非法值写成出厂默认。这样「配置即事实」——文件里写着的就是运行时用的,不再需要
65
+ * 使用方各自 `?? "keep"` 地兵分两路兜底。
63
66
  */
64
67
  export declare function migrateConfig(config: AppConfig): {
65
68
  config: AppConfig;
66
69
  changed: boolean;
67
70
  };
71
+ /** 合法的中断策略取值(与 core 的枚举一致) */
72
+ export declare const ABORTED_MESSAGE_STRATEGIES: readonly ["keep", "drop"];
73
+ export declare const ABORTED_TOOL_ROUND_STRATEGIES: readonly ["fill", "drop"];
74
+ /** 缺失 / 非法 → 出厂默认;返回是否有改动(直接改写传入对象) */
75
+ export declare function normalizeAbortStrategies(config: AppConfig): boolean;
@@ -103,8 +103,11 @@ export function migrateModelReasoning(model) {
103
103
  * 迁移整个配置。只处理对话模型(`models`)——
104
104
  * 图片模型(`imageModels`)既没有上下文阈值、也没有思考概念。
105
105
  *
106
- * 同时清除已废弃的 `defaultModel`:模型的真相源已改为 `AgentDefinition.modelId`,
107
- * 配置级默认值连同字段一起消失(旧值先由 `ConfigManager.bootstrap()` 搬到默认 Agent 上)。
106
+ * 同时做两件字段级收敛:
107
+ * - 清除已废弃的 `defaultModel`(模型真相源已改为 `AgentDefinition.modelId`);
108
+ * - 补齐中断策略(`abortedMessageStrategy` / `abortedToolRoundStrategy`):把缺失或
109
+ * 非法值写成出厂默认。这样「配置即事实」——文件里写着的就是运行时用的,不再需要
110
+ * 使用方各自 `?? "keep"` 地兵分两路兜底。
108
111
  */
109
112
  export function migrateConfig(config) {
110
113
  let changed = false;
@@ -121,7 +124,25 @@ export function migrateConfig(config) {
121
124
  delete next.defaultModel;
122
125
  changed = true;
123
126
  }
127
+ if (normalizeAbortStrategies(next))
128
+ changed = true;
124
129
  if (!changed)
125
130
  return { config, changed };
126
131
  return { config: next, changed };
127
132
  }
133
+ /** 合法的中断策略取值(与 core 的枚举一致) */
134
+ export const ABORTED_MESSAGE_STRATEGIES = ["keep", "drop"];
135
+ export const ABORTED_TOOL_ROUND_STRATEGIES = ["fill", "drop"];
136
+ /** 缺失 / 非法 → 出厂默认;返回是否有改动(直接改写传入对象) */
137
+ export function normalizeAbortStrategies(config) {
138
+ let changed = false;
139
+ if (!ABORTED_MESSAGE_STRATEGIES.includes(config.abortedMessageStrategy)) {
140
+ config.abortedMessageStrategy = DEFAULT_APP_CONFIG.abortedMessageStrategy;
141
+ changed = true;
142
+ }
143
+ if (!ABORTED_TOOL_ROUND_STRATEGIES.includes(config.abortedToolRoundStrategy)) {
144
+ config.abortedToolRoundStrategy = DEFAULT_APP_CONFIG.abortedToolRoundStrategy;
145
+ changed = true;
146
+ }
147
+ return changed;
148
+ }
@@ -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
  }