@ai-zen/agents-sdk 0.9.4 → 0.11.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0] - 2026-09-28
4
+
5
+ ### 💥 Breaking Changes
6
+
7
+ - **`createCallSkillSubAgentTool` 移除第三参 `provider`** — Skill 子 Agent 不再经 `provider.buildTools(parent.definition, …)` 重新解析工具集,改为**直接沿用父 Agent 的工具集**(`ctx.agent.tools`),仅剔除 `call_skill_sub_agent` 自身:Skill 子 Agent 作为一次性对话分身,沿用调用者的能力而不再走第二遍权限过滤,同时避免 skill 链式自递归。传入 `Provider` 的调用方需删除该实参
8
+
9
+ ### 🚀 New Features
10
+
11
+ - **Skill 子 Agent 纳入统一委派边界** — `call_skill_sub_agent` 由 `AgentToolLazy` 承载(原为就地实现的 `CallbackTool`),因此与常规 SubAgent 工具共享同一套 `onSubAgentStart` / `onSubAgentEnd` 插件钩子与 `sub-agent-start` / `sub-agent-end` 事件。将护栏装配到 `ctx.subAgent` 的消费方(如上下文护栏)与渲染子 Agent 流式输出的消费方(如 CLI),自此均覆盖该路径
12
+
13
+ ### 🛠 Optimized
14
+
15
+ - **Skill 子 Agent 的构建收敛到 `buildAgent`** — 子 Agent 的初始消息(`System(SKILL.md 内容)` + `User(task)`)改由 `buildAgent` 决定(依托 core 4.3.0 的 `messages` 可选),原先手写的 abort 联动与返回值处理一并删除,交由工具基类统一实现
16
+ - **校验失败改为抛错** — `skill_id` 不存在、或 Skill 未声明 `sub-agent: true` 时抛出错误(原为返回提示文本);正常路径由 `skill_id` 枚举限定,不触发
17
+ - **依赖 `@ai-zen/agents-core` 升至 `^4.3.0`** — 依赖声明保持 `workspace:^`,由 pnpm 在发布时展开为 `^4.3.0`。core 4.3.0 使 `AgentToolLazy.messages` 变为可选
18
+
19
+ ### ✅ Tests
20
+
21
+ - `skillTools.test.ts` 中 `createCallSkillSubAgentTool` 用例重写为围绕 `AgentToolLazy`:工具契约不变、未知 Skill 抛错、非子 Agent Skill 抛错、委派边界(`onSubAgentStart` 与 `sub-agent-start` 分发、子 Agent 沿用父工具集且剔除自身、初始消息取自 SKILL.md 与 `task`);SDK 全量 **463 passed / 6 skipped**
22
+
23
+ ## [0.10.0] - 2026-09-28
24
+
25
+ ### 🚀 New Features
26
+
27
+ - **`load_mcp` 新增 `include_manifest` 开关(可选布尔,默认 `true`)** — 默认行为逐字不变(返回 `{ tools, resources }` 完整清单,tools 含完整 `inputSchema`);显式传 `false` 时仅建立连接并返回摘要 `{ server, connected, tools: 数量, resources: 数量 }`,不返回任何工具/资源定义。用于「进程重启后连接已失效、但上文中已存在该清单」的场景:只重建连接,不重复向上下文灌入数十 KB 的工具定义,节省 token。完整清单仍留在 `McpConnectionManager` 内,`call_mcp_tool` / `read_mcp_resource` 行为不变;`server` 参数枚举、工具名表与 `DYNAMIC_TOOL_NAMES` 均未变动(前缀缓存结构不变)。失败路径与已连接路径的语义在两种模式下一致;`docs/sdk-design.md` §9 同步更新
28
+
29
+ ### 🛠 Optimized
30
+
31
+ - **依赖 `@ai-zen/agents-core` 升至 `^4.2.0`** — 依赖声明保持 `workspace:^`,由 pnpm 在发布时展开为 `^4.2.0`。core 4.2.0 新增子 Agent 委派阻塞钩子 `onSubAgentStart` / `onSubAgentEnd`,并将原有事件 `sub-agent` 更名为 `sub-agent-start`(core 侧破坏性变更);SDK 内部不监听该事件,源码无需适配。监听该事件的消费方需同步更名
32
+
33
+ ### ✅ Tests
34
+
35
+ - `mcpTools.test.ts` 新增 4 个用例:静默 + 未连接、静默 + 已连接、显式 `true`、静默 + 连接失败;SDK 全量 **461 passed / 6 skipped**
36
+
3
37
  ## [0.9.4] - 2026-09-20
4
38
 
5
39
  ### 🔧 Fixed
package/README.md CHANGED
@@ -19,7 +19,7 @@ Desktop ──┘ │
19
19
 
20
20
  ```
21
21
  types ← pure types, zero business dependencies (incl. ToolEnv)
22
- config ← read/write config.json + migration + in-memory cache + atomic writes
22
+ config ← ConfigManager + constants: config.json read/write + directory setup + factory defaults
23
23
  crud ← capability-entity CRUD (Agent definitions, etc.; conversations/drafts are persisted by each consumer)
24
24
  capabilities ← capability discovery & assembly (built-in + user + MCP + Skill + SubAgent)
25
25
  runtime ← Provider + model factory + Agent assembly + MCP connection management + task migration + SdkCallbackTool base
@@ -37,7 +37,7 @@ Dependency direction: `plugin → runtime → capabilities → crud → config
37
37
  | **ToolEnv** | Tool environment `{ cwd, config }`; injected when the Provider instantiates built-in tools, serving as the base for relative path resolution and config reads |
38
38
  | **SdkCallbackTool** | Abstract base for built-in tools: `env` constructor injection + subclass `call()` + `resolve()` relative path resolution |
39
39
  | **SdkAgent** | Extends the Core Agent, carries SDK metadata, supports `use()` plugin registration |
40
- | **AgentPlugin** | Plugin interface (`onInit`, `onBeforeSend`, `onAfterSend`, `onInnerLoopStart`, `onInnerLoopEnd`) |
40
+ | **AgentPlugin** | Plugin interface (`onInit`, `onBeforeSend`, `onAfterSend`, `onInnerLoopStart`, `onInnerLoopEnd`, `onInnerLoopsStart`, `onInnerLoopsEnd`, `onToolCall`, `onUnknownTool`, `onSubAgentStart`, `onSubAgentEnd`) |
41
41
  | **Endpoint** | API endpoint (baseUrl + apiKey) |
42
42
  | **Model** | Model config, bound to an Endpoint |
43
43
  | **SubAgent** | An Agent with a `function` field, callable by other Agents as a tool |
@@ -62,7 +62,7 @@ const provider = await Provider.create({
62
62
  cwd: "/path/to/workspace", // one working directory per Provider; parallel sessions don't interfere
63
63
  ...paths,
64
64
  });
65
- const agent = createAgent(provider, "my-agent");
65
+ const agent = await createAgent(provider, "my-agent");
66
66
  const migrationService = new TaskMigrationService({ onMigrated }); // migrates via the agent's own model client
67
67
  agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
68
68
  agent.use(new AutoRefreshToolsPlugin());
@@ -78,10 +78,9 @@ await agent.send("Hello");
78
78
  | `config` | ✅ Implemented — ConfigManager + factory defaults + one-shot bootstrap |
79
79
  | `crud` | ✅ Implemented — capability-entity CRUD for Agents, etc. (conversations/drafts persisted by each consumer) |
80
80
  | `capabilities` | ✅ Implemented — discovery + permission filtering + safe pre-filtering + enumeration disclosure |
81
- | `runtime` | ✅ Implemented — Provider, Capabilities, createAgent, MCP connection management, task migration |
81
+ | `runtime` | ✅ Implemented — Provider, createAgent, MCP connection management, task migration |
82
82
  | `plugin` | ✅ Implemented — AutoMigratePlugin / AutoRefreshToolsPlugin / ContextGuardPlugin / UnknownToolHintPlugin |
83
83
  | `shared` | ✅ Implemented — SdkError + injectable Logger |
84
- | Tests | ✅ 452 passing, 51 files, all green (incl. real-API chat & viewImage e2e) |
85
84
 
86
85
  ## Built-in Tools
87
86
 
@@ -134,4 +133,3 @@ See the project-root [`PRINCIPLES.md`](../../PRINCIPLES.md):
134
133
  5. Occam's razor
135
134
  6. Refactor as you go; keep clean layering
136
135
  7. Tests are the foundation
137
- he foundation
package/README.zh.md CHANGED
@@ -18,7 +18,7 @@ Desktop ──┘ │
18
18
 
19
19
  ```
20
20
  types ← 纯类型,零业务依赖(含 ToolEnv 工具环境)
21
- config ← 读写 config.json + 迁移 + 内存缓存 + 原子写入
21
+ config ← ConfigManager + constants:读写 config.json + 目录初始化 + 出厂默认
22
22
  crud ← 能力实体 CRUD(Agent 定义等;会话/草稿已下放给各端自行持久化)
23
23
  capabilities ← 能力发现与装配(内置 + 用户 + MCP + Skill + SubAgent)
24
24
  runtime ← Provider + 模型工厂 + Agent 组装 + MCP 连接管理 + 任务迁移 + SdkCallbackTool 工具基类
@@ -36,7 +36,7 @@ shared ← 日志、错误
36
36
  | **ToolEnv** | 工具环境 `{ cwd, config }`,Provider 实例化内置工具时注入,作为相对路径解析与配置读取的基准 |
37
37
  | **SdkCallbackTool** | 内置工具抽象基类:`env` 构造注入 + 子类实现 `call()` + `resolve()` 相对路径解析 |
38
38
  | **SdkAgent** | 继承 Core Agent,携带 SDK 元数据,支持 `use()` 插件注册 |
39
- | **AgentPlugin** | 插件接口(`onInit`, `onBeforeSend`, `onAfterSend`, `onInnerLoopStart`, `onInnerLoopEnd`) |
39
+ | **AgentPlugin** | 插件接口(`onInit`, `onBeforeSend`, `onAfterSend`, `onInnerLoopStart`, `onInnerLoopEnd`, `onInnerLoopsStart`, `onInnerLoopsEnd`, `onToolCall`, `onUnknownTool`, `onSubAgentStart`, `onSubAgentEnd`) |
40
40
  | **Endpoint** | API 端点(baseUrl + apiKey) |
41
41
  | **Model** | 模型配置,绑定 Endpoint |
42
42
  | **SubAgent** | 有 `function` 字段的 Agent,可被其他 Agent 作为工具调用 |
@@ -61,7 +61,7 @@ const provider = await Provider.create({
61
61
  cwd: "/path/to/workspace", // 每个 Provider 一个工作目录,多会话并行互不干扰
62
62
  ...paths,
63
63
  });
64
- const agent = createAgent(provider, "my-agent");
64
+ const agent = await createAgent(provider, "my-agent");
65
65
  const migrationService = new TaskMigrationService({ onMigrated }); // 迁移复用传入 agent 自身的模型调用
66
66
  agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
67
67
  agent.use(new AutoRefreshToolsPlugin());
@@ -77,10 +77,9 @@ await agent.send("你好");
77
77
  | `config` | ✅ 已实现 — ConfigManager + 出厂默认配置 + 一键 bootstrap |
78
78
  | `crud` | ✅ 已实现 — Agent 等能力实体 CRUD(会话/草稿由各端自行持久化) |
79
79
  | `capabilities` | ✅ 已实现 — 发现 + 权限过滤 + 安全预过滤 + 枚举披露 |
80
- | `runtime` | ✅ 已实现 — Provider、Capabilities、createAgent、MCP 连接管理、任务迁移 |
80
+ | `runtime` | ✅ 已实现 — Provider、createAgent、MCP 连接管理、任务迁移 |
81
81
  | `plugin` | ✅ 已实现 — AutoMigratePlugin / AutoRefreshToolsPlugin / ContextGuardPlugin / UnknownToolHintPlugin |
82
82
  | `shared` | ✅ 已实现 — SdkError + 可注入 Logger |
83
- | 测试 | ✅ 452 通过,51 个文件,全绿(含真实 API 聊天与 viewImage e2e) |
84
83
 
85
84
  ## 内置工具
86
85
 
@@ -24,7 +24,7 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
24
24
  return new CallbackTool({
25
25
  function: {
26
26
  name: "load_mcp",
27
- description: "连接到指定 MCP 服务器,获取其可用工具和资源列表。",
27
+ description: "连接到指定 MCP 服务器,获取其可用工具和资源列表。可用 include_manifest=false 仅建立连接、不返回清单(适用于上文中已有该清单的场景)。",
28
28
  parameters: {
29
29
  type: "object",
30
30
  properties: {
@@ -33,6 +33,11 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
33
33
  description: serverIdDescription,
34
34
  ...(param.enum ? { enum: param.enum } : {}),
35
35
  },
36
+ include_manifest: {
37
+ type: "boolean",
38
+ description: "是否返回该服务器的完整能力清单(工具与资源)。可选,默认 true。若你的上文中已存在该清单(例如本会话早前加载过,而进程重启后连接已失效),可设为 false 仅建立连接、只返回摘要,以节省上下文 token",
39
+ default: true,
40
+ },
36
41
  },
37
42
  required: ["server"],
38
43
  additionalProperties: false,
@@ -44,20 +49,35 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
44
49
  if (!config) {
45
50
  return `❌ MCP 服务器 "${serverName}" 不存在`;
46
51
  }
52
+ // 披露开关:默认返回完整清单;显式传 false 时仅建立连接、只返回摘要
53
+ const includeManifest = input.include_manifest !== false;
54
+ // 获取清单:已连接 → 复用缓存(touch 续期);未连接 → 建连并发现
55
+ let manifest;
47
56
  const existingManifest = mcpManager.getManifest(serverName);
48
57
  if (existingManifest && mcpManager.getState(serverName) === "connected") {
49
58
  mcpManager.touch(serverName);
50
- log.info(`[load_mcp] ${serverName}:\n${JSON.stringify(existingManifest, null, 2)}`);
51
- return JSON.stringify({ tools: existingManifest.tools, resources: existingManifest.resources });
59
+ manifest = existingManifest;
52
60
  }
53
- try {
54
- const manifest = await mcpManager.connect(serverName, config);
55
- log.info(`[load_mcp] ${serverName}:\n${JSON.stringify(manifest, null, 2)}`);
56
- return JSON.stringify({ tools: manifest.tools, resources: manifest.resources });
61
+ else {
62
+ try {
63
+ manifest = await mcpManager.connect(serverName, config);
64
+ }
65
+ catch (error) {
66
+ return `无法连接到 "${serverName}": ${error?.message ?? error}`;
67
+ }
57
68
  }
58
- catch (error) {
59
- return `无法连接到 "${serverName}": ${error?.message ?? error}`;
69
+ // 静默模式:只回吐摘要(清单仍留在管理器内,供 call_mcp_tool 使用)
70
+ if (!includeManifest) {
71
+ log.info(`[load_mcp] ${serverName}: 已连接(未披露清单:工具 ${manifest.tools.length} 个,资源 ${manifest.resources.length} 个)`);
72
+ return JSON.stringify({
73
+ server: serverName,
74
+ connected: true,
75
+ tools: manifest.tools.length,
76
+ resources: manifest.resources.length,
77
+ });
60
78
  }
79
+ log.info(`[load_mcp] ${serverName}:\n${JSON.stringify(manifest, null, 2)}`);
80
+ return JSON.stringify({ tools: manifest.tools, resources: manifest.resources });
61
81
  },
62
82
  });
63
83
  }
@@ -1,6 +1,5 @@
1
- import { CallbackTool } from "@ai-zen/agents-core";
1
+ import { CallbackTool, AgentToolLazy } from "@ai-zen/agents-core";
2
2
  import type { SkillInfo } from "../discovery/skills.js";
3
- import type { Provider } from "../../runtime/Provider.js";
4
3
  /**
5
4
  * 创建 load_skill 工具。
6
5
  * 枚举所有允许的 skill(不限是否支持子 Agent),由 filteredSkills 提供完整信息。
@@ -10,6 +9,9 @@ export declare function createLoadSkillTool(skillDirs: string[], filteredSkills:
10
9
  * 创建 call_skill_sub_agent 工具。
11
10
  * 只展示 subAgent: true 的 Skill,枚举中自动排除不支持子 Agent 模式的 skill。
12
11
  *
13
- * 回调中创建独立的 Skill 子 Agent,通过 caps 按父 Agent 权限独立解析工具集。
12
+ * 工具由 AgentToolLazy 承载:子 Agent 在 exec 时构建,从而复用统一的委派边界
13
+ * (onSubAgentStart / onSubAgentEnd 钩子与 sub-agent-start / sub-agent-end 事件)。
14
+ * 子 Agent 沿用父 Agent 的工具能力(不做权限过滤),但剔除 call_skill_sub_agent 自身,
15
+ * 避免 skill 链式自递归。
14
16
  */
15
- export declare function createCallSkillSubAgentTool(skillDirs: string[], filteredSkills: SkillInfo[], provider?: Provider): CallbackTool;
17
+ export declare function createCallSkillSubAgentTool(skillDirs: string[], filteredSkills: SkillInfo[]): AgentToolLazy;
@@ -1,5 +1,4 @@
1
- import { CallbackTool, Agent, Message } from "@ai-zen/agents-core";
2
- import { SdkAgent } from "../../runtime/SdkAgent.js";
1
+ import { CallbackTool, Agent, AgentToolLazy, Message } from "@ai-zen/agents-core";
3
2
  import { createDisclosureParam } from "../disclosure.js";
4
3
  import { readSkill } from "../discovery/skills.js";
5
4
  import { getLogger } from "../../shared/logger.js";
@@ -70,9 +69,12 @@ export function createLoadSkillTool(skillDirs, filteredSkills) {
70
69
  * 创建 call_skill_sub_agent 工具。
71
70
  * 只展示 subAgent: true 的 Skill,枚举中自动排除不支持子 Agent 模式的 skill。
72
71
  *
73
- * 回调中创建独立的 Skill 子 Agent,通过 caps 按父 Agent 权限独立解析工具集。
72
+ * 工具由 AgentToolLazy 承载:子 Agent 在 exec 时构建,从而复用统一的委派边界
73
+ * (onSubAgentStart / onSubAgentEnd 钩子与 sub-agent-start / sub-agent-end 事件)。
74
+ * 子 Agent 沿用父 Agent 的工具能力(不做权限过滤),但剔除 call_skill_sub_agent 自身,
75
+ * 避免 skill 链式自递归。
74
76
  */
75
- export function createCallSkillSubAgentTool(skillDirs, filteredSkills, provider) {
77
+ export function createCallSkillSubAgentTool(skillDirs, filteredSkills) {
76
78
  // 只保留支持子 Agent 模式的 skill
77
79
  const subAgentSkills = filteredSkills.filter((s) => s.subAgent);
78
80
  const ids = subAgentSkills.map((s) => s.id);
@@ -82,7 +84,7 @@ export function createCallSkillSubAgentTool(skillDirs, filteredSkills, provider)
82
84
  .map((s) => ` - ${s.id}: ${s.description || "无描述"}`)
83
85
  .join("\n");
84
86
  const skillIdDescription = `${param.description}\n\n各 Skill 说明:\n${skillDescriptions || " 无可用 Skill"}`;
85
- return new CallbackTool({
87
+ return new AgentToolLazy({
86
88
  function: {
87
89
  name: "call_skill_sub_agent",
88
90
  description: "将任务委派给指定的 Skill 子 Agent,由其独立完成并返回结果。",
@@ -103,46 +105,28 @@ export function createCallSkillSubAgentTool(skillDirs, filteredSkills, provider)
103
105
  additionalProperties: false,
104
106
  },
105
107
  },
106
- async callback(input, ctx) {
107
- const skillId = input.skill_id;
108
- const task = input.task;
108
+ // 初始消息依赖运行时参数(skill_id → SKILL.md 内容),故不传 messages 模板,
109
+ // 由 buildAgent 读取 SKILL.md 后自行决定;abort 联动与委派边界由 AgentToolLazy 统一处理
110
+ buildAgent: async (parsedArgs, ctx) => {
111
+ const skillId = parsedArgs.skill_id;
112
+ const task = parsedArgs.task;
109
113
  const skill = await readSkill(skillDirs, skillId);
110
114
  if (!skill) {
111
- return `❌ Skill "${skillId}" 不存在,请确认名称是否正确`;
115
+ throw new Error(`❌ Skill "${skillId}" 不存在,请确认名称是否正确`);
112
116
  }
113
117
  if (!skill.subAgent) {
114
- return `Skill "${skillId}" 不支持子 Agent 模式,请使用 load_skill 加载指导后自行处理`;
118
+ throw new Error(`Skill "${skillId}" 不支持子 Agent 模式,请使用 load_skill 加载指导后自行处理`);
115
119
  }
116
- // 临时 Skill 子 Agent 复用父 Agent 定义(权限 + 模型),排除当前 Skill 防自调用
120
+ // 沿用父 Agent 的工具能力(不做权限过滤),剔除自身以防自递归
117
121
  const parentAgent = ctx.agent;
118
- const skillTools = provider && parentAgent instanceof SdkAgent
119
- ? provider.buildTools(parentAgent.definition, {
120
- exclude: { skills: [skillId] },
121
- })
122
- : [];
123
- const subAgent = new Agent({
122
+ const tools = parentAgent.tools.filter((t) => t.function.name !== "call_skill_sub_agent");
123
+ return new Agent({
124
124
  client: parentAgent.client,
125
125
  model: parentAgent.model,
126
126
  modelConfig: parentAgent.modelConfig,
127
- messages: [
128
- Message.System(skill.content),
129
- Message.User(task),
130
- ],
131
- tools: skillTools,
127
+ messages: [Message.System(skill.content), Message.User(task)],
128
+ tools,
132
129
  });
133
- // 子 Agent 中断联动:外层 abort → subAgent.abort()
134
- const onAbort = () => subAgent.abort();
135
- if (ctx.signal) {
136
- ctx.signal.addEventListener("abort", onAbort, { once: true });
137
- }
138
- try {
139
- await subAgent.run();
140
- }
141
- finally {
142
- ctx.signal?.removeEventListener("abort", onAbort);
143
- }
144
- const lastMsg = subAgent.messages.at(-1);
145
- return typeof lastMsg?.content === "string" ? lastMsg.content : "";
146
130
  },
147
131
  });
148
132
  }
@@ -170,7 +170,7 @@ export class Provider {
170
170
  // call_skill_sub_agent:只要有支持子 Agent 的 skill 就注册
171
171
  const hasSubAgentSkills = filteredSkills.some((s) => s.subAgent);
172
172
  if (allowedToolNames.has("call_skill_sub_agent") && hasSubAgentSkills) {
173
- result.push(createCallSkillSubAgentTool(this.skillsPaths, filteredSkills, this));
173
+ result.push(createCallSkillSubAgentTool(this.skillsPaths, filteredSkills));
174
174
  }
175
175
  const mcpManager = this.mcpManager;
176
176
  if (allowedToolNames.has("load_mcp") && mcpManager && filteredMcps.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "0.9.4",
3
+ "version": "0.11.0",
4
4
  "description": "AI-Zen Agents SDK — shared business logic for CLI and Desktop",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "author": "LZQCN",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "@ai-zen/agents-core": "^4.1.0",
19
+ "@ai-zen/agents-core": "^4.3.0",
20
20
  "@modelcontextprotocol/sdk": "^1.29.0",
21
21
  "dayjs": "^1.11.21",
22
22
  "openai": "^7.5.0"