@ai-zen/agents-sdk 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (48) hide show
  1. package/CHANGELOG.md +154 -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 +12 -1
  9. package/dist/config/ConfigManager.js +40 -1
  10. package/dist/config/constants.d.ts +3 -1
  11. package/dist/config/constants.js +10 -2
  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/scope/plugins/mcp/McpScopePlugin.d.ts +1 -1
  42. package/dist/scope/plugins/mcp/McpScopePlugin.js +3 -2
  43. package/dist/scope/plugins/mcp/mcpTools.d.ts +8 -2
  44. package/dist/scope/plugins/mcp/mcpTools.js +129 -9
  45. package/dist/types/index.d.ts +3 -3
  46. package/package.json +3 -3
  47. package/dist/runtime/TaskMigrationService.d.ts +0 -128
  48. package/dist/runtime/TaskMigrationService.js +0 -211
@@ -0,0 +1,372 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { dirname, join, resolve as resolvePath } from "node:path";
3
+ import { Message } from "@ai-zen/agents-core";
4
+ import { getLogger } from "../shared/logger.js";
5
+ import { readSkill } from "../scope/plugins/skills/discover.js";
6
+ import { FALLBACK_HANDOFF_TEMPLATE, HANDOFF_SKILL_ID } from "../config/factory-skills.js";
7
+ import { HandoffGuardPlugin } from "./HandoffGuardPlugin.js";
8
+ /**
9
+ * 任务交接服务。
10
+ *
11
+ * 职责分层(这是本模块唯一的设计主线):
12
+ * - `handoff()`:**只产出交接文档**(停工 → 写文档 → 校验 → 失败降级),不动会话;
13
+ * - `migrate()`:`handoff()` 之后清历史并开新会话(原「任务迁移」语义)。
14
+ *
15
+ * 模板的唯一真相源是 `handoff` Skill(`SKILL.md`);代码只负责
16
+ * 「读正文 → 注入 → 校验文件 → 读不到时用兜底模板」,不持有第二份章节清单。
17
+ *
18
+ * 重入:`handoff()` 与 `migrate()` 共用 `busy` 标志。交接回合本身也是一次 `send()`,
19
+ * 若不在 busy 期间让 `AutoMigratePlugin` / `ContextGuardPlugin` 让行,就会「交接触发迁移、
20
+ * 迁移再触发交接」无限递归,或在最满的时刻被护栏直接击杀。
21
+ */
22
+ export class HandoffService {
23
+ /** 交接文档兜底模板(仅在读不到 Skill 时使用) */
24
+ static FALLBACK_TEMPLATE = FALLBACK_HANDOFF_TEMPLATE;
25
+ handoffDirPath;
26
+ defaultMode;
27
+ defaultStrategy;
28
+ skillDirs;
29
+ client;
30
+ model;
31
+ modelConfig;
32
+ minHandoffBytes;
33
+ onBeforeHandoff;
34
+ onHandoff;
35
+ onMigrated;
36
+ logger;
37
+ /** 交接/迁移进行中标志(插件据此让行) */
38
+ _busy = false;
39
+ /** 交接回合的工具守卫(同一实例复用 + arm/disarm,避免插件数组无限增长) */
40
+ guard;
41
+ /** 已注册过守卫的 agent(每个 agent 只 use 一次) */
42
+ guardedAgents = new WeakSet();
43
+ constructor(options = {}) {
44
+ const { handoffDir, mode, strategy, skillDirs, client, model, modelConfig, minHandoffBytes, onBeforeHandoff, onHandoff, onMigrated, logger, } = options;
45
+ this.handoffDirPath = resolvePath(handoffDir ?? join(process.cwd(), ".ai-zen", "handoffs"));
46
+ this.defaultMode = mode ?? "self";
47
+ this.defaultStrategy = strategy ?? "omit";
48
+ this.skillDirs = skillDirs ?? [];
49
+ this.client = client;
50
+ this.model = model;
51
+ this.modelConfig = modelConfig;
52
+ this.minHandoffBytes = minHandoffBytes ?? 400;
53
+ this.onBeforeHandoff = onBeforeHandoff;
54
+ this.onHandoff = onHandoff;
55
+ this.onMigrated = onMigrated;
56
+ this.logger = logger ?? getLogger();
57
+ }
58
+ /** 是否正在交接/迁移(`AutoMigratePlugin` / `ContextGuardPlugin` 据此让行) */
59
+ get busy() {
60
+ return this._busy;
61
+ }
62
+ /** 交接文档目录 */
63
+ get handoffDir() {
64
+ return this.handoffDirPath;
65
+ }
66
+ // ==================================================================
67
+ // 静态纯工具(供上层复用 / 单测)
68
+ // ==================================================================
69
+ /**
70
+ * 交接文件名:`<YYYYMMDD-HHmmss>-<会话id前8位>.md`(本地时间,便于人眼归档)。
71
+ */
72
+ static createHandoffFileName(date = new Date(), conversationId) {
73
+ const p = (n) => String(n).padStart(2, "0");
74
+ const stamp = `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}` +
75
+ `-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`;
76
+ const suffix = conversationId ? `-${conversationId.slice(0, 8)}` : "";
77
+ return `${stamp}${suffix}.md`;
78
+ }
79
+ /**
80
+ * 拼装「本轮交接」指令:`[Skill 正文] + [本次交接参数]`。
81
+ *
82
+ * 不在 Skill 正文里发明占位符(本体系没有模板变量引擎),参数一律由代码拼在后面,
83
+ * 这样 Skill 只讲「怎么写」、代码只讲「写到哪、有什么能力」。
84
+ */
85
+ static createHandoffInstruction(params) {
86
+ const { skillContent, handoffPath, toolsAvailable } = params;
87
+ const tail = toolsAvailable
88
+ ? [
89
+ "请立即开始撰写,并用 write 工具把完整文档写入上面给出的路径(路径原样使用,不要改名、不要换目录)。",
90
+ "写完后用 read 回读一次确认完整,然后只回一句不超过 40 字的确认,**不要复述文档内容**。",
91
+ ]
92
+ : [
93
+ "请直接输出完整文档正文:不要任何解释、不要用代码块包裹,调用方会自行落盘。",
94
+ ];
95
+ return [
96
+ "你现在要为本轮对话编写一份任务交接文档。以下是必须遵守的交接规范。",
97
+ "",
98
+ "---",
99
+ "",
100
+ skillContent.trim(),
101
+ "",
102
+ "---",
103
+ "",
104
+ "## 本次交接参数",
105
+ `- 交接文档绝对路径:${handoffPath}`,
106
+ `- 工具能力:${toolsAvailable
107
+ ? "可用(read / exec 等,请据此核实真实状态)"
108
+ : "不可用(你无法核实磁盘,状态类信息一律写「⚠️ 未核实」,禁止推测)"}`,
109
+ "",
110
+ ...tail,
111
+ ].join("\n");
112
+ }
113
+ /**
114
+ * 生成迁移后追加的「交接断点」消息(role=user):只给路径,让接手方自己 read。
115
+ *
116
+ * 为什么不内联全文:接手方第一次真正需要的是「去哪里取交接」,读一次文件的输入成本
117
+ * 与内联全文相当,但内联会让每条请求都携带全文;路径方案把成本推迟到真正需要时。
118
+ */
119
+ static createPostMessages(handoffPath) {
120
+ const content = [
121
+ "上一轮对话已交接,交接文档保存在:",
122
+ handoffPath,
123
+ "",
124
+ "请先用 read 工具读取该文件,理解上下文与未完成事项后,再继续协助用户完成任务。",
125
+ ].join("\n");
126
+ return [Message.User(content)];
127
+ }
128
+ /** 序列化消息为 `[role]: content` 文本(仅 `summary` 模式使用) */
129
+ static serializeMessages(messages) {
130
+ return messages
131
+ .map((m) => {
132
+ const role = m.role ?? "unknown";
133
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
134
+ return `[${role}]: ${content}`;
135
+ })
136
+ .join("\n\n");
137
+ }
138
+ // ==================================================================
139
+ // 公开流程
140
+ // ==================================================================
141
+ /**
142
+ * 只产出交接文档,**不改动会话**(`/handoff`)。
143
+ *
144
+ * 不清理历史:交接回合(指令 + 工具调用 + 回执)会留在 `agent.messages` 里,
145
+ * 用户可继续让 agent 打磨这份文档(重写同一路径即可)。
146
+ */
147
+ async handoff(params) {
148
+ return this.withBusy(() => this.runHandoff(params, false));
149
+ }
150
+ /**
151
+ * 交接 + 开新会话(`/migrate`、自动迁移)。
152
+ *
153
+ * 策略(`strategy`):
154
+ * - `omit`(默认):历史标记 `omit: true`(保留可审计),末尾追加交接断点;
155
+ * - `prune`:物理剔除历史,只留 `definition.messages` + 交接断点。
156
+ */
157
+ async migrate(params) {
158
+ return this.withBusy(async () => {
159
+ const ctx = await this.runHandoff(params, true);
160
+ const { agent } = ctx;
161
+ const strategy = params.strategy ?? this.defaultStrategy;
162
+ const breakpointMessages = HandoffService.createPostMessages(ctx.handoffPath);
163
+ if (strategy === "prune") {
164
+ // 浅拷贝模板消息,避免污染 definition.messages 引用的对象
165
+ agent.messages = [...agent.definition.messages, ...breakpointMessages];
166
+ }
167
+ else {
168
+ const preserveCount = Math.min(agent.definition.messages.length, agent.messages.length);
169
+ agent.messages = [
170
+ ...agent.messages.map((m, i) => ({
171
+ ...m,
172
+ omit: i >= preserveCount ? true : m.omit,
173
+ })),
174
+ ...breakpointMessages,
175
+ ];
176
+ }
177
+ await this.safeHook(this.onMigrated, ctx);
178
+ return ctx;
179
+ });
180
+ }
181
+ // ==================================================================
182
+ // 内部
183
+ // ==================================================================
184
+ /** 串行化 + 防重入:交接与迁移共用同一把锁 */
185
+ async withBusy(fn) {
186
+ if (this._busy)
187
+ throw new Error("交接流程已在进行中,请等待当前交接完成");
188
+ this._busy = true;
189
+ try {
190
+ return await fn();
191
+ }
192
+ finally {
193
+ this._busy = false;
194
+ }
195
+ }
196
+ async runHandoff(params, migrating) {
197
+ const { agent, promptTokens, maxTokens, conversationId } = params;
198
+ const requestedMode = params.mode ?? this.defaultMode;
199
+ const handoffPath = join(this.handoffDirPath, HandoffService.createHandoffFileName(new Date(), conversationId));
200
+ const model = this.model ?? agent.model;
201
+ const messageCountBefore = agent.messages.length;
202
+ // 迁移前上下文(不含文档,语义固定)
203
+ const beforeCtx = {
204
+ agent,
205
+ mode: requestedMode,
206
+ requestedMode,
207
+ handoffPath,
208
+ model,
209
+ promptTokens,
210
+ maxTokens,
211
+ migrating,
212
+ messageCountBefore,
213
+ historyText: "",
214
+ };
215
+ await this.safeHook(this.onBeforeHandoff, beforeCtx);
216
+ // ---- 执行者:self 优先,失败/不可用则单向降级 summary ----
217
+ let mode = requestedMode;
218
+ let handoffDoc;
219
+ let historyText = "";
220
+ if (mode === "self" && !this.supportsSelfHandoff(agent)) {
221
+ this.logger.warn("[handoff] agent 不具备 write 工具或 send 能力,降级为独立摘要模式");
222
+ mode = "summary";
223
+ }
224
+ if (mode === "self") {
225
+ const ok = await this.runSelfHandoff(agent, handoffPath);
226
+ if (!ok) {
227
+ this.logger.warn("[handoff] 自交接失败,降级为独立摘要模式");
228
+ mode = "summary";
229
+ }
230
+ }
231
+ if (mode === "summary") {
232
+ historyText = HandoffService.serializeMessages(agent.messages.filter((m) => !m.omit));
233
+ handoffDoc = await this.generateSummary(agent, historyText, handoffPath);
234
+ await this.writeHandoffFile(handoffPath, handoffDoc);
235
+ }
236
+ const ctx = {
237
+ agent,
238
+ mode,
239
+ requestedMode,
240
+ handoffPath,
241
+ handoffDoc,
242
+ model,
243
+ promptTokens,
244
+ maxTokens,
245
+ migrating,
246
+ messageCountBefore,
247
+ historyText,
248
+ };
249
+ await this.safeHook(this.onHandoff, ctx);
250
+ return ctx;
251
+ }
252
+ /** 自交接是否可用:需要 `write` 工具 + `send` 能力 */
253
+ supportsSelfHandoff(agent) {
254
+ if (typeof agent.send !== "function")
255
+ return false;
256
+ return Boolean(agent.tools?.some((tool) => tool.function?.name === "write"));
257
+ }
258
+ /**
259
+ * 自交接回合:让原 agent 停工、核实、写文档,并校验文件确实落盘。
260
+ *
261
+ * 失败判定(任一成立即视为失败,交由上层单向降级 summary):
262
+ * - 文件不存在 / 小于阈值;
263
+ * - 模型/工具链抛错(例如没有权限、被护栏中断)。
264
+ */
265
+ async runSelfHandoff(agent, handoffPath) {
266
+ const guard = this.ensureGuard(agent);
267
+ const skillContent = await this.readSkillContent();
268
+ const cwd = agent.scope?.cwd ?? process.cwd();
269
+ /*
270
+ * 清空 lastUsage 再开跑:
271
+ * - 护栏(ContextGuardPlugin)在每轮请求前用 lastUsage 判定严重超限,
272
+ * 而交接恰好发生在上下文最满的时刻;
273
+ * - AutoMigratePlugin 也会读它。busy 已能让两者让行,这里再清一次是纵深防御。
274
+ * 交接回合结束后 lastUsage 由本轮真实响应用量回填,无需恢复旧值。
275
+ */
276
+ agent.lastUsage = undefined;
277
+ guard.arm(handoffPath, cwd);
278
+ try {
279
+ await agent.send(HandoffService.createHandoffInstruction({
280
+ skillContent,
281
+ handoffPath,
282
+ toolsAvailable: true,
283
+ }));
284
+ if (await this.isHandoffFileReady(handoffPath))
285
+ return true;
286
+ this.logger.warn(`[handoff] 交接文档缺失或过小,重试一次:${handoffPath}`);
287
+ await agent.send([
288
+ `交接文档不存在或内容过少,无法交付:${handoffPath}`,
289
+ "请立即用 write 工具把**完整**交接文档写入该路径(路径原样使用),不要做其他任何事。",
290
+ ].join("\n"));
291
+ return await this.isHandoffFileReady(handoffPath);
292
+ }
293
+ catch (err) {
294
+ this.logger.error(`[handoff] 自交接回合失败:${err?.message ?? err}`);
295
+ return false;
296
+ }
297
+ finally {
298
+ guard.disarm();
299
+ }
300
+ }
301
+ /** 独立摘要:一次模型调用,用同一份 Skill 正文作为规范 */
302
+ async generateSummary(agent, historyText, handoffPath) {
303
+ const client = this.client ?? agent.client;
304
+ const model = this.model ?? agent.model;
305
+ const modelConfig = this.modelConfig ?? agent.modelConfig;
306
+ const skillContent = await this.readSkillContent();
307
+ const response = await client.chat.completions.create({
308
+ model,
309
+ ...(modelConfig ?? {}),
310
+ messages: [
311
+ {
312
+ role: "system",
313
+ content: HandoffService.createHandoffInstruction({
314
+ skillContent,
315
+ handoffPath,
316
+ toolsAvailable: false,
317
+ }),
318
+ },
319
+ { role: "user", content: historyText },
320
+ ],
321
+ });
322
+ const handoffDoc = response.choices[0]?.message?.content;
323
+ if (!handoffDoc || !handoffDoc.trim()) {
324
+ throw new Error("未能生成任务交接文档");
325
+ }
326
+ return handoffDoc;
327
+ }
328
+ /** 落盘(仅 `summary` 模式需要;`self` 模式由 agent 的 write 工具完成) */
329
+ async writeHandoffFile(handoffPath, content) {
330
+ await fs.mkdir(dirname(handoffPath), { recursive: true });
331
+ await fs.writeFile(handoffPath, content, "utf-8");
332
+ }
333
+ /** 交接文档是否可用:文件存在且不小于阈值(不读内容,避免二次读取) */ async isHandoffFileReady(handoffPath) {
334
+ try {
335
+ const stat = await fs.stat(handoffPath);
336
+ return stat.isFile() && stat.size >= this.minHandoffBytes;
337
+ }
338
+ catch {
339
+ return false;
340
+ }
341
+ }
342
+ /** 读取 `handoff` Skill 正文;读不到时回落到兜底模板 */
343
+ async readSkillContent() {
344
+ if (this.skillDirs.length > 0) {
345
+ const skill = await readSkill(this.skillDirs, HANDOFF_SKILL_ID, { silent: true });
346
+ if (skill?.content?.trim())
347
+ return skill.content;
348
+ }
349
+ this.logger.warn(`[handoff] 未找到 ${HANDOFF_SKILL_ID} Skill,使用内置兜底模板(路径:${this.skillDirs.join(", ") || "未配置"})`);
350
+ return FALLBACK_HANDOFF_TEMPLATE;
351
+ }
352
+ /** 守卫按 agent 注册一次(`Agent.use()` 无移除 API,故复用实例 + 开关) */
353
+ ensureGuard(agent) {
354
+ const guard = (this.guard ??= new HandoffGuardPlugin());
355
+ if (!this.guardedAgents.has(agent)) {
356
+ agent.use(guard);
357
+ this.guardedAgents.add(agent);
358
+ }
359
+ return guard;
360
+ }
361
+ /** 钩子容错:抛错只记录,不中断交接(与既有迁移钩子语义一致) */
362
+ async safeHook(hook, ctx) {
363
+ if (!hook)
364
+ return;
365
+ try {
366
+ await hook(ctx);
367
+ }
368
+ catch (err) {
369
+ this.logger.error(`[handoff] 钩子执行失败: ${err?.message ?? err}`);
370
+ }
371
+ }
372
+ }
@@ -2,7 +2,7 @@ import * as fsp from "fs/promises";
2
2
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  export class BatchEditTool extends SdkCallbackTool {
4
4
  function = {
5
- name: "batchEdit",
5
+ name: "batch_edit",
6
6
  description: "批量编辑文件文本,可以优先使用这个工具对文件进行编辑",
7
7
  parameters: {
8
8
  type: "object",
@@ -3,7 +3,7 @@ import * as path from "path";
3
3
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
4
4
  export class DownloadFileTool extends SdkCallbackTool {
5
5
  function = {
6
- name: "downloadFile",
6
+ name: "download_file",
7
7
  description: "从 URL 下载文件(图片、PDF 等二进制文件)并保存到本地磁盘。自动从 URL 推断文件名,也可指定保存路径。",
8
8
  parameters: {
9
9
  type: "object",
@@ -14,7 +14,7 @@ export class ExecTool extends SdkCallbackTool {
14
14
  },
15
15
  timeout: {
16
16
  type: "number",
17
- description: "超时时间(毫秒),必填。超时后会终止进程;超过该时长仍未完成即视为超时并终止。建议长时间运行的命令使用 exec_async 异步执行。",
17
+ description: "超时时间(毫秒),必填。超时后会终止进程;超过该时长仍未完成即视为超时并终止。建议长时间运行的命令使用 spawn 异步执行。",
18
18
  },
19
19
  },
20
20
  required: ["command", "timeout"],
@@ -98,7 +98,7 @@ export class ExecTool extends SdkCallbackTool {
98
98
  return JSON.stringify({
99
99
  outputTooLarge: true,
100
100
  warning: `输出过大:stdout + stderr 合计 ${result.stdout.length + result.stderr.length} 字符,` +
101
- `超过上限 ${limit} 字符。完整内容已分文件落盘(stdout.log / stderr.log),可用 readFile 分批读取。`,
101
+ `超过上限 ${limit} 字符。完整内容已分文件落盘(stdout.log / stderr.log),可用 read 分批读取。`,
102
102
  files: ctx.files,
103
103
  stdout: { chars: result.stdout.length, lines: countLines(result.stdout), ...stdout },
104
104
  stderr: { chars: result.stderr.length, lines: countLines(result.stderr), ...stderr },
@@ -4,13 +4,13 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
4
4
  import { DEFAULT_MAX_TOOL_OUTPUT, guardOutput, headPreview } from "./outputGuard.js";
5
5
  /** 默认总匹配数上限 */
6
6
  const DEFAULT_MAX_MATCHES = 200;
7
- /** 默认文件大小上限(字节,与 ReadFileTool 的 300KB 口径一致) */
7
+ /** 默认文件大小上限(字节,与 ReadTool 的 300KB 口径一致) */
8
8
  const DEFAULT_MAX_FILE_SIZE = 300 * 1024;
9
9
  /** 默认行内容/匹配子串截断长度 */
10
10
  const DEFAULT_MAX_LINE_LENGTH = 200;
11
11
  export class FindTextTool extends SdkCallbackTool {
12
12
  function = {
13
- name: "findText",
13
+ name: "find_text",
14
14
  description: "查找文本出现的位置,支持普通文本或正则匹配,返回文件名及具体行号、行内容。为避免结果撑爆上下文,默认限制总匹配数、单文件大小与行内容长度,超限会截断并标记 truncated。",
15
15
  parameters: {
16
16
  type: "object",
@@ -89,12 +89,12 @@ export class FindTextTool extends SdkCallbackTool {
89
89
  break; // 中断:stat 后再次检查
90
90
  if (!stats.isFile())
91
91
  continue;
92
- // 文件过大则跳过(避免撑爆上下文;与 ReadFileTool 300KB 口径一致,但此处为跳过而非报错)
92
+ // 文件过大则跳过(避免撑爆上下文;与 ReadTool 300KB 口径一致,但此处为跳过而非报错)
93
93
  if (stats.size > maxFileSize)
94
94
  continue;
95
95
  const content = await fsp.readFile(fullPath, "utf-8");
96
96
  if (signal?.aborted)
97
- break; // 中断:readFile 后再次检查
97
+ break; // 中断:read 后再次检查
98
98
  const lines = content.split("\n");
99
99
  const matches = [];
100
100
  for (let index = 0; index < lines.length; index++) {
@@ -145,7 +145,7 @@ export class FindTextTool extends SdkCallbackTool {
145
145
  async protect(json) {
146
146
  const limit = this.env.config.maxToolOutput ?? DEFAULT_MAX_TOOL_OUTPUT;
147
147
  return await guardOutput({
148
- tool: "findText",
148
+ tool: "find_text",
149
149
  content: json,
150
150
  isOverLimit: (text) => text.length > limit,
151
151
  dump: (_dir, text) => [{ name: "result.json", content: text }],
@@ -154,7 +154,7 @@ export class FindTextTool extends SdkCallbackTool {
154
154
  return JSON.stringify({
155
155
  outputTooLarge: true,
156
156
  warning: `输出过大:结果共 ${json.length} 字符,超过上限 ${limit} 字符。` +
157
- `完整结果已落盘(result.json),可用 readFile 分批读取。`,
157
+ `完整结果已落盘(result.json),可用 read 分批读取。`,
158
158
  files: ctx.files,
159
159
  chars: json.length,
160
160
  ...preview,
@@ -4,7 +4,7 @@ import type { AgentDefinition, AppConfig, ToolEnv } from "../../../types/index.j
4
4
  /**
5
5
  * 根据文字描述生成图片。**统一返回字符串**(JSON,含图片 URL 列表):
6
6
  * 生成结果如何处理(查看/下载/转发)由模型与上层自由决定——
7
- * 需要让模型看图时,模型可主动调用 viewImage(URL);需要保存时用 downloadFile。
7
+ * 需要让模型看图时,模型可主动调用 view_image(URL);需要保存时用 download_file。
8
8
  *
9
9
  * 依赖 AppConfig 中的 imageModels 和 defaultImageModel 配置(isAvailable 声明),
10
10
  * 未配置时由 buildTools 过滤,不暴露给 Agent。
@@ -3,7 +3,7 @@ import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  /**
4
4
  * 根据文字描述生成图片。**统一返回字符串**(JSON,含图片 URL 列表):
5
5
  * 生成结果如何处理(查看/下载/转发)由模型与上层自由决定——
6
- * 需要让模型看图时,模型可主动调用 viewImage(URL);需要保存时用 downloadFile。
6
+ * 需要让模型看图时,模型可主动调用 view_image(URL);需要保存时用 download_file。
7
7
  *
8
8
  * 依赖 AppConfig 中的 imageModels 和 defaultImageModel 配置(isAvailable 声明),
9
9
  * 未配置时由 buildTools 过滤,不暴露给 Agent。
@@ -14,8 +14,8 @@ export class GenerateImageTool extends SdkCallbackTool {
14
14
  return !!config.defaultImageModel;
15
15
  }
16
16
  function = {
17
- name: "generateImage",
18
- description: "根据文字描述生成图片,返回图片 URL 列表(JSON)。如需查看图片内容,请用 viewImage 工具(传入图片 URL);如需保存到本地,用 downloadFile 下载。",
17
+ name: "generate_image",
18
+ description: "根据文字描述生成图片,返回图片 URL 列表(JSON)。如需查看图片内容,请用 view_image 工具(传入图片 URL);如需保存到本地,用 download_file 下载。",
19
19
  parameters: {
20
20
  type: "object",
21
21
  properties: {
@@ -25,7 +25,7 @@ export class GenerateImageTool extends SdkCallbackTool {
25
25
  },
26
26
  model: {
27
27
  type: "string",
28
- description: "图片模型 ID,不指定则使用配置中的默认图片模型。配置中的图片模型可通过 'aiz config show' 查看。",
28
+ description: "图片模型 ID,不指定则使用配置中的默认图片模型;若指定的模型不存在,本工具会返回当前可用的模型列表。",
29
29
  },
30
30
  size: {
31
31
  type: "string",
@@ -99,15 +99,15 @@ export class GenerateImageTool extends SdkCallbackTool {
99
99
  .map((img) => img.url)
100
100
  .filter((url) => !!url);
101
101
  // 统一返回字符串(JSON):生成结果如何处理(查看/下载/转发)由模型与上层自由决定,
102
- // 不替模型做「看或不看」的假设。需要让模型看图 → 模型可主动调用 viewImage(URL);
103
- // 需要保存 → downloadFile 下载。
102
+ // 不替模型做「看或不看」的假设。需要让模型看图 → 模型可主动调用 view_image(URL);
103
+ // 需要保存 → download_file 下载。
104
104
  return JSON.stringify({
105
105
  success: true,
106
106
  model: imageModel.name,
107
107
  modelId: imageModel.id,
108
108
  created: result.created,
109
109
  images: urls.map((url, i) => ({ index: i, url })),
110
- note: "图片临时链接有效期为30天。如需查看图片内容,可用 viewImage 工具(传入图片 URL);如需保存,可用 downloadFile 下载。",
110
+ note: "图片临时链接有效期为30天。如需查看图片内容,可用 view_image 工具(传入图片 URL);如需保存,可用 download_file 下载。",
111
111
  }, null, 2);
112
112
  }
113
113
  catch (error) {
@@ -69,7 +69,7 @@ export class GlobTool extends SdkCallbackTool {
69
69
  return JSON.stringify({
70
70
  outputTooLarge: true,
71
71
  warning: `输出过大:匹配结果共 ${json.length} 字符,超过上限 ${limit} 字符。` +
72
- `完整结果已落盘(result.json),可用 readFile 分批读取;` +
72
+ `完整结果已落盘(result.json),可用 read 分批读取;` +
73
73
  `也可用更精确的 pattern 或 exclude 缩小范围后重新查找。`,
74
74
  files: ctx.files,
75
75
  chars: json.length,
@@ -4,8 +4,8 @@ import type { AgentNS } from "@ai-zen/agents-core";
4
4
  /**
5
5
  * 勘察文件结构概况(行数、每行列数等),不做内容读取。
6
6
  *
7
- * 定位与 readFile 的 range 配套:大文件先看结构(有多少行、每行多长),
8
- * 再决定 readFile 的读取范围。流式扫描,故不受 readFile 的 300KB 读取阈值限制。
7
+ * 定位与 read 的 range 配套:大文件先看结构(有多少行、每行多长),
8
+ * 再决定 read 的读取范围。流式扫描,故不受 read 的 300KB 读取阈值限制。
9
9
  */
10
10
  export declare class InspectFileTool extends SdkCallbackTool {
11
11
  function: AgentNS.FunctionDefine;
@@ -6,13 +6,13 @@ import { DEFAULT_MAX_TOOL_OUTPUT } from "./outputGuard.js";
6
6
  /**
7
7
  * 勘察文件结构概况(行数、每行列数等),不做内容读取。
8
8
  *
9
- * 定位与 readFile 的 range 配套:大文件先看结构(有多少行、每行多长),
10
- * 再决定 readFile 的读取范围。流式扫描,故不受 readFile 的 300KB 读取阈值限制。
9
+ * 定位与 read 的 range 配套:大文件先看结构(有多少行、每行多长),
10
+ * 再决定 read 的读取范围。流式扫描,故不受 read 的 300KB 读取阈值限制。
11
11
  */
12
12
  export class InspectFileTool extends SdkCallbackTool {
13
13
  function = {
14
- name: "inspectFile",
15
- description: "勘察文件的结构概况:总行数、总字符数、最长行的列数及其行索引、平均列数、行尾类型(LF/CRLF)。可选返回每行的列数映射。适用于在 readFile 之前先了解文件规模,再决定用 range 读取哪一段。仅做流式扫描,不受 readFile 的 300KB 读取阈值限制。",
14
+ name: "inspect_file",
15
+ description: "勘察文件的结构概况:总行数、总字符数、最长行的列数及其行索引、平均列数、行尾类型(LF/CRLF)。可选返回每行的列数映射。适用于在 read 之前先了解文件规模,再决定用 range 读取哪一段。仅做流式扫描,不受 read 的 300KB 读取阈值限制。",
16
16
  parameters: {
17
17
  type: "object",
18
18
  properties: {
@@ -63,7 +63,7 @@ export class InspectFileTool extends SdkCallbackTool {
63
63
  colCountMapOmitted: true,
64
64
  warning: `行长度明细过大:概况与 colCountMap 合计 ${payload.length} 字符,` +
65
65
  `超过上限 ${limit} 字符,未落盘返回。文件规模超出可处理范围,` +
66
- `建议改用 findText 定位后再按 range 分段读取。`,
66
+ `建议改用 find_text 定位后再按 range 分段读取。`,
67
67
  });
68
68
  }
69
69
  return payload;
@@ -36,7 +36,7 @@ export class LsTool extends SdkCallbackTool {
36
36
  return JSON.stringify({
37
37
  outputTooLarge: true,
38
38
  warning: `输出过大:目录条目共 ${entries.length} 项、${json.length} 字符,超过上限 ${limit} 字符。` +
39
- `完整列表已落盘(result.json),可用 readFile 分批读取;` +
39
+ `完整列表已落盘(result.json),可用 read 分批读取;` +
40
40
  `也可改用 glob 按模式筛选。`,
41
41
  files: ctx.files,
42
42
  chars: json.length,
@@ -1,7 +1,7 @@
1
1
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
2
2
  import type { ToolEnv } from "../../../types/index.js";
3
3
  import type { AgentNS } from "@ai-zen/agents-core";
4
- export declare class ReadFileTool extends SdkCallbackTool {
4
+ export declare class ReadTool extends SdkCallbackTool {
5
5
  function: AgentNS.FunctionDefine;
6
6
  constructor(env: ToolEnv);
7
7
  call(input: {
@@ -1,9 +1,9 @@
1
1
  import * as fsp from "fs/promises";
2
2
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
3
  import { DEFAULT_MAX_TOOL_OUTPUT, guardOutput } from "./outputGuard.js";
4
- export class ReadFileTool extends SdkCallbackTool {
4
+ export class ReadTool extends SdkCallbackTool {
5
5
  function = {
6
- name: "readFile",
6
+ name: "read",
7
7
  description: "读取文件",
8
8
  parameters: {
9
9
  type: "object",
@@ -41,7 +41,7 @@ export class ReadFileTool extends SdkCallbackTool {
41
41
  // 输出保护:本次返回内容超过上限时只警告、不落盘,并提示用 range 分批读取
42
42
  const limit = this.env.config.maxToolOutput ?? DEFAULT_MAX_TOOL_OUTPUT;
43
43
  return await guardOutput({
44
- tool: "readFile",
44
+ tool: "read",
45
45
  content: output,
46
46
  isOverLimit: (text) => text.length > limit,
47
47
  buildWarning: () => `读取内容过大:本次输出 ${output.length} 字符,超过上限 ${limit} 字符,未落盘。` +
@@ -1,7 +1,7 @@
1
1
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
2
2
  import type { ToolEnv } from "../../../types/index.js";
3
3
  import type { AgentNS } from "@ai-zen/agents-core";
4
- export declare class ExecAsyncTool extends SdkCallbackTool {
4
+ export declare class SpawnTool extends SdkCallbackTool {
5
5
  function: AgentNS.FunctionDefine;
6
6
  constructor(env: ToolEnv);
7
7
  call(input: {
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "child_process";
2
2
  import { SdkCallbackTool } from "./SdkCallbackTool.js";
3
- export class ExecAsyncTool extends SdkCallbackTool {
3
+ export class SpawnTool extends SdkCallbackTool {
4
4
  function = {
5
- name: "exec_async",
5
+ name: "spawn",
6
6
  description: "异步执行命令,启动程序后立即返回,不等待执行结果。适用于启动长期运行的进程(如服务器、监听程序、GUI应用等)或不需要等待结果的命令。注意:该工具本身不捕获命令的输出;若需获取/留存输出,可在命令中使用 shell 重定向写入文件(如 `> /path/file`,支持 `>>` 追加、`|` 管道)。命令经 shell 解析执行。",
7
7
  parameters: {
8
8
  type: "object",
@@ -33,7 +33,7 @@ export class ViewImageTool extends SdkCallbackTool {
33
33
  return model?.vision === true;
34
34
  }
35
35
  function = {
36
- name: "viewImage",
36
+ name: "view_image",
37
37
  description: "查看一张图片并分析其内容。传入本地图片路径或 http(s) 图片 URL。本地图片会自动上传(Files API)后引用,网络图片直接引用。不支持本地 URL(如 file:// 协议、指向本机 localhost/127.0.0.1 的 http(s) 地址),本地图片请使用文件路径。适合识别图片文字、描述图片内容、分析图表等。",
38
38
  parameters: {
39
39
  type: "object",
@@ -54,7 +54,7 @@ export class ViewImageTool extends SdkCallbackTool {
54
54
  const signal = ctx?.signal;
55
55
  const target = input?.path_or_url;
56
56
  if (!target || !target.trim()) {
57
- return "viewImage 需要提供 path_or_url 参数(本地图片路径或 http(s) URL)。";
57
+ return "view_image 需要提供 path_or_url 参数(本地图片路径或 http(s) URL)。";
58
58
  }
59
59
  const value = target.trim();
60
60
  // 拒绝服务商无法访问的"本地 URL":file:// 等本地协议,或指向本机回环地址(localhost/127.0.0.1/[::1]/0.0.0.0)的 http(s) 地址