@ai-zen/agents-sdk 0.2.0-alpha.3 → 0.2.2

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 ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## [0.2.2] - 2026-07-20
4
+
5
+ ### 🔧 修复
6
+
7
+ - **`SdkAgent.send()` — `ctx.messages` 不再直接引用 `this.messages`** — 改为浅拷贝快照,确保消息数组仅由 agent 作为唯一维护者
8
+ - **`AutoMigratePlugin` — 不再创建新 Agent 实例** — 直接替换 `agent.messages` 数组,保留所有引用、插件绑定和事件监听
9
+ - **`AutoMigratePlugin` — 迁移前增加日志提示** — 使用 `log.warn` 输出迁移开始通知
10
+
11
+ ### 🎯 优化
12
+
13
+ - **`AutoMigratePlugin` — `onHandoff` 签名简化** — 从 `(doc, oldAgent, newAgent)` 改为 `(doc, agent)`,因为 agent 是同一对象
14
+ - **`TaskMigrationService.createPrompt()` — 精简提示词** — 明确要求只输出交接文档,不做任何解释
15
+
16
+ ### ✅ 测试
17
+
18
+ - 补充迁移前后消息数组**长度精确断言**(迁移前 3 条 → 迁移后 2 条)
19
+ - 补充迁移失败时消息数组**长度和内容不变**的断言
@@ -67,5 +67,7 @@ export declare class Capabilities {
67
67
  /**
68
68
  * 重新执行全局发现(重新扫描文件系统)。
69
69
  */
70
- refresh(): void;
70
+ refresh(options?: {
71
+ silent?: boolean;
72
+ }): void;
71
73
  }
@@ -116,14 +116,15 @@ export class Capabilities {
116
116
  if (allowedToolNames.has("call_skill_sub_agent") && hasSubAgentSkills) {
117
117
  result.push(createCallSkillSubAgentTool(provider.skillsPaths, filteredSkills, this));
118
118
  }
119
- if (allowedToolNames.has("load_mcp") && provider.mcpManager && provider.mcpConfigs && filteredMcps.length > 0) {
120
- result.push(createLoadMcpTool(provider.mcpManager, provider.mcpConfigs, filteredMcps));
119
+ const mcpManager = provider.getMcpManager();
120
+ if (allowedToolNames.has("load_mcp") && mcpManager && filteredMcps.length > 0) {
121
+ result.push(createLoadMcpTool(mcpManager, filteredMcps));
121
122
  }
122
- if (allowedToolNames.has("call_mcp_tool") && provider.mcpManager) {
123
- result.push(createCallMcpTool(provider.mcpManager));
123
+ if (allowedToolNames.has("call_mcp_tool") && mcpManager) {
124
+ result.push(createCallMcpTool(mcpManager));
124
125
  }
125
- if (allowedToolNames.has("read_mcp_resource") && provider.mcpManager) {
126
- result.push(createReadMcpResourceTool(provider.mcpManager));
126
+ if (allowedToolNames.has("read_mcp_resource") && mcpManager) {
127
+ result.push(createReadMcpResourceTool(mcpManager));
127
128
  }
128
129
  // 4. SubAgent
129
130
  const allowedSubagentSet = new Set(filtered.subagents);
@@ -138,12 +139,13 @@ export class Capabilities {
138
139
  /**
139
140
  * 重新执行全局发现(重新扫描文件系统)。
140
141
  */
141
- refresh() {
142
+ refresh(options) {
143
+ const silent = options?.silent ?? false;
142
144
  const provider = this.provider;
143
145
  this.builtinInstances = discoverBuiltinTools(provider.config);
144
- this.userInstances = discoverUserTools(provider.toolsPaths);
146
+ this.userInstances = discoverUserTools(provider.toolsPaths, { silent });
145
147
  this.subagentDefs = discoverSubAgents(provider.subAgentsPaths);
146
- this.skills = discoverSkills(provider.skillsPaths);
148
+ this.skills = discoverSkills(provider.skillsPaths, { silent });
147
149
  this.mcps = discoverMcpServers(provider.mcpPaths);
148
150
  }
149
151
  }
@@ -1,7 +1,12 @@
1
1
  import type { McpServerConfig } from "../../types/index.js";
2
2
  /**
3
3
  * 从 mcp.json 文件列表中发现所有 MCP server 配置。
4
+ *
5
+ * 遵循业界标准格式:
6
+ * ```json
7
+ * { "mcpServers": { "server-id": { "type": "stdio", "command": "npx", ... } } }
8
+ * ```
9
+ *
4
10
  * 按优先级从高到低传入路径列表,同名 server 靠前的文件优先(先到先得)。
5
- * 返回完整配置,不再丢失信息。
6
11
  */
7
12
  export declare function discoverMcpServers(paths: string[]): McpServerConfig[];
@@ -1,8 +1,55 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
+ /**
3
+ * 归一化原始 MCP server 配置为内部 McpServerConfig。
4
+ * 处理业界标准中的字段别名差异。
5
+ */
6
+ function normalizeConfig(id, raw) {
7
+ // disabled 处理
8
+ if (raw.disabled === true)
9
+ return null;
10
+ // transport 类型:优先取 type,兼容 transport/transportType
11
+ const transportRaw = (raw.type ?? raw.transport ?? raw.transportType ?? "");
12
+ const transport = normalizeTransport(transportRaw, raw);
13
+ if (!transport)
14
+ return null;
15
+ const config = {
16
+ id,
17
+ transport,
18
+ command: raw.command,
19
+ args: raw.args,
20
+ env: raw.env,
21
+ url: raw.url,
22
+ headers: raw.headers,
23
+ };
24
+ return config;
25
+ }
26
+ /**
27
+ * 归一化 transport 取值。
28
+ * 不填时自动推断:有 command→stdio,有 url→http。
29
+ */
30
+ function normalizeTransport(type, raw) {
31
+ if (type === "stdio")
32
+ return "stdio";
33
+ if (type === "http")
34
+ return "http";
35
+ if (type === "sse")
36
+ return "sse";
37
+ // 自动推断
38
+ if (raw.command)
39
+ return "stdio";
40
+ if (raw.url)
41
+ return "http";
42
+ return null;
43
+ }
2
44
  /**
3
45
  * 从 mcp.json 文件列表中发现所有 MCP server 配置。
46
+ *
47
+ * 遵循业界标准格式:
48
+ * ```json
49
+ * { "mcpServers": { "server-id": { "type": "stdio", "command": "npx", ... } } }
50
+ * ```
51
+ *
4
52
  * 按优先级从高到低传入路径列表,同名 server 靠前的文件优先(先到先得)。
5
- * 返回完整配置,不再丢失信息。
6
53
  */
7
54
  export function discoverMcpServers(paths) {
8
55
  const seen = new Set();
@@ -13,16 +60,23 @@ export function discoverMcpServers(paths) {
13
60
  try {
14
61
  const raw = readFileSync(path, "utf-8");
15
62
  const config = JSON.parse(raw);
16
- const servers = config.servers ?? {};
63
+ // 业界标准使用 mcpServers 顶层字段
64
+ const servers = config.mcpServers ?? {};
17
65
  for (const id of Object.keys(servers)) {
18
- if (!seen.has(id)) {
19
- seen.add(id);
20
- items.push({ id, ...servers[id] });
21
- }
66
+ if (seen.has(id))
67
+ continue;
68
+ const rawServer = servers[id];
69
+ if (!rawServer || typeof rawServer !== "object")
70
+ continue;
71
+ const normalized = normalizeConfig(id, rawServer);
72
+ if (!normalized)
73
+ continue;
74
+ seen.add(id);
75
+ items.push(normalized);
22
76
  }
23
77
  }
24
- catch {
25
- // 跳过解析失败
78
+ catch (err) {
79
+ console.warn(`[sdk] 解析 MCP 配置失败: ${path}`, err);
26
80
  }
27
81
  }
28
82
  return items;
@@ -30,13 +30,17 @@ export interface SkillInfo {
30
30
  * 按优先级从高到低传入路径列表,同名 skill 靠前的路径优先(先到先得)。
31
31
  * 解析 YAML frontmatter 中的 name 和 description,同时校验合规。
32
32
  */
33
- export declare function discoverSkills(paths: string[]): SkillInfo[];
33
+ export declare function discoverSkills(paths: string[], options?: {
34
+ silent?: boolean;
35
+ }): SkillInfo[];
34
36
  /**
35
37
  * 读取单个 Skill 的完整内容与元数据。
36
38
  * 按优先级从高到低传入路径列表,靠前的路径优先(先到先得)。
37
39
  * 返回 null 如果 skill 在所有目录中都不存在或 SKILL.md 不可读。
38
40
  */
39
- export declare function readSkill(skillDirs: string[], skillId: string): SkillInfo | null;
41
+ export declare function readSkill(skillDirs: string[], skillId: string, options?: {
42
+ silent?: boolean;
43
+ }): SkillInfo | null;
40
44
  /** @internal */
41
45
  export declare function parseFrontmatter(content: string): Frontmatter;
42
46
  /** @internal */
@@ -8,7 +8,8 @@ const log = createLogger();
8
8
  * 按优先级从高到低传入路径列表,同名 skill 靠前的路径优先(先到先得)。
9
9
  * 解析 YAML frontmatter 中的 name 和 description,同时校验合规。
10
10
  */
11
- export function discoverSkills(paths) {
11
+ export function discoverSkills(paths, options) {
12
+ const silent = options?.silent ?? false;
12
13
  const seen = new Set();
13
14
  const items = [];
14
15
  for (const dir of paths) {
@@ -29,7 +30,7 @@ export function discoverSkills(paths) {
29
30
  const skillMdPath = join(dir, entry.name, "SKILL.md");
30
31
  if (!existsSync(skillMdPath))
31
32
  continue;
32
- const skill = readSkillFromPath(entry.name, skillMdPath);
33
+ const skill = readSkillFromPath(entry.name, skillMdPath, silent);
33
34
  if (skill) {
34
35
  seen.add(entry.name);
35
36
  items.push(skill);
@@ -44,26 +45,29 @@ export function discoverSkills(paths) {
44
45
  * 按优先级从高到低传入路径列表,靠前的路径优先(先到先得)。
45
46
  * 返回 null 如果 skill 在所有目录中都不存在或 SKILL.md 不可读。
46
47
  */
47
- export function readSkill(skillDirs, skillId) {
48
+ export function readSkill(skillDirs, skillId, options) {
49
+ const silent = options?.silent ?? false;
48
50
  for (const dir of skillDirs) {
49
51
  const candidate = join(dir, skillId, "SKILL.md");
50
52
  if (existsSync(candidate)) {
51
- return readSkillFromPath(skillId, candidate);
53
+ return readSkillFromPath(skillId, candidate, silent);
52
54
  }
53
55
  }
54
56
  return null;
55
57
  }
56
58
  // ---- 内部 ----
57
- function readSkillFromPath(skillId, skillMdPath) {
59
+ function readSkillFromPath(skillId, skillMdPath, silent) {
58
60
  try {
59
61
  const content = readFileSync(skillMdPath, "utf-8");
60
62
  const fm = parseFrontmatter(content);
61
63
  if (!fm.name)
62
64
  return null;
63
65
  // 规范合规校验
64
- const errors = validateSkill(skillId, fm);
65
- for (const err of errors) {
66
- log.warn(`[skills] ${err.skillId}: ${err.field} — ${err.message}`);
66
+ if (!silent) {
67
+ const errors = validateSkill(skillId, fm);
68
+ for (const err of errors) {
69
+ log.warn(`[skills] ${err.skillId}: ${err.field} — ${err.message}`);
70
+ }
67
71
  }
68
72
  return {
69
73
  id: skillId,
@@ -10,4 +10,6 @@ import { Tool } from "@ai-zen/agents-core";
10
10
  * 3. 支持直接导出 { function, exec } 格式的对象(自动适配为 Tool)
11
11
  * 4. 支持导出 CallbackTool 兼容的 { function, callback } 格式
12
12
  */
13
- export declare function discoverUserTools(paths: string[]): Tool[];
13
+ export declare function discoverUserTools(paths: string[], options?: {
14
+ silent?: boolean;
15
+ }): Tool[];
@@ -13,7 +13,8 @@ import { Tool, CallbackTool } from "@ai-zen/agents-core";
13
13
  * 3. 支持直接导出 { function, exec } 格式的对象(自动适配为 Tool)
14
14
  * 4. 支持导出 CallbackTool 兼容的 { function, callback } 格式
15
15
  */
16
- export function discoverUserTools(paths) {
16
+ export function discoverUserTools(paths, options) {
17
+ const silent = options?.silent ?? false;
17
18
  const seen = new Set();
18
19
  const tools = [];
19
20
  for (const dir of paths) {
@@ -28,7 +29,7 @@ export function discoverUserTools(paths) {
28
29
  if (!seen.has(name)) {
29
30
  seen.add(name);
30
31
  try {
31
- const tool = loadToolFile(join(dir, name + ".js"));
32
+ const tool = loadToolFile(join(dir, name + ".js"), silent);
32
33
  if (tool) {
33
34
  tools.push(tool);
34
35
  }
@@ -51,7 +52,7 @@ export function discoverUserTools(paths) {
51
52
  * 使用 vm.compileFunction 创建沙箱执行环境,避免污染全局作用域。
52
53
  * 注入有限的全局 API(console, setTimeout 等),不暴露 process/require。
53
54
  */
54
- function loadToolFile(filepath) {
55
+ function loadToolFile(filepath, silent) {
55
56
  const code = readFileSync(filepath, "utf-8");
56
57
  // 构造沙箱上下文
57
58
  const sandbox = {
@@ -83,17 +84,19 @@ function loadToolFile(filepath) {
83
84
  return null;
84
85
  }
85
86
  // 适配不同导出格式
86
- return normalizeToolExport(exported, filepath);
87
+ return normalizeToolExport(exported, filepath, silent);
87
88
  }
88
89
  catch (err) {
89
- console.error(`[usertools] 加载工具文件失败: ${filepath} — ${err?.message ?? err}`);
90
+ if (!silent) {
91
+ console.error(`[usertools] 加载工具文件失败: ${filepath} — ${err?.message ?? err}`);
92
+ }
90
93
  return null;
91
94
  }
92
95
  }
93
96
  /**
94
97
  * 统一各种导出格式为 Tool 实例。
95
98
  */
96
- function normalizeToolExport(exported, filepath) {
99
+ function normalizeToolExport(exported, filepath, silent) {
97
100
  // 情况 1:直接是 Tool 实例
98
101
  if (exported instanceof Tool) {
99
102
  return exported;
@@ -114,7 +117,9 @@ function normalizeToolExport(exported, filepath) {
114
117
  callback: target.callback,
115
118
  });
116
119
  }
117
- console.error(`[usertools] 无法识别的工具格式: ${filepath}`);
120
+ if (!silent) {
121
+ console.error(`[usertools] 无法识别的工具格式: ${filepath}`);
122
+ }
118
123
  return null;
119
124
  }
120
125
  /**
@@ -94,7 +94,7 @@ export const execAsyncTool = new CallbackTool({
94
94
  resolve(JSON.stringify({
95
95
  success: false,
96
96
  pid: null,
97
- error: `启动失败: ${err.message}`,
97
+ message: `启动失败: ${err.message}`,
98
98
  }));
99
99
  });
100
100
  child.on("spawn", () => {
@@ -3,12 +3,10 @@ import type { McpConnectionManager } from "../../runtime/McpConnectionManager.js
3
3
  import type { McpServerConfig } from "../../types/index.js";
4
4
  /**
5
5
  * 创建 load_mcp 工具。
6
- * server 枚举由 filteredMcps (完整 McpServerConfig[])自动推导。
6
+ * server 枚举由 filteredMcps (完整 McpServerConfig[])自动推导,
7
+ * 查找配置也直接使用 filteredMcps,与枚举来源一致。
7
8
  */
8
- export declare function createLoadMcpTool(mcpManager: McpConnectionManager, mcpConfigs: Map<string, {
9
- name: string;
10
- config: McpServerConfig;
11
- }>, filteredMcps: McpServerConfig[]): CallbackTool;
9
+ export declare function createLoadMcpTool(mcpManager: McpConnectionManager, filteredMcps: McpServerConfig[]): CallbackTool;
12
10
  /**
13
11
  * 创建 call_mcp_tool 工具。
14
12
  * 通过官方 Client.callTool() API 调用 MCP 服务器上的工具。
@@ -3,9 +3,15 @@ import { createDisclosureParam } from "../disclosure.js";
3
3
  const EMPTY_HINT = "(当前没有可用的 MCP 服务器,请联系用户添加)";
4
4
  /**
5
5
  * 创建 load_mcp 工具。
6
- * server 枚举由 filteredMcps (完整 McpServerConfig[])自动推导。
6
+ * server 枚举由 filteredMcps (完整 McpServerConfig[])自动推导,
7
+ * 查找配置也直接使用 filteredMcps,与枚举来源一致。
7
8
  */
8
- export function createLoadMcpTool(mcpManager, mcpConfigs, filteredMcps) {
9
+ export function createLoadMcpTool(mcpManager, filteredMcps) {
10
+ // 构建 id→config 查找表
11
+ const configMap = new Map();
12
+ for (const s of filteredMcps) {
13
+ configMap.set(s.id, s);
14
+ }
9
15
  const ids = filteredMcps.map((s) => s.id);
10
16
  const param = createDisclosureParam(ids, "选择一个 MCP 服务器", EMPTY_HINT);
11
17
  return new CallbackTool({
@@ -27,8 +33,8 @@ export function createLoadMcpTool(mcpManager, mcpConfigs, filteredMcps) {
27
33
  },
28
34
  callback: async (input) => {
29
35
  const serverName = input.server;
30
- const serverConfig = mcpConfigs.get(serverName);
31
- if (!serverConfig) {
36
+ const config = configMap.get(serverName);
37
+ if (!config) {
32
38
  return `❌ MCP 服务器 "${serverName}" 不存在`;
33
39
  }
34
40
  const existingManifest = mcpManager.getManifest(serverName);
@@ -38,7 +44,7 @@ export function createLoadMcpTool(mcpManager, mcpConfigs, filteredMcps) {
38
44
  return `✅ MCP 服务器 "${serverName}" 已连接 (${existingManifest.tools.length} 个工具):\n${toolList}`;
39
45
  }
40
46
  try {
41
- const manifest = await mcpManager.connect(serverName, serverConfig.config);
47
+ const manifest = await mcpManager.connect(serverName, config);
42
48
  const toolList = manifest.tools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
43
49
  return `✅ MCP 服务器 "${serverName}" 已连接 (${manifest.tools.length} 个工具):\n${toolList}`;
44
50
  }
@@ -12,6 +12,11 @@ const EMPTY_HINT = "(当前没有可用的 Skill,请联系用户添加)";
12
12
  export function createLoadSkillTool(skillDirs, filteredSkills) {
13
13
  const ids = filteredSkills.map((s) => s.id);
14
14
  const param = createDisclosureParam(ids, "选择一个 Skill", EMPTY_HINT);
15
+ // 拼接各 skill 的描述供 LLM 参考
16
+ const skillDescriptions = filteredSkills
17
+ .map((s) => ` - ${s.id}: ${s.description || "无描述"}`)
18
+ .join("\n");
19
+ const skillIdDescription = `${param.description}\n\n各 Skill 说明:\n${skillDescriptions || " 无可用 Skill"}`;
15
20
  return new CallbackTool({
16
21
  function: {
17
22
  name: "load_skill",
@@ -21,7 +26,7 @@ export function createLoadSkillTool(skillDirs, filteredSkills) {
21
26
  properties: {
22
27
  skill_id: {
23
28
  type: "string",
24
- description: param.description,
29
+ description: skillIdDescription,
25
30
  ...(param.enum ? { enum: param.enum } : {}),
26
31
  },
27
32
  },
@@ -57,6 +62,11 @@ export function createCallSkillSubAgentTool(skillDirs, filteredSkills, caps) {
57
62
  const subAgentSkills = filteredSkills.filter((s) => s.subAgent);
58
63
  const ids = subAgentSkills.map((s) => s.id);
59
64
  const param = createDisclosureParam(ids, "选择一个 Skill(支持子 Agent)", EMPTY_HINT);
65
+ // 拼接各 skill 的描述供 LLM 参考
66
+ const skillDescriptions = subAgentSkills
67
+ .map((s) => ` - ${s.id}: ${s.description || "无描述"}`)
68
+ .join("\n");
69
+ const skillIdDescription = `${param.description}\n\n各 Skill 说明:\n${skillDescriptions || " 无可用 Skill"}`;
60
70
  return new CallbackTool({
61
71
  function: {
62
72
  name: "call_skill_sub_agent",
@@ -66,7 +76,7 @@ export function createCallSkillSubAgentTool(skillDirs, filteredSkills, caps) {
66
76
  properties: {
67
77
  skill_id: {
68
78
  type: "string",
69
- description: param.description,
79
+ description: skillIdDescription,
70
80
  ...(param.enum ? { enum: param.enum } : {}),
71
81
  },
72
82
  task: {
@@ -1,18 +1,21 @@
1
- import { SdkAgent } from "../runtime/SdkAgent.js";
2
1
  import type { AgentPlugin, SendContext } from "../runtime/SdkAgent.js";
2
+ import { SdkAgent } from "../runtime/SdkAgent.js";
3
3
  export interface AutoMigrateOptions {
4
4
  maxTokens: number;
5
5
  migrationAgent: SdkAgent;
6
- onHandoff?: (handoffDoc: string, oldAgent: SdkAgent, newAgent: SdkAgent) => void;
6
+ onHandoff?: (handoffDoc: string, agent: SdkAgent) => void;
7
7
  }
8
8
  /**
9
9
  * 自动迁移插件。
10
10
  *
11
+ * 当 token 使用量超过 maxTokens 时,自动将对话历史交给 migrationAgent 生成交接文档,
12
+ * 然后用交接文档替换当前 agent 的消息列表,实现无缝迁移。
13
+ *
11
14
  * ```ts
12
15
  * agent.use(new AutoMigratePlugin({
13
16
  * maxTokens: 250_000,
14
- * migrationAgent,
15
- * onHandoff: (doc, old, next) => { ... },
17
+ * migrationAgent: anotherAgent,
18
+ * onHandoff: (doc, agent) => { ... },
16
19
  * }));
17
20
  * ```
18
21
  */
@@ -1,16 +1,18 @@
1
1
  import { AgentNS } from "@ai-zen/agents-core";
2
- import { SdkAgent } from "../runtime/SdkAgent.js";
3
2
  import { TaskMigrationService } from "../runtime/TaskMigrationService.js";
4
3
  import { createLogger } from "../shared/logger.js";
5
4
  const log = createLogger();
6
5
  /**
7
6
  * 自动迁移插件。
8
7
  *
8
+ * 当 token 使用量超过 maxTokens 时,自动将对话历史交给 migrationAgent 生成交接文档,
9
+ * 然后用交接文档替换当前 agent 的消息列表,实现无缝迁移。
10
+ *
9
11
  * ```ts
10
12
  * agent.use(new AutoMigratePlugin({
11
13
  * maxTokens: 250_000,
12
- * migrationAgent,
13
- * onHandoff: (doc, old, next) => { ... },
14
+ * migrationAgent: anotherAgent,
15
+ * onHandoff: (doc, agent) => { ... },
14
16
  * }));
15
17
  * ```
16
18
  */
@@ -27,21 +29,19 @@ export class AutoMigratePlugin {
27
29
  return;
28
30
  if (!TaskMigrationService.shouldMigrate(promptTokens, maxTokens))
29
31
  return;
32
+ log.warn(`[autoMigrate] Token 使用量已达 ${promptTokens},超出限制 ${maxTokens},开始自动迁移……`);
30
33
  try {
31
34
  const historyText = this.serializeMessages(agent.messages);
32
35
  const migrationResult = await migrationAgent.send(historyText);
33
36
  const handoffDoc = this.getLastAssistantContent(migrationResult);
34
37
  if (!handoffDoc)
35
38
  return;
36
- const newMessages = [
37
- ...agent.definition.messages,
38
- ...TaskMigrationService.createPostMessages(handoffDoc),
39
- ];
40
- const newAgent = new SdkAgent({ ...agent, messages: newMessages });
41
- ctx.agent = newAgent;
39
+ // 仅替换消息列表,不重建 agent,保留所有引用和插件绑定
40
+ agent.messages.length = 0;
41
+ agent.messages.push(...agent.definition.messages, ...TaskMigrationService.createPostMessages(handoffDoc));
42
42
  if (onHandoff) {
43
43
  try {
44
- await onHandoff(handoffDoc, agent, newAgent);
44
+ await onHandoff(handoffDoc, agent);
45
45
  }
46
46
  catch (err) {
47
47
  log.error(`[autoMigrate] onHandoff 回调失败: ${err?.message ?? err}`);
@@ -11,7 +11,7 @@ export class AutoRefreshToolsPlugin {
11
11
  const { agent } = ctx;
12
12
  if (!agent.caps)
13
13
  return;
14
- agent.caps.refresh();
14
+ agent.caps.refresh({ silent: true });
15
15
  agent.tools = agent.caps.buildTools(agent.permissions ?? {}, {
16
16
  exclude: {
17
17
  subagents: agent.definition.function?.name
@@ -4,6 +4,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
4
4
  const DEFAULT_IDLE_TIMEOUT = {
5
5
  stdio: 30 * 60 * 1000,
6
6
  http: 5 * 60 * 1000,
7
+ sse: 5 * 60 * 1000,
7
8
  };
8
9
  /** 指数退避: 1s → 2s → 4s → 8s → 16s → 30s(封顶) */
9
10
  function backoff(attempt) {
@@ -169,9 +170,10 @@ export class McpConnectionManager {
169
170
  env: config.env,
170
171
  });
171
172
  }
172
- if (config.transport === "http") {
173
+ // http 和 sse 都使用 StreamableHTTPClientTransport
174
+ if (config.transport === "http" || config.transport === "sse") {
173
175
  if (!config.url) {
174
- throw new Error(`MCP HTTP server 缺少 url`);
176
+ throw new Error(`MCP ${config.transport} server 缺少 url`);
175
177
  }
176
178
  return new StreamableHTTPClientTransport(new URL(config.url), {
177
179
  requestInit: config.headers ? { headers: config.headers } : undefined,
@@ -1,5 +1,5 @@
1
- import type { AppConfig, McpServerConfig } from "../types/index.js";
2
- import type { McpConnectionManager } from "./McpConnectionManager.js";
1
+ import type { AppConfig } from "../types/index.js";
2
+ import { McpConnectionManager } from "./McpConnectionManager.js";
3
3
  /**
4
4
  * Provider — 全局上下文实例。
5
5
  *
@@ -19,13 +19,7 @@ export declare class Provider {
19
19
  readonly skillsPaths: string[];
20
20
  readonly toolsPaths: string[];
21
21
  readonly mcpPaths: string[];
22
- readonly conversationsDir: string;
23
- readonly draftsDir: string;
24
- readonly mcpManager?: McpConnectionManager;
25
- readonly mcpConfigs?: Map<string, {
26
- name: string;
27
- config: McpServerConfig;
28
- }>;
22
+ private _mcpManager?;
29
23
  constructor(options: {
30
24
  config: AppConfig;
31
25
  agentsDir: string;
@@ -33,12 +27,10 @@ export declare class Provider {
33
27
  skillsPaths?: string[];
34
28
  toolsPaths?: string[];
35
29
  mcpPaths?: string[];
36
- conversationsDir: string;
37
- draftsDir: string;
38
- mcpManager?: McpConnectionManager;
39
- mcpConfigs?: Map<string, {
40
- name: string;
41
- config: McpServerConfig;
42
- }>;
43
30
  });
31
+ /**
32
+ * 获取 McpConnectionManager,有 mcpPaths 时延迟创建。
33
+ * 首次访问时创建,后续复用同一实例。
34
+ */
35
+ getMcpManager(): McpConnectionManager | undefined;
44
36
  }
@@ -1,3 +1,4 @@
1
+ import { McpConnectionManager } from "./McpConnectionManager.js";
1
2
  /**
2
3
  * Provider — 全局上下文实例。
3
4
  *
@@ -18,11 +19,8 @@ export class Provider {
18
19
  skillsPaths;
19
20
  toolsPaths;
20
21
  mcpPaths;
21
- conversationsDir;
22
- draftsDir;
23
22
  // ---- MCP ----
24
- mcpManager;
25
- mcpConfigs;
23
+ _mcpManager;
26
24
  constructor(options) {
27
25
  this.config = options.config;
28
26
  this.agentsDir = options.agentsDir;
@@ -30,9 +28,15 @@ export class Provider {
30
28
  this.skillsPaths = options.skillsPaths ?? [];
31
29
  this.toolsPaths = options.toolsPaths ?? [];
32
30
  this.mcpPaths = options.mcpPaths ?? [];
33
- this.conversationsDir = options.conversationsDir;
34
- this.draftsDir = options.draftsDir;
35
- this.mcpManager = options.mcpManager;
36
- this.mcpConfigs = options.mcpConfigs;
31
+ }
32
+ /**
33
+ * 获取 McpConnectionManager,有 mcpPaths 时延迟创建。
34
+ * 首次访问时创建,后续复用同一实例。
35
+ */
36
+ getMcpManager() {
37
+ if (!this._mcpManager && this.mcpPaths.length > 0) {
38
+ this._mcpManager = new McpConnectionManager();
39
+ }
40
+ return this._mcpManager;
37
41
  }
38
42
  }
@@ -5,10 +5,16 @@ import type { Capabilities } from "../capabilities/Capabilities.js";
5
5
  import type { Provider } from "./Provider.js";
6
6
  /**
7
7
  * Agent 插件上下文:当前 SdkAgent + 发送内容 + 消息列表。
8
+ *
9
+ * 注意:
10
+ * - messages 是当前 agent 消息数组的**快照**(浅拷贝),插件不应直接修改它。
11
+ * 所有消息变更应通过 agent 上的方法进行。
12
+ * - 如果 onAfterSend 替换了 ctx.agent,send() 方法会使用新 agent 的消息作为返回值。
8
13
  */
9
14
  export interface SendContext {
10
15
  agent: SdkAgent;
11
16
  content: string;
17
+ /** 当前 agent 消息数组的浅拷贝快照,仅供读取,不应直接修改 */
12
18
  messages: AgentNS.Message[];
13
19
  }
14
20
  /**
@@ -95,6 +101,10 @@ export declare class SdkAgent extends Agent {
95
101
  * 1. onBeforeSend — send 外部准备(刷新工具等)
96
102
  * 2. super.send() — 委托 Core Agent,内含内循环及其钩子
97
103
  * 3. onAfterSend — send 外部后处理(保存草稿、迁移等)
104
+ *
105
+ * 注意:
106
+ * - ctx.messages 是当前 agent 消息的**浅拷贝快照**,仅供读取,不应直接修改。
107
+ * 所有消息变更应通过 agent 上的方法进行(如 agent.messages.push 等)。
98
108
  */
99
109
  send(content: string): Promise<AgentNS.Message[]>;
100
110
  }
@@ -72,12 +72,16 @@ export class SdkAgent extends Agent {
72
72
  * 1. onBeforeSend — send 外部准备(刷新工具等)
73
73
  * 2. super.send() — 委托 Core Agent,内含内循环及其钩子
74
74
  * 3. onAfterSend — send 外部后处理(保存草稿、迁移等)
75
+ *
76
+ * 注意:
77
+ * - ctx.messages 是当前 agent 消息的**浅拷贝快照**,仅供读取,不应直接修改。
78
+ * 所有消息变更应通过 agent 上的方法进行(如 agent.messages.push 等)。
75
79
  */
76
80
  async send(content) {
77
81
  const ctx = {
78
82
  agent: this,
79
83
  content,
80
- messages: this.messages,
84
+ messages: [...this.messages],
81
85
  };
82
86
  for (const plugin of this._plugins) {
83
87
  await plugin.onBeforeSend?.(ctx);
@@ -92,12 +96,12 @@ export class SdkAgent extends Agent {
92
96
  await plugin.onInnerLoopEnd?.(ctx);
93
97
  }
94
98
  };
95
- const messages = await super.send(content);
99
+ await super.send(content);
96
100
  this.onInnerLoopStart = undefined;
97
101
  this.onInnerLoopEnd = undefined;
98
102
  for (const plugin of this._plugins) {
99
103
  await plugin.onAfterSend?.(ctx);
100
104
  }
101
- return messages;
105
+ return this.messages;
102
106
  }
103
107
  }
@@ -16,7 +16,7 @@ export class TaskMigrationService {
16
16
  }
17
17
  static createPrompt() {
18
18
  return [
19
- "你是一个任务交接助手。请根据以下对话历史,生成一份结构化交接文档。",
19
+ "根据以下对话历史,生成一篇结构化交接文档。不要做任何解释,只输出交接文档。",
20
20
  "",
21
21
  `文档必须包含以下章节:`,
22
22
  `${TaskMigrationService.HANDOFF_SECTIONS.breakpoint}`,
@@ -36,8 +36,6 @@ export class TaskMigrationService {
36
36
  "",
37
37
  `${TaskMigrationService.HANDOFF_SECTIONS.instructions}`,
38
38
  `- 接手后建议的操作步骤`,
39
- "",
40
- "只输出交接文档本身,不要加额外说明。",
41
39
  ].join("\n");
42
40
  }
43
41
  static createAgentDefinition(options = {}) {
@@ -92,13 +92,13 @@ export interface AppConfig {
92
92
  }
93
93
  /** MCP 连接状态 */
94
94
  export type McpConnectionState = "disconnected" | "connecting" | "connected" | "error";
95
- /** MCP 服务器配置(含 CLI/Desktop 需要的额外字段) */
95
+ /** MCP 服务器配置 */
96
96
  export interface McpServerConfig {
97
97
  id: string;
98
- name: string;
98
+ /** transport 类型,内部统一使用 "stdio" | "http" | "sse" */
99
99
  transport: "stdio" | "http" | "sse";
100
- /** 是否启用 */
101
- enabled?: boolean;
100
+ /** 是否禁用,默认为 false */
101
+ disabled?: boolean;
102
102
  command?: string;
103
103
  args?: string[];
104
104
  env?: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "0.2.0-alpha.3",
3
+ "version": "0.2.2",
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",