@ai-zen/agents-sdk 1.0.0-alpha.0 → 1.0.0-alpha.10

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 (44) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +15 -14
  3. package/README.zh.md +15 -14
  4. package/dist/agent-plugins/AutoMigratePlugin.d.ts +33 -7
  5. package/dist/agent-plugins/AutoMigratePlugin.js +20 -6
  6. package/dist/agent-plugins/ContextGuardPlugin.d.ts +13 -3
  7. package/dist/agent-plugins/ContextGuardPlugin.js +9 -6
  8. package/dist/config/ConfigManager.d.ts +29 -1
  9. package/dist/config/ConfigManager.js +84 -2
  10. package/dist/config/constants.d.ts +3 -1
  11. package/dist/config/constants.js +77 -39
  12. package/dist/config/factory-skills.d.ts +33 -0
  13. package/dist/config/factory-skills.js +126 -0
  14. package/dist/index.d.ts +6 -4
  15. package/dist/index.js +5 -2
  16. package/dist/runtime/HandoffGuardPlugin.d.ts +35 -0
  17. package/dist/runtime/HandoffGuardPlugin.js +65 -0
  18. package/dist/runtime/HandoffService.d.ts +199 -0
  19. package/dist/runtime/HandoffService.js +372 -0
  20. package/dist/scope/plugins/builtin/BatchEditTool.js +1 -1
  21. package/dist/scope/plugins/builtin/DownloadFileTool.js +1 -1
  22. package/dist/scope/plugins/builtin/ExecTool.js +2 -2
  23. package/dist/scope/plugins/builtin/FindTextTool.js +6 -6
  24. package/dist/scope/plugins/builtin/GenerateImageTool.d.ts +1 -1
  25. package/dist/scope/plugins/builtin/GenerateImageTool.js +7 -7
  26. package/dist/scope/plugins/builtin/GlobTool.js +1 -1
  27. package/dist/scope/plugins/builtin/InspectFileTool.d.ts +2 -2
  28. package/dist/scope/plugins/builtin/InspectFileTool.js +5 -5
  29. package/dist/scope/plugins/builtin/LsTool.js +1 -1
  30. package/dist/scope/plugins/builtin/{ReadFileTool.d.ts → ReadTool.d.ts} +1 -1
  31. package/dist/scope/plugins/builtin/{ReadFileTool.js → ReadTool.js} +3 -3
  32. package/dist/scope/plugins/builtin/{ExecAsyncTool.d.ts → SpawnTool.d.ts} +1 -1
  33. package/dist/scope/plugins/builtin/{ExecAsyncTool.js → SpawnTool.js} +2 -2
  34. package/dist/scope/plugins/builtin/ViewImageTool.js +2 -2
  35. package/dist/scope/plugins/builtin/{WriteFileTool.d.ts → WriteTool.d.ts} +1 -1
  36. package/dist/scope/plugins/builtin/{WriteFileTool.js → WriteTool.js} +2 -2
  37. package/dist/scope/plugins/builtin/index.d.ts +4 -4
  38. package/dist/scope/plugins/builtin/index.js +7 -7
  39. package/dist/scope/plugins/builtin/outputGuard.d.ts +2 -2
  40. package/dist/scope/plugins/builtin/outputGuard.js +1 -1
  41. package/dist/types/index.d.ts +24 -3
  42. package/package.json +3 -3
  43. package/dist/runtime/TaskMigrationService.d.ts +0 -128
  44. package/dist/runtime/TaskMigrationService.js +0 -211
@@ -1,6 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
4
+ import { FACTORY_SKILLS, isCustomSkill } from "./factory-skills.js";
4
5
  /**
5
6
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
6
7
  */
@@ -54,17 +55,28 @@ export class ConfigManager {
54
55
  }
55
56
  /**
56
57
  * 确保 config.json 存在。不存在时写入出厂默认配置 DSL。
58
+ *
59
+ * 已存在时执行「出厂模型清单同步」(见 syncManagedModels):
60
+ * models / imageModels 中凡是**没有** `custom: true` 的条目一律视为出厂托管,
61
+ * 会被移除并替换为 DEFAULT_APP_CONFIG 中的最新定义;标了 `custom: true` 的
62
+ * 条目视为用户自有,原样保留。同步结果与磁盘内容一致时不写盘。
57
63
  */
58
64
  async ensureDefaultConfig() {
65
+ let existing;
59
66
  try {
60
67
  await fs.access(this.configPath);
61
- return await this.read();
68
+ existing = await this.read();
62
69
  }
63
70
  catch {
64
71
  await this.ensureDirs();
65
72
  await this.write(DEFAULT_APP_CONFIG);
66
73
  return { ...DEFAULT_APP_CONFIG };
67
74
  }
75
+ const synced = syncManagedModels(existing);
76
+ if (JSON.stringify(synced) !== JSON.stringify(existing)) {
77
+ await this.write(synced);
78
+ }
79
+ return synced;
68
80
  }
69
81
  /**
70
82
  * 确保 basePath/agents/default.json 存在。
@@ -167,9 +179,47 @@ export class ConfigManager {
167
179
  const agent = await this.ensureDefaultAgent();
168
180
  const subAgent = await this.ensureDefaultSubAgent();
169
181
  await this.ensureDefaultMcpConfig();
182
+ await this.ensureFactorySkills();
170
183
  return { config, agent, subAgent };
171
184
  }
172
185
  // -----------------------------------------------------------------------
186
+ // 出厂 Skill({basePath}/skills/<id>/SKILL.md)
187
+ // -----------------------------------------------------------------------
188
+ /**
189
+ * 释放 / 同步出厂 Skill。
190
+ *
191
+ * 覆盖策略与 `syncManagedModels` 同构:
192
+ * - 磁盘上不存在 → 写入出厂内容;
193
+ * - 存在且 frontmatter 标了 `custom: "true"` → 用户自有,原样保留;
194
+ * - 存在但未标 → 视为出厂托管,同步为当前版本内容(内容相同则不写盘)。
195
+ *
196
+ * @returns 实际写入的 Skill id 列表(内容未变化 / 用户自有 均不计入)
197
+ */
198
+ async ensureFactorySkills() {
199
+ const written = [];
200
+ for (const skill of FACTORY_SKILLS) {
201
+ const dir = join(this.basePath, "skills", skill.id);
202
+ const file = join(dir, "SKILL.md");
203
+ let existing = null;
204
+ try {
205
+ existing = await fs.readFile(file, "utf-8");
206
+ }
207
+ catch {
208
+ existing = null;
209
+ }
210
+ // 用户声明自有 → 永不覆盖
211
+ if (existing != null && isCustomSkill(existing))
212
+ continue;
213
+ // 内容已一致 → 不重复写盘
214
+ if (existing === skill.content)
215
+ continue;
216
+ await fs.mkdir(dir, { recursive: true });
217
+ await fs.writeFile(file, skill.content, "utf-8");
218
+ written.push(skill.id);
219
+ }
220
+ return written;
221
+ }
222
+ // -----------------------------------------------------------------------
173
223
  // MCP 配置(~/.ai-zen/mcp.json)
174
224
  // -----------------------------------------------------------------------
175
225
  /** mcp.json 路径:{basePath}/mcp.json */
@@ -177,7 +227,7 @@ export class ConfigManager {
177
227
  return join(this.basePath, "mcp.json");
178
228
  }
179
229
  /**
180
- * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty)。
230
+ * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty 与 chrome-devtools)。
181
231
  * 已有文件不被覆盖,尊重用户已配置的 MCP 服务器。
182
232
  * @returns 返回当前生效的 MCP 配置原始内容
183
233
  */
@@ -234,6 +284,38 @@ export class ConfigManager {
234
284
  await fs.rename(tmpPath, path);
235
285
  }
236
286
  }
287
+ /**
288
+ * 同步出厂托管的模型清单(与默认 Agent 的 custom 机制一致)。
289
+ *
290
+ * 规则:
291
+ * - 未标 `custom: true` 的条目 → 由 SDK 托管,从结果中移除,改用 defaults 中的最新定义;
292
+ * - 标了 `custom: true` 的条目 → 用户自有,原样保留;与出厂定义同 id 时用户版本优先;
293
+ * - 默认模型 ID(defaultModel / defaultImageModel / defaultMigrationModel)
294
+ * 在合并后若已不存在 → 回退到出厂默认,避免悬空引用。
295
+ *
296
+ * 端点(endpoints)不参与托管,始终保持用户配置。
297
+ */
298
+ export function syncManagedModels(config) {
299
+ const pick = (existing, defaults) => {
300
+ const kept = (existing ?? []).filter((m) => m.custom === true);
301
+ const keptIds = new Set(kept.map((m) => m.id));
302
+ // 出厂定义在前,用户自有条目随后;同 id 时用户版本胜出(出厂侧已剔除)
303
+ return [...defaults.filter((m) => !keptIds.has(m.id)), ...kept];
304
+ };
305
+ const models = pick(config.models, DEFAULT_APP_CONFIG.models);
306
+ const imageModels = pick(config.imageModels, DEFAULT_APP_CONFIG.imageModels ?? []);
307
+ const modelIds = new Set(models.map((m) => m.id));
308
+ const imageIds = new Set(imageModels.map((m) => m.id));
309
+ const resolve = (id, ids, fallback) => id && ids.has(id) ? id : fallback;
310
+ return {
311
+ ...config,
312
+ models,
313
+ imageModels,
314
+ defaultModel: resolve(config.defaultModel, modelIds, DEFAULT_APP_CONFIG.defaultModel),
315
+ defaultImageModel: resolve(config.defaultImageModel, imageIds, DEFAULT_APP_CONFIG.defaultImageModel),
316
+ defaultMigrationModel: resolve(config.defaultMigrationModel, modelIds, DEFAULT_APP_CONFIG.defaultMigrationModel),
317
+ };
318
+ }
237
319
  /**
238
320
  * 比较两段消息列表的提示词内容是否一致。
239
321
  *
@@ -11,7 +11,9 @@ export declare const DEFAULT_SUBAGENT_DEFINITION: Omit<AgentDefinition, "created
11
11
  export declare const DEFAULT_APP_CONFIG: AppConfig;
12
12
  /**
13
13
  * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
14
- * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
14
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用:
15
+ * - socket-pty 伪终端(pty)能力
16
+ * - chrome-devtools Chrome DevTools 浏览器调试/自动化能力(Google 官方 chrome-devtools-mcp)
15
17
  *
16
18
  * 若文件已存在则不覆盖(尊重用户已有配置)。
17
19
  */
@@ -85,71 +85,101 @@ export const DEFAULT_APP_CONFIG = {
85
85
  description: "DeepSeek API 端点",
86
86
  },
87
87
  ],
88
+ // 模型清单(2026-10-03 依据各厂商官方来源核对):
89
+ // OpenAI → openai.com 发布公告 / API 目录(platform 文档站对脚本有访问限制)
90
+ // 智谱 → docs.bigmodel.cn 模型概览/定价 + open.bigmodel.cn/api/paas/v4/models
91
+ // DeepSeek → api-docs.deepseek.com 定价页与 News + api.deepseek.com/v1/models
92
+ //
93
+ // 更新策略:本清单为「出厂托管」清单。用户 config.json 中凡未标 `custom: true`
94
+ // 的模型(含 imageModels)在初始化时一律被移除并替换为本清单中的最新定义;
95
+ // 标了 `custom: true` 的条目视为用户自有,原样保留。实现见
96
+ // ConfigManager.syncManagedModels。
97
+ //
98
+ // maxContextTokens 是「任务迁移(交接)触发阈值」,不是模型的真实上下文窗口:
99
+ // 迁移判断只看 API 响应返回的 usage.prompt_tokens。窗口 ≥1M 的模型统一取 250K
100
+ // (约 25%),glm-4.7-flash 窗口仅 200K,取 100K。
88
101
  models: [
102
+ // ---------- OpenAI ----------
89
103
  {
90
- id: "gpt-5.5",
91
- name: "GPT-5.5",
104
+ id: "gpt-6-astra",
105
+ name: "GPT-6 Astra",
92
106
  endpointId: "openai",
93
- modelName: "gpt-5.5",
107
+ modelName: "gpt-6-astra",
94
108
  maxContextTokens: 250_000,
95
109
  defaultParams: {},
96
- description: "OpenAI 最新旗舰模型,擅长编程与代码调试、在线研究、数据分析",
110
+ vision: true,
111
+ description: "OpenAI 最强旗舰(2026-09):高级分析、软件工程、深度研究与科学工作",
97
112
  },
98
113
  {
99
- id: "glm-5.1",
100
- name: "GLM-5.1",
101
- endpointId: "bigmodelcn",
102
- modelName: "glm-5.1",
114
+ id: "gpt-6.1-sol",
115
+ name: "GPT-6.1 Sol",
116
+ endpointId: "openai",
117
+ modelName: "gpt-6.1-sol",
103
118
  maxContextTokens: 250_000,
104
119
  defaultParams: {},
105
- description: "智谱AI 最新旗舰模型,支持8小时长程Agent任务",
120
+ vision: true,
121
+ description: "OpenAI 最新旗舰(2026-09-29):Agentic Coding、计算机操作与文档密集型专业任务",
106
122
  },
107
123
  {
108
- id: "glm-5v-turbo",
109
- name: "GLM-5V-Turbo",
110
- endpointId: "bigmodelcn",
111
- modelName: "glm-5v-turbo",
124
+ id: "gpt-6-luna",
125
+ name: "GPT-6 Luna",
126
+ endpointId: "openai",
127
+ modelName: "gpt-6-luna",
112
128
  maxContextTokens: 250_000,
113
129
  defaultParams: {},
114
130
  vision: true,
115
- description: "智谱AI 多模态Coding基座(支持图片输入)",
131
+ description: "OpenAI GPT-6 系列快速低成本模型:适合高吞吐、低延迟场景",
116
132
  },
133
+ // ---------- 智谱 BigModel ----------
117
134
  {
118
- id: "glm-4.7-flash",
119
- name: "GLM-4.7-Flash",
135
+ id: "glm-5.3",
136
+ name: "GLM-5.3",
120
137
  endpointId: "bigmodelcn",
121
- modelName: "glm-4.7-flash",
138
+ modelName: "glm-5.3",
122
139
  maxContextTokens: 250_000,
123
- defaultParams: {},
124
- description: "智谱AI 免费轻量模型",
140
+ defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
141
+ description: "智谱最新旗舰:复杂软件工程与长程 Agent 任务(强制开启思考,1M 上下文)",
125
142
  },
126
143
  {
127
- id: "deepseek-v4-pro",
128
- name: "DeepSeek-V4-Pro",
129
- endpointId: "deepseek",
130
- modelName: "deepseek-v4-pro",
144
+ id: "glm-5.3-flash",
145
+ name: "GLM-5.3-Flash",
146
+ endpointId: "bigmodelcn",
147
+ modelName: "glm-5.3-flash",
131
148
  maxContextTokens: 250_000,
132
- defaultParams: { thinking: { type: "disabled" } },
133
- description: "DeepSeek 旗舰模型,Agentic Coding开源第一",
149
+ defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
150
+ vision: true,
151
+ description: "智谱原生多模态模型(图片/视频/文件):视觉 Coding,1M 上下文",
134
152
  },
135
153
  {
136
- id: "deepseek-v4-flash",
137
- name: "DeepSeek-V4-Flash",
138
- endpointId: "deepseek",
139
- modelName: "deepseek-v4-flash",
154
+ id: "glm-5.3-flashx",
155
+ name: "GLM-5.3-FlashX",
156
+ endpointId: "bigmodelcn",
157
+ modelName: "glm-5.3-flashx",
140
158
  maxContextTokens: 250_000,
141
- defaultParams: { thinking: { type: "enabled" } },
142
- description: "DeepSeek 经济高效模型",
159
+ defaultParams: { thinking: { type: "enabled" }, reasoning_effort: "max" },
160
+ vision: true,
161
+ description: "GLM-5.3-Flash 高速版(200 tokens/s),原生多模态",
143
162
  },
144
163
  {
145
- id: "deepseek-v4-flash-vision-exp",
146
- name: "DeepSeek-V4-Flash-Vision-Exp",
164
+ id: "glm-4.7-flash",
165
+ name: "GLM-4.7-Flash",
166
+ endpointId: "bigmodelcn",
167
+ modelName: "glm-4.7-flash",
168
+ // 该模型真实窗口仅 200K,阈值下调至 100K(约窗口的 50%),留足 response 空间
169
+ maxContextTokens: 100_000,
170
+ defaultParams: {},
171
+ description: "智谱免费文本模型:200K 上下文,适合轻量任务",
172
+ },
173
+ // ---------- DeepSeek ----------
174
+ {
175
+ id: "deepseek-flash",
176
+ name: "DeepSeek-V4.1-Flash",
147
177
  endpointId: "deepseek",
148
- modelName: "deepseek-v4-flash-vision-exp",
178
+ modelName: "deepseek-flash",
149
179
  maxContextTokens: 250_000,
150
180
  defaultParams: { thinking: { type: "enabled" } },
151
181
  vision: true,
152
- description: "DeepSeek 视觉实验模型(支持图片/文件输入)",
182
+ description: "DeepSeek 旗舰(2026-09-10):552B MoE 非对称架构,原生多模态,1M 上下文",
153
183
  },
154
184
  ],
155
185
  imageModels: [
@@ -176,19 +206,21 @@ export const DEFAULT_APP_CONFIG = {
176
206
  defaultSize: "1024x1024",
177
207
  },
178
208
  ],
179
- defaultModel: "deepseek-v4-flash",
209
+ defaultModel: "deepseek-flash",
180
210
  defaultImageModel: "cogview-4",
181
211
  defaultAgent: "default",
182
- defaultMigrationModel: "deepseek-v4-flash",
212
+ defaultMigrationModel: "deepseek-flash",
183
213
  // 工具输出上限(字符数),与 DEFAULT_MAX_TOOL_OUTPUT 保持一致
184
214
  maxToolOutput: 32_768,
185
215
  };
186
216
  // ---------------------------------------------------------------------------
187
- // 默认 MCP 配置(socket-pty 终端 MCP)
217
+ // 默认 MCP 配置(socket-pty 终端 MCP + chrome-devtools 浏览器调试 MCP)
188
218
  // ---------------------------------------------------------------------------
189
219
  /**
190
220
  * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
191
- * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
221
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用:
222
+ * - socket-pty 伪终端(pty)能力
223
+ * - chrome-devtools Chrome DevTools 浏览器调试/自动化能力(Google 官方 chrome-devtools-mcp)
192
224
  *
193
225
  * 若文件已存在则不覆盖(尊重用户已有配置)。
194
226
  */
@@ -200,6 +232,12 @@ export const DEFAULT_MCP_CONFIG = {
200
232
  args: ["-y", "@ai-zen/socket-pty", "mcp"],
201
233
  description: "可托管的伪终端(pty):spawn/read/wait/write/resize/status/kill",
202
234
  },
235
+ "chrome-devtools": {
236
+ type: "stdio",
237
+ command: "npx",
238
+ args: ["-y", "chrome-devtools-mcp@latest"],
239
+ description: "Chrome DevTools 浏览器调试与自动化(Google chrome-devtools-mcp):页面导航、性能追踪、网络与控制台检查、截图等",
240
+ },
203
241
  },
204
242
  };
205
243
  // ---------------------------------------------------------------------------
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 出厂 Skill 资产 —— 随 SDK 版本释放到 `<basePath>/skills/<id>/SKILL.md`。
3
+ *
4
+ * 为什么以 TS 字符串常量内联、而不是独立 .md 文件:
5
+ * SDK 用 `tsc` 直接编译,不拷贝非 TS 资源;内容内联后 `dist/` 天然携带,
6
+ * 无需额外打包步骤,也不依赖包内相对资源路径。
7
+ *
8
+ * 覆盖策略(与 `syncManagedModels` 同构):
9
+ * - 磁盘上的 SKILL.md frontmatter 里写了 `custom: "true"` → 视为用户自有,永不被覆盖;
10
+ * - 否则每次 `ConfigManager.bootstrap()` 同步为当前版本内容(模板属于产品行为,
11
+ * 改进了就要让老用户吃到)。
12
+ */
13
+ /** 交接 Skill 的 id(目录名 = frontmatter name) */
14
+ export declare const HANDOFF_SKILL_ID = "handoff";
15
+ /**
16
+ * 交接文档兜底模板。
17
+ *
18
+ * 仅在磁盘上读不到 `handoff` Skill 时使用(用户删除 / 目录不可读)。
19
+ * 它是**保险丝**,不是第二真相源:正常路径下模板一律来自 SKILL.md。
20
+ */
21
+ export declare const FALLBACK_HANDOFF_TEMPLATE: string;
22
+ /** 全部出厂 Skill(顺序无意义,按 id 去重) */
23
+ export declare const FACTORY_SKILLS: readonly {
24
+ id: string;
25
+ content: string;
26
+ }[];
27
+ /**
28
+ * 判断一份 SKILL.md 是否被用户声明为自有。
29
+ *
30
+ * 只检查 frontmatter 区块内的 `custom: true`(允许带引号),
31
+ * 正文里出现同样的字样不生效——避免误判导致模板停止更新。
32
+ */
33
+ export declare function isCustomSkill(content: string): boolean;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 出厂 Skill 资产 —— 随 SDK 版本释放到 `<basePath>/skills/<id>/SKILL.md`。
3
+ *
4
+ * 为什么以 TS 字符串常量内联、而不是独立 .md 文件:
5
+ * SDK 用 `tsc` 直接编译,不拷贝非 TS 资源;内容内联后 `dist/` 天然携带,
6
+ * 无需额外打包步骤,也不依赖包内相对资源路径。
7
+ *
8
+ * 覆盖策略(与 `syncManagedModels` 同构):
9
+ * - 磁盘上的 SKILL.md frontmatter 里写了 `custom: "true"` → 视为用户自有,永不被覆盖;
10
+ * - 否则每次 `ConfigManager.bootstrap()` 同步为当前版本内容(模板属于产品行为,
11
+ * 改进了就要让老用户吃到)。
12
+ */
13
+ /** 交接 Skill 的 id(目录名 = frontmatter name) */
14
+ export const HANDOFF_SKILL_ID = "handoff";
15
+ /**
16
+ * 交接文档兜底模板。
17
+ *
18
+ * 仅在磁盘上读不到 `handoff` Skill 时使用(用户删除 / 目录不可读)。
19
+ * 它是**保险丝**,不是第二真相源:正常路径下模板一律来自 SKILL.md。
20
+ */
21
+ export const FALLBACK_HANDOFF_TEMPLATE = [
22
+ "# 交接文档",
23
+ "",
24
+ "立即停工:不再实现任何功能、不继续未完成任务、不向用户提问。",
25
+ "唯一任务是:核实当前真实状态 → 把交接文档写到「本次交接参数」给出的路径。",
26
+ "",
27
+ "## 💬 对话断点",
28
+ "## 🎯 任务目标",
29
+ "## 📊 当前状态",
30
+ "## ✅ 已完成",
31
+ "## 📋 未完成",
32
+ "## 🧠 关键决策",
33
+ "## ⚠️ 约束与偏好",
34
+ "## 📁 文件索引",
35
+ "## 🔔 接手第一步",
36
+ "",
37
+ "无法核实的信息写「⚠️ 未核实」,禁止推测。",
38
+ ].join("\n");
39
+ /** 出厂 Skill:交接(handoff) */
40
+ const HANDOFF_SKILL = `---
41
+ name: handoff
42
+ description: 编写任务交接文档——对话停手离场时,把任务目标、当前真实状态、关键决策与下一步写成一份可被他人直接接手的文档。
43
+ metadata:
44
+ custom: "false"
45
+ ---
46
+
47
+ # 交接文档书写规范
48
+
49
+ ## 何时适用
50
+
51
+ 上下文即将超限触发任务迁移、用户明确要求「写交接文档」、或你被要求「交班」时。
52
+
53
+ ## 铁律(违反即视为失败)
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. **一次性写完**,不要反复重写;不要读大文件;不要把长代码贴进文档。
61
+
62
+ ## 落盘
63
+
64
+ 路径由「本次交接参数」给出,**原样使用**,不得改名、不得换目录、不得省略。
65
+ 用 write 工具写入后,用 read 回读一次,确认内容完整(无 read 能力时跳过此步)。
66
+
67
+ ## 文档结构(按序书写,标题保持原样)
68
+
69
+ ## 💬 对话断点
70
+ 用户最后要求了什么、你最后回复/交付了什么。各一段,不要复述整段对话。
71
+
72
+ ## 🎯 任务目标
73
+ 用户到底想要什么(意图与验收标准),而不是你执行过的步骤清单。
74
+
75
+ ## 📊 当前状态
76
+ 分支与 commit、未提交改动、测试是否通过、有无进程/端口在跑、依赖是否装好。
77
+
78
+ ## ✅ 已完成
79
+ 每项写:做了什么 + 产出路径(文件/目录) + 怎么验证它确实生效。
80
+
81
+ ## 📋 未完成
82
+ 按优先级排列;每项写「下一步最小动作」,具体到文件或可执行命令。
83
+
84
+ ## 🧠 关键决策
85
+ 为什么这样做(取舍理由);以及走过的死路——防止接手者重蹈覆辙。
86
+
87
+ ## ⚠️ 约束与偏好
88
+ 用户强调过的规矩、项目约定、不可改动的部分、必须遵守的风格。
89
+
90
+ ## 📁 文件索引
91
+ 路径 + 作用 + 本次是否改动(新增 / 修改 / 仅阅读)。
92
+
93
+ ## 🔔 接手第一步
94
+ 3 条以内可直接执行的命令或动作,让接手者立刻能开工。
95
+
96
+ ## 状态核实清单
97
+
98
+ - \`git status\` / \`git diff --stat\`:当前分支、是否有未提交改动
99
+ - 最近一次测试结果:跑过就写结果摘要;没跑就写「未运行」,**不要写「应该能过」**
100
+ - 交接涉及的关键文件:用 ls / read 确认确实存在于磁盘
101
+ - 正在运行的服务与端口:\`ps\` / \`lsof -i\` 或项目自带的启动脚本
102
+
103
+ > ⚠️ 若「本次交接参数」声明工具不可用:以上每一项都写「⚠️ 未核实」,**禁止推测**具体路径、进度与测试状态。
104
+
105
+ ## 写作要求
106
+
107
+ - 每句话都要让接手者能「立刻开始干活」;禁止「继续开发」「完善功能」「优化一下」这类空话。
108
+ - 已无法在当前上下文中确定的信息,写「需向用户确认 XXX」,不要编造。
109
+ - 全文控制在 300 行以内。
110
+ `;
111
+ /** 全部出厂 Skill(顺序无意义,按 id 去重) */
112
+ export const FACTORY_SKILLS = [
113
+ { id: HANDOFF_SKILL_ID, content: HANDOFF_SKILL },
114
+ ];
115
+ /**
116
+ * 判断一份 SKILL.md 是否被用户声明为自有。
117
+ *
118
+ * 只检查 frontmatter 区块内的 `custom: true`(允许带引号),
119
+ * 正文里出现同样的字样不生效——避免误判导致模板停止更新。
120
+ */
121
+ export function isCustomSkill(content) {
122
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
123
+ if (!match)
124
+ return false;
125
+ return /^\s*custom:\s*["']?true["']?\s*$/m.test(match[1]);
126
+ }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { SubAgentsScopePlugin } from "./scope/plugins/subagents/SubAgentsScopePl
20
20
  export { allInOne } from "./scope/plugins/allInOne.js";
21
21
  export type { AllInOneOptions } from "./scope/plugins/allInOne.js";
22
22
  export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
23
+ export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
23
24
  export { EntityRepository } from "./shared/EntityRepository.js";
24
25
  export { AgentRepository } from "./crud/AgentRepository.js";
25
26
  export { createModel } from "./runtime/createModel.js";
@@ -30,16 +31,17 @@ export { McpConnectionManager } from "./scope/plugins/mcp/McpConnectionManager.j
30
31
  export type { McpConnectOptions } from "./scope/plugins/mcp/McpConnectionManager.js";
31
32
  export { SdkCallbackTool } from "./scope/plugins/builtin/SdkCallbackTool.js";
32
33
  export type { SdkCallbackToolOptions } from "./scope/plugins/builtin/SdkCallbackTool.js";
33
- export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
34
- export type { MigrationContext, MigrationStrategy, TaskMigrationServiceOptions, } from "./runtime/TaskMigrationService.js";
34
+ export { HandoffService } from "./runtime/HandoffService.js";
35
+ export type { HandoffContext, HandoffMode, HandoffParams, HandoffServiceOptions, MigrateParams, MigrationStrategy, } from "./runtime/HandoffService.js";
36
+ export { HandoffGuardPlugin } from "./runtime/HandoffGuardPlugin.js";
35
37
  export { AutoMigratePlugin } from "./agent-plugins/AutoMigratePlugin.js";
36
- export type { AutoMigrateOptions } from "./agent-plugins/AutoMigratePlugin.js";
38
+ export type { AutoMigrateOptions, AutoMigrateTriggerInfo, AutoMigrateModeResolver, } from "./agent-plugins/AutoMigratePlugin.js";
37
39
  export { AutoRefreshToolsPlugin } from "./agent-plugins/AutoRefreshToolsPlugin.js";
38
40
  export { ContextGuardPlugin } from "./agent-plugins/ContextGuardPlugin.js";
39
41
  export type { ContextGuardOptions } from "./agent-plugins/ContextGuardPlugin.js";
40
42
  export { UnknownToolHintPlugin } from "./agent-plugins/UnknownToolHintPlugin.js";
41
43
  export { BUILTIN_TOOL_CLASSES } from "./scope/plugins/builtin/index.js";
42
- export { CwdTool, ReadFileTool, InspectFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./scope/plugins/builtin/index.js";
44
+ export { CwdTool, ReadTool, InspectFileTool, WriteTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, SpawnTool, SleepTool, } from "./scope/plugins/builtin/index.js";
43
45
  export { GenerateImageTool } from "./scope/plugins/builtin/GenerateImageTool.js";
44
46
  export { ViewImageTool } from "./scope/plugins/builtin/ViewImageTool.js";
45
47
  export { createLoadSkillTool, createCallSkillSubAgentTool } from "./scope/plugins/skills/skillTools.js";
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ export { SubAgentsScopePlugin } from "./scope/plugins/subagents/SubAgentsScopePl
17
17
  export { allInOne } from "./scope/plugins/allInOne.js";
18
18
  // 配置 — ConfigManager
19
19
  export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
20
+ // 出厂资产 — 出厂 Skill(随版本释放到 <basePath>/skills/)
21
+ export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
20
22
  // CRUD — 实体仓储(仅 Agent 定义等能力实体)
21
23
  export { EntityRepository } from "./shared/EntityRepository.js";
22
24
  export { AgentRepository } from "./crud/AgentRepository.js";
@@ -26,7 +28,8 @@ export { SdkAgent } from "./runtime/SdkAgent.js";
26
28
  export { createAgent } from "./runtime/createAgent.js";
27
29
  export { McpConnectionManager } from "./scope/plugins/mcp/McpConnectionManager.js";
28
30
  export { SdkCallbackTool } from "./scope/plugins/builtin/SdkCallbackTool.js";
29
- export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
31
+ export { HandoffService } from "./runtime/HandoffService.js";
32
+ export { HandoffGuardPlugin } from "./runtime/HandoffGuardPlugin.js";
30
33
  // 插件
31
34
  export { AutoMigratePlugin } from "./agent-plugins/AutoMigratePlugin.js";
32
35
  export { AutoRefreshToolsPlugin } from "./agent-plugins/AutoRefreshToolsPlugin.js";
@@ -34,7 +37,7 @@ export { ContextGuardPlugin } from "./agent-plugins/ContextGuardPlugin.js";
34
37
  export { UnknownToolHintPlugin } from "./agent-plugins/UnknownToolHintPlugin.js";
35
38
  // 工具 — 内置工具类 + 动态工具工厂
36
39
  export { BUILTIN_TOOL_CLASSES } from "./scope/plugins/builtin/index.js";
37
- export { CwdTool, ReadFileTool, InspectFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./scope/plugins/builtin/index.js";
40
+ export { CwdTool, ReadTool, InspectFileTool, WriteTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, SpawnTool, SleepTool, } from "./scope/plugins/builtin/index.js";
38
41
  export { GenerateImageTool } from "./scope/plugins/builtin/GenerateImageTool.js";
39
42
  export { ViewImageTool } from "./scope/plugins/builtin/ViewImageTool.js";
40
43
  export { createLoadSkillTool, createCallSkillSubAgentTool } from "./scope/plugins/skills/skillTools.js";
@@ -0,0 +1,35 @@
1
+ import type { AgentPlugin, ToolCallContext } from "@ai-zen/agents-core";
2
+ /**
3
+ * 交接回合的工具守卫 —— 保证「原 agent 停下手头一切工作」不只是提示词约定。
4
+ *
5
+ * 交接由**同一个 agent** 在自己的会话里完成,它天然有继续干活的动力。
6
+ * 本插件在交接回合期间拦截写入类工具,只放行对交接文档那一份文件的写入:
7
+ * - `write`:仅当目标路径(按交接时给定的 cwd 归一后)等于交接文档路径时放行;
8
+ * - `edit` / `batch_edit` / `rm` / `rename` / `mkdir` / `copy`:一律拒绝;
9
+ * - 读取类工具与其他工具:放行。
10
+ *
11
+ * 拒绝方式是**软拒绝**(返回字符串):core 的 `onToolCall` 语义会把该字符串作为
12
+ * 工具结果回给模型并继续下一轮,因此模型能读到引导(「只允许写交接文档」)并转回正题。
13
+ * 反例:抛错会让该工具结果标记为 Error 且 `isPreventDefault`,整轮交接直接中断。
14
+ *
15
+ * 边界(必须如实记录,不要误以为是安全边界):
16
+ * - **挡不住 `exec`**:`echo > file`、`cat > file <<EOF` 永远能绕过。本插件降低的是
17
+ * 「顺手改代码」的误操作概率,不是沙箱。
18
+ * - 一次性:`arm()` 后生效,`disarm()` 后完全让行。`Agent.use()` 没有移除 API,
19
+ * 故用「同一实例 + 开关」而非反复注册,避免插件数组无限增长。
20
+ */
21
+ export declare class HandoffGuardPlugin implements AgentPlugin {
22
+ /** 写入类工具(除白名单的 write 外一律拒绝) */
23
+ private static readonly WRITE_TOOLS;
24
+ /** 当前交接文档绝对路径;undefined 表示未处于交接回合(完全让行) */
25
+ private handoffPath?;
26
+ /** 相对路径解析基准(交接时 agent 的 cwd) */
27
+ private cwd;
28
+ /** 进入交接回合:仅允许写入 handoffPath(相对路径按 cwd 归一) */
29
+ arm(handoffPath: string, cwd?: string): void;
30
+ /** 离开交接回合:恢复为完全让行 */
31
+ disarm(): void;
32
+ /** 是否处于交接回合(供上层/测试观察) */
33
+ get armed(): boolean;
34
+ onToolCall(ctx: ToolCallContext): string | undefined;
35
+ }
@@ -0,0 +1,65 @@
1
+ import { resolve as resolvePath } from "node:path";
2
+ /**
3
+ * 交接回合的工具守卫 —— 保证「原 agent 停下手头一切工作」不只是提示词约定。
4
+ *
5
+ * 交接由**同一个 agent** 在自己的会话里完成,它天然有继续干活的动力。
6
+ * 本插件在交接回合期间拦截写入类工具,只放行对交接文档那一份文件的写入:
7
+ * - `write`:仅当目标路径(按交接时给定的 cwd 归一后)等于交接文档路径时放行;
8
+ * - `edit` / `batch_edit` / `rm` / `rename` / `mkdir` / `copy`:一律拒绝;
9
+ * - 读取类工具与其他工具:放行。
10
+ *
11
+ * 拒绝方式是**软拒绝**(返回字符串):core 的 `onToolCall` 语义会把该字符串作为
12
+ * 工具结果回给模型并继续下一轮,因此模型能读到引导(「只允许写交接文档」)并转回正题。
13
+ * 反例:抛错会让该工具结果标记为 Error 且 `isPreventDefault`,整轮交接直接中断。
14
+ *
15
+ * 边界(必须如实记录,不要误以为是安全边界):
16
+ * - **挡不住 `exec`**:`echo > file`、`cat > file <<EOF` 永远能绕过。本插件降低的是
17
+ * 「顺手改代码」的误操作概率,不是沙箱。
18
+ * - 一次性:`arm()` 后生效,`disarm()` 后完全让行。`Agent.use()` 没有移除 API,
19
+ * 故用「同一实例 + 开关」而非反复注册,避免插件数组无限增长。
20
+ */
21
+ export class HandoffGuardPlugin {
22
+ /** 写入类工具(除白名单的 write 外一律拒绝) */
23
+ static WRITE_TOOLS = new Set([
24
+ "edit",
25
+ "batch_edit",
26
+ "rm",
27
+ "rename",
28
+ "mkdir",
29
+ "copy",
30
+ ]);
31
+ /** 当前交接文档绝对路径;undefined 表示未处于交接回合(完全让行) */
32
+ handoffPath;
33
+ /** 相对路径解析基准(交接时 agent 的 cwd) */
34
+ cwd = process.cwd();
35
+ /** 进入交接回合:仅允许写入 handoffPath(相对路径按 cwd 归一) */
36
+ arm(handoffPath, cwd) {
37
+ this.cwd = cwd ? resolvePath(cwd) : process.cwd();
38
+ this.handoffPath = resolvePath(this.cwd, handoffPath);
39
+ }
40
+ /** 离开交接回合:恢复为完全让行 */
41
+ disarm() {
42
+ this.handoffPath = undefined;
43
+ }
44
+ /** 是否处于交接回合(供上层/测试观察) */
45
+ get armed() {
46
+ return this.handoffPath !== undefined;
47
+ }
48
+ onToolCall(ctx) {
49
+ const handoffPath = this.handoffPath;
50
+ if (handoffPath == null)
51
+ return;
52
+ const name = ctx.tool_call?.function?.name ?? "";
53
+ if (name === "write") {
54
+ const raw = ctx.parsedArgs?.path;
55
+ if (typeof raw === "string" && resolvePath(this.cwd, raw) === handoffPath) {
56
+ return;
57
+ }
58
+ return `交接回合只允许写交接文档:${handoffPath}(write 的 path 请原样使用该路径),请立即开始撰写。`;
59
+ }
60
+ if (HandoffGuardPlugin.WRITE_TOOLS.has(name)) {
61
+ return `交接回合禁止修改工作区(${name}):你的唯一任务是把交接文档写入 ${handoffPath}。`;
62
+ }
63
+ return;
64
+ }
65
+ }