hyacinth-ai 0.9.18 → 0.9.20

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.
@@ -25,6 +25,14 @@ export interface SubAgentContext {
25
25
  sandboxRoot?: string;
26
26
  /** 创建带独立 userId 的 Provider(子Agent 之间 + 与主Agent 的 KVCache 隔离) */
27
27
  createSubProvider(userId: string): Provider;
28
+ /** 异步子Agent 结果推送队列(主 loop 的 pendingAsyncResults,完成后自动注入对话) */
29
+ pendingAsyncResults?: Array<{
30
+ handle: string;
31
+ agentName: string;
32
+ status: 'completed' | 'failed';
33
+ result?: string;
34
+ error?: string;
35
+ }>;
28
36
  }
29
37
  /** 注册正在运行的子 Agent loop */
30
38
  export declare function registerRunningLoop(instanceId: string, loop: AgentLoop): void;
@@ -32,6 +40,29 @@ export declare function registerRunningLoop(instanceId: string, loop: AgentLoop)
32
40
  export declare function unregisterRunningLoop(instanceId: string): void;
33
41
  /** 中断指定子 Agent */
34
42
  export declare function interruptSubAgentLoop(instanceId: string): boolean;
43
+ export interface AsyncSubAgentTask {
44
+ handle: string;
45
+ agentName: string;
46
+ instanceId: string;
47
+ task: string;
48
+ status: 'running' | 'completed' | 'failed';
49
+ startTime: string;
50
+ endTime?: string;
51
+ result?: string;
52
+ error?: string;
53
+ }
54
+ /** 注册异步子 Agent 任务,返回 handle */
55
+ export declare function registerAsyncTask(agentName: string, instanceId: string, task: string): string;
56
+ /** 标记异步任务为已完成 */
57
+ export declare function completeAsyncTask(handle: string, result: string): void;
58
+ /** 标记异步任务为失败 */
59
+ export declare function failAsyncTask(handle: string, error: string): void;
60
+ /** 获取所有异步任务列表 */
61
+ export declare function listAsyncTasks(): AsyncSubAgentTask[];
62
+ /** 按 handle 获取异步任务 */
63
+ export declare function getAsyncTask(handle: string): AsyncSubAgentTask | undefined;
64
+ /** 等待所有运行中的异步子 Agent 任务完成(用于优雅关闭) */
65
+ export declare function waitForAsyncTasks(timeoutMs?: number): Promise<void>;
35
66
  /** 销毁子 Agent session 目录(清理 conversation/events/stats/meta) */
36
67
  export declare function destroySubAgentSession(parentSessionDir: string, instanceId: string): Promise<boolean>;
37
68
  /**
@@ -73,6 +104,14 @@ export declare class DelegateToAgentTool implements Tool {
73
104
  type: "string";
74
105
  description: string;
75
106
  };
107
+ async: {
108
+ type: "boolean";
109
+ description: string;
110
+ };
111
+ across_turns: {
112
+ type: "boolean";
113
+ description: string;
114
+ };
76
115
  };
77
116
  required: string[];
78
117
  };
@@ -81,7 +120,14 @@ export declare class DelegateToAgentTool implements Tool {
81
120
  constructor(agentRegistry: AgentRegistry, parentContext: SubAgentContext);
82
121
  execute(args: Record<string, unknown>): Promise<string>;
83
122
  /**
84
- * 委托模式:单个子 Agent 执行任务
123
+ * 异步委托:在后台启动子 Agent,立即返回任务句柄。
124
+ * 子 Agent 的 loop.run() 在后台 Promise 中执行,完成后结果写入 AsyncSubAgentTask。
125
+ */
126
+ private executeAsync;
127
+ /** 后台执行子 Agent 任务 */
128
+ private runAsyncInBackground;
129
+ /**
130
+ * 委托模式:单个子 Agent 执行任务(同步阻塞)
85
131
  */
86
132
  private executeDelegate;
87
133
  /**
@@ -33,6 +33,57 @@ export function interruptSubAgentLoop(instanceId) {
33
33
  loop.interrupt();
34
34
  return true;
35
35
  }
36
+ const asyncTasks = new Map();
37
+ let asyncTaskCounter = 0;
38
+ /** 注册异步子 Agent 任务,返回 handle */
39
+ export function registerAsyncTask(agentName, instanceId, task) {
40
+ const handle = `sub_${String(++asyncTaskCounter).padStart(3, '0')}`;
41
+ asyncTasks.set(handle, {
42
+ handle,
43
+ agentName,
44
+ instanceId,
45
+ task,
46
+ status: 'running',
47
+ startTime: new Date().toISOString(),
48
+ });
49
+ return handle;
50
+ }
51
+ /** 标记异步任务为已完成 */
52
+ export function completeAsyncTask(handle, result) {
53
+ const t = asyncTasks.get(handle);
54
+ if (t) {
55
+ t.status = 'completed';
56
+ t.endTime = new Date().toISOString();
57
+ t.result = result;
58
+ }
59
+ }
60
+ /** 标记异步任务为失败 */
61
+ export function failAsyncTask(handle, error) {
62
+ const t = asyncTasks.get(handle);
63
+ if (t) {
64
+ t.status = 'failed';
65
+ t.endTime = new Date().toISOString();
66
+ t.error = error;
67
+ }
68
+ }
69
+ /** 获取所有异步任务列表 */
70
+ export function listAsyncTasks() {
71
+ return [...asyncTasks.values()];
72
+ }
73
+ /** 按 handle 获取异步任务 */
74
+ export function getAsyncTask(handle) {
75
+ return asyncTasks.get(handle);
76
+ }
77
+ /** 等待所有运行中的异步子 Agent 任务完成(用于优雅关闭) */
78
+ export async function waitForAsyncTasks(timeoutMs = 5000) {
79
+ const start = Date.now();
80
+ while (Date.now() - start < timeoutMs) {
81
+ const running = listAsyncTasks().filter(t => t.status === 'running');
82
+ if (running.length === 0)
83
+ return;
84
+ await new Promise(resolve => setTimeout(resolve, 200));
85
+ }
86
+ }
36
87
  /** 销毁子 Agent session 目录(清理 conversation/events/stats/meta) */
37
88
  export async function destroySubAgentSession(parentSessionDir, instanceId) {
38
89
  const subDir = path.join(parentSessionDir, 'sub-agents', instanceId);
@@ -57,6 +108,11 @@ export async function createSubAgentLoop(agentDef, task, parentContext) {
57
108
  const instanceId = agentDef.instanceId ?? `${agentDef.name}-default`;
58
109
  const subSessionDir = path.join(parentContext.sessionDir, 'sub-agents', instanceId);
59
110
  const ttlMinutes = agentDef.sessionTtlMinutes ?? 10;
111
+ // 并发保护:同一 instanceId 已有活跃 loop 时拒绝,防止会话文件并发写入冲突
112
+ if (runningLoops.has(instanceId)) {
113
+ throw new Error(`子 Agent "${agentDef.name}" (${instanceId}) 正在执行中,不能同时委派第二个任务。` +
114
+ '等待当前任务完成后重试,或使用 spawn_sub_agent 创建独立实例来并行执行。');
115
+ }
60
116
  // TTL 检查:过期则清理重建
61
117
  let isNew = false;
62
118
  try {
@@ -163,6 +219,7 @@ export class DelegateToAgentTool {
163
219
  '"parallel"(并行分工):同时启动多个子 Agent 并行执行,汇总结果。' +
164
220
  '编排场景:当你有一个复杂计划时,自己负责规划和决策,将其中可并行的子任务分别委派给多个子 Agent 同步执行——spawn_sub_agent 可克隆多份实例,配合不同的 instance_id 并发调度,大幅缩短总耗时。' +
165
221
  '会话复用:子 Agent 会话在 TTL 窗口内持久化(默认 10 分钟),相同 instance_id 再次委派时自动恢复完整对话记忆,无需重新交代背景。' +
222
+ '异步执行:设置 async=true 后子 Agent 在后台运行,主 Agent 立即获得任务句柄(如 sub_001)并可继续其他工作。之后用 list_sub_agent_tasks 查看所有异步任务状态,get_sub_agent_result <handle> 获取已完成任务的结果。默认 async=false(同步阻塞,等待结果返回)。' +
166
223
  '使用 list_sub_agents 查看可用 Agent 及其实例 ID,create_sub_agent 创建新 Agent,spawn_sub_agent 克隆以支持并行,destroy_sub_agent 清理不再需要的实例。';
167
224
  inputSchema = {
168
225
  type: 'object',
@@ -183,6 +240,14 @@ export class DelegateToAgentTool {
183
240
  type: 'string',
184
241
  description: '可选的附加上下文(如相关代码片段、文件路径、背景信息)。',
185
242
  },
243
+ async: {
244
+ type: 'boolean',
245
+ description: '是否异步执行。默认 false(阻塞等待结果)。设为 true 时立即返回任务句柄(如 sub_001),子 Agent 在后台执行,主 Agent 可继续其他工具调用。',
246
+ },
247
+ across_turns: {
248
+ type: 'boolean',
249
+ description: '异步结果是否允许跨 turn。默认 false——子Agent完成后结果在当前 turn 内自动注入对话,LLM 立即看到并继续处理。设为 true 时结果不自动注入,需手动用 get_sub_agent_result 获取(适用于需要跨多轮对话的后台任务)。仅在 async=true 时生效。',
250
+ },
186
251
  },
187
252
  required: ['task'],
188
253
  };
@@ -197,6 +262,8 @@ export class DelegateToAgentTool {
197
262
  const agentName = args.agent_name;
198
263
  const task = args.task;
199
264
  const context = args.context;
265
+ const runAsync = args.async === true;
266
+ const acrossTurns = args.across_turns === true;
200
267
  if (!agentName && !instanceId) {
201
268
  return 'Error: either agent_name or instance_id is required. Use list_sub_agents to see available agents and their instance IDs.';
202
269
  }
@@ -219,19 +286,23 @@ export class DelegateToAgentTool {
219
286
  if (!agentDef) {
220
287
  return 'Error: no sub-agent found matching the provided criteria.';
221
288
  }
289
+ // 异步模式下不支持 adversarial 和 parallel(这两种本身就是多Agent协同,异步应逐个个委派)
290
+ if (runAsync && agentDef.collaborationMode !== 'delegate') {
291
+ return `错误:异步模式仅支持 collaborationMode="delegate"。当前模式为 "${agentDef.collaborationMode}"。请用 spawn_sub_agent 克隆独立实例,然后对每个实例分别用 async=true 委派。`;
292
+ }
222
293
  // 构造完整任务描述
223
294
  const fullTask = context ? `${task}\n\nAdditional Context:\n${context}` : task;
224
295
  try {
296
+ if (runAsync) {
297
+ return await this.executeAsync(agentDef, fullTask, acrossTurns);
298
+ }
225
299
  if (agentDef.collaborationMode === 'adversarial') {
226
- // 对抗审查模式:找一个不同角色的子 Agent 同时审查
227
300
  return await this.executeAdversarial(agentDef, fullTask);
228
301
  }
229
302
  else if (agentDef.collaborationMode === 'parallel') {
230
- // 并行分工模式:所有 parallel 模式的子 Agent 并行执行
231
303
  return await this.executeParallel(agentDef, fullTask);
232
304
  }
233
305
  else {
234
- // 委托模式:直接委托给子 Agent
235
306
  return await this.executeDelegate(agentDef, fullTask);
236
307
  }
237
308
  }
@@ -241,7 +312,54 @@ export class DelegateToAgentTool {
241
312
  }
242
313
  }
243
314
  /**
244
- * 委托模式:单个子 Agent 执行任务
315
+ * 异步委托:在后台启动子 Agent,立即返回任务句柄。
316
+ * 子 Agent 的 loop.run() 在后台 Promise 中执行,完成后结果写入 AsyncSubAgentTask。
317
+ */
318
+ async executeAsync(agentDef, task, acrossTurns) {
319
+ const instanceId = agentDef.instanceId;
320
+ const handle = registerAsyncTask(agentDef.name, instanceId, task);
321
+ // 在后台启动子 Agent,不阻塞。catch 兜底防止未处理的 Promise rejection
322
+ this.runAsyncInBackground(handle, agentDef, task, acrossTurns).catch((err) => {
323
+ const message = err instanceof Error ? err.message : String(err);
324
+ failAsyncTask(handle, message);
325
+ if (!acrossTurns) {
326
+ this.parentContext.pendingAsyncResults?.push({ handle, agentName: agentDef.name, status: 'failed', error: message });
327
+ }
328
+ });
329
+ const hint = acrossTurns
330
+ ? `跨 turn 模式——任务完成后需手动用 get_sub_agent_result ${handle} 获取结果。`
331
+ : '回合内模式——子Agent完成后结果将自动注入当前对话。';
332
+ return `异步子 Agent 任务已启动。\n句柄: ${handle}\nAgent: ${agentDef.name} (${instanceId})\n${hint}\n任务: ${task.slice(0, 100)}${task.length > 100 ? '...' : ''}`;
333
+ }
334
+ /** 后台执行子 Agent 任务 */
335
+ async runAsyncInBackground(handle, agentDef, task, acrossTurns) {
336
+ const instanceId = agentDef.instanceId;
337
+ try {
338
+ const { loop, sessionDir, isNew } = await createSubAgentLoop(agentDef, task, this.parentContext);
339
+ registerRunningLoop(instanceId, loop);
340
+ if (!isNew) {
341
+ await fs.utimes(path.join(sessionDir, 'meta.json'), new Date(), new Date()).catch(() => { });
342
+ }
343
+ await loop.run(task);
344
+ unregisterRunningLoop(instanceId);
345
+ const result = await this.collectResult(agentDef, task);
346
+ completeAsyncTask(handle, result);
347
+ if (!acrossTurns) {
348
+ // 回合内模式:推送到主 loop 队列,当前 turn 自动注入结果
349
+ this.parentContext.pendingAsyncResults?.push({ handle, agentName: agentDef.name, status: 'completed', result });
350
+ }
351
+ }
352
+ catch (error) {
353
+ unregisterRunningLoop(instanceId);
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ failAsyncTask(handle, message);
356
+ if (!acrossTurns) {
357
+ this.parentContext.pendingAsyncResults?.push({ handle, agentName: agentDef.name, status: 'failed', error: message });
358
+ }
359
+ }
360
+ }
361
+ /**
362
+ * 委托模式:单个子 Agent 执行任务(同步阻塞)
245
363
  */
246
364
  async executeDelegate(agentDef, task) {
247
365
  const instanceId = agentDef.instanceId;
@@ -424,12 +424,15 @@ export async function createAgent(options, supervisor) {
424
424
  for (const agent of agents) {
425
425
  agentRegistry.register(agent);
426
426
  }
427
+ // 异步子Agent 结果队列——先创建引用,后续赋给 loop
428
+ const pendingAsyncResults = [];
427
429
  const delegateTool = new DelegateToAgentTool(agentRegistry, {
428
430
  modelRouter,
429
431
  toolRegistry,
430
432
  sessionDir,
431
433
  maxContextTokens: maxContext,
432
434
  dependencyAnalyzer,
435
+ pendingAsyncResults,
433
436
  createSubProvider: (userId) => {
434
437
  if (!subProviderApiKey || !subProviderBaseType) {
435
438
  throw new Error('Cannot create sub-agent provider: main provider not configured.');
@@ -517,6 +520,7 @@ export async function createAgent(options, supervisor) {
517
520
  const loop = new AgentLoop(provider, contextComposer, compressor, orchestrator, toolExecutor, toolRegistry, conversationStore, eventStore, statsManager, sessionDir, summaryStore, effectiveMaxTurns, maxContext, outputHandler, skillRegistry, mcpSystem.getBridge(), dependencyAnalyzer, agentRegistry, effectivePersonaDir, flowRegistry, providerRouter, new Set(config.safety?.dangerousTools ?? ['write', 'bash']), new Set(), configCenter, modelRouter, turnRecorder);
518
521
  // 注入知识库状态引用
519
522
  loop.kbState = kbState;
523
+ loop.pendingAsyncResults = pendingAsyncResults; // 异步子Agent结果队列(和 delegateTool 共享引用)
520
524
  loopRef = loop; // wire fallback notification
521
525
  // 注入旁路 Provider 引用(供模式切换时 setBypassUserId 使用)
522
526
  if (bypassProvider)
@@ -1,5 +1,6 @@
1
1
  import { ProcessManager } from './manager.js';
2
2
  import { LocalModelManager } from './local-model.js';
3
+ import { waitForAsyncTasks } from '../agents/delegate-tool.js';
3
4
  /**
4
5
  * LifecycleSupervisor — 统一管理所有受管进程的生命周期。
5
6
  *
@@ -184,6 +185,8 @@ export class LifecycleSupervisor {
184
185
  if (this.backgroundRegistry) {
185
186
  await this.backgroundRegistry.shutdownAll().catch(() => { });
186
187
  }
188
+ // 等待异步子 Agent 任务完成(最多等 5 秒)
189
+ await waitForAsyncTasks(5000).catch(() => { });
187
190
  const results = this.entities.map((e) => e.manager.stop().catch(() => {
188
191
  // 单个关闭失败不影响其他
189
192
  }));
@@ -151,6 +151,14 @@ export declare class AgentLoop {
151
151
  name: string;
152
152
  firedAt: string;
153
153
  }>;
154
+ /** 异步子Agent 完成后的结果队列(delegate-tool 写入,主循环消费) */
155
+ pendingAsyncResults: Array<{
156
+ handle: string;
157
+ agentName: string;
158
+ status: 'completed' | 'failed';
159
+ result?: string;
160
+ error?: string;
161
+ }>;
154
162
  /** 当前正在执行的定时任务名(供 TUI 显示上下文),run 前设置,run 后清除 */
155
163
  pendingTaskName: string | null;
156
164
  /** 知识库状态引用(factory 注入) */
@@ -203,6 +203,8 @@ export class AgentLoop {
203
203
  schedulerInitialized = false;
204
204
  /** 定时任务触发后待注入对话的通知 */
205
205
  pendingTaskNotifications = [];
206
+ /** 异步子Agent 完成后的结果队列(delegate-tool 写入,主循环消费) */
207
+ pendingAsyncResults = [];
206
208
  /** 当前正在执行的定时任务名(供 TUI 显示上下文),run 前设置,run 后清除 */
207
209
  pendingTaskName = null;
208
210
  /** 知识库状态引用(factory 注入) */
@@ -1159,6 +1161,23 @@ export class AgentLoop {
1159
1161
  turnCount++;
1160
1162
  if (result.toolCalled)
1161
1163
  toolWasCalled = true;
1164
+ // ── 异步子Agent 结果回合内注入 ─────────────────────────
1165
+ // delegate-tool 中异步任务完成后会将结果推送到此队列。
1166
+ // 本轮迭代结束后检查:有已完成的结果→注入对话→强制继续迭代,
1167
+ // 让 LLM 在当前 turn 内拿到结果并做出反应,无需跨 turn 手动查。
1168
+ if (this.pendingAsyncResults.length > 0) {
1169
+ const results = this.pendingAsyncResults.splice(0);
1170
+ for (const r of results) {
1171
+ const content = r.status === 'completed'
1172
+ ? `[异步子Agent ${r.handle} (${r.agentName}) 已完成]\n${r.result ?? ''}`
1173
+ : `[异步子Agent ${r.handle} (${r.agentName}) 执行失败]\n${r.error ?? ''}`;
1174
+ await this.conversationStore.append(this.sessionDir, {
1175
+ role: 'user',
1176
+ content: [{ type: 'text', text: content }],
1177
+ });
1178
+ }
1179
+ result.stop = false; // 强制继续迭代,让 LLM 看到注入的异步结果
1180
+ }
1162
1181
  // 更新 stats
1163
1182
  await this.statsManager.increment(this.sessionDir, 'turn_count', 1);
1164
1183
  // ── 旁路Agent 迭代审查(每次 LLM 回复后) ──────────
@@ -3,7 +3,7 @@ import { getActiveRouterName } from '../context/profiles.js';
3
3
  import path from 'node:path';
4
4
  import fs from 'node:fs';
5
5
  import { createGetConfigTool, createUpdateConfigTool, createConfigSchemaTool, createResetConfigTool, } from '../tools/config.js';
6
- import { createSwitchProviderTool, createListProvidersTool, createProviderInfoTool, createSwitchToAutoRouteTool, createToggleToolTool, createListToolsTool, createToggleSkillTool, createListSkillsTool, createToggleSubAgentTool, createListSubAgentsTool, createSpawnSubAgentTool, createCreateSubAgentTool, createUpdateSubAgentTool, createInterruptTool, createSessionStatsTool, createCurrentSessionTool, createListSessionsTool, createNewSessionTool, createSwitchSessionTool, createDeleteSessionTool, createAllowToolTool, createDisallowToolTool, createListAllowlistTool, createAddTaskTool, createRemoveTaskTool, createListTasksTool, createToggleTaskTool, createMcpStatusTool, createListModelChannelsTool, createAddModelChannelTool, createRemoveModelChannelTool, createSetChannelRoleTool, createSetChannelModelTool, createResetChannelModelTool, createChannelInfoTool, } from '../tools/runtime-control.js';
6
+ import { createSwitchProviderTool, createListProvidersTool, createProviderInfoTool, createSwitchToAutoRouteTool, createToggleToolTool, createListToolsTool, createToggleSkillTool, createListSkillsTool, createToggleSubAgentTool, createListSubAgentsTool, createSpawnSubAgentTool, createCreateSubAgentTool, createUpdateSubAgentTool, createListSubAgentTasksTool, createGetSubAgentResultTool, createInterruptTool, createSessionStatsTool, createCurrentSessionTool, createListSessionsTool, createNewSessionTool, createSwitchSessionTool, createDeleteSessionTool, createAllowToolTool, createDisallowToolTool, createListAllowlistTool, createAddTaskTool, createRemoveTaskTool, createListTasksTool, createToggleTaskTool, createMcpStatusTool, createListModelChannelsTool, createAddModelChannelTool, createRemoveModelChannelTool, createSetChannelRoleTool, createSetChannelModelTool, createResetChannelModelTool, createChannelInfoTool, } from '../tools/runtime-control.js';
7
7
  /**
8
8
  * 工具注册表
9
9
  * 负责注册、查询工具,以及生成 LLM 格式的工具定义
@@ -110,6 +110,9 @@ export class ToolRegistry extends GenericRegistry {
110
110
  this.register(createSpawnSubAgentTool(agentRegistry));
111
111
  this.register(createCreateSubAgentTool(agentRegistry, cwd));
112
112
  this.register(createUpdateSubAgentTool(agentRegistry));
113
+ // ── 异步子 Agent 任务工具 (2) ─────────────────────────────────
114
+ this.register(createListSubAgentTasksTool());
115
+ this.register(createGetSubAgentResultTool());
113
116
  // destroy_sub_agent needs factory.ts sessionDir; register in factory.ts after loop is created
114
117
  // (handled by importing and registering createDestroySubAgentTool directly in factory.ts)
115
118
  // ── Session control tools (7) ────────────────────────
@@ -51,6 +51,14 @@ export declare function createToggleSubAgentTool(agentRegistry: AgentRegistry, c
51
51
  * list_sub_agents — list all registered sub-agents with their enabled/disabled status and instance IDs.
52
52
  */
53
53
  export declare function createListSubAgentsTool(agentRegistry: any): Tool;
54
+ /**
55
+ * list_sub_agent_tasks — 列出所有异步子 Agent 任务及其状态。
56
+ */
57
+ export declare function createListSubAgentTasksTool(): Tool;
58
+ /**
59
+ * get_sub_agent_result — 获取异步子 Agent 任务的执行结果。
60
+ */
61
+ export declare function createGetSubAgentResultTool(): Tool;
54
62
  /**
55
63
  * spawn_sub_agent — clone an existing sub-agent to create a parallel instance (分身).
56
64
  */
@@ -1,5 +1,6 @@
1
1
  import { clearPromptCache } from '../prompts/loader.js';
2
2
  import { switchRouter } from '../context/profiles.js';
3
+ import { listAsyncTasks, getAsyncTask } from '../agents/delegate-tool.js';
3
4
  import fs from 'node:fs';
4
5
  import path from 'node:path';
5
6
  import os from 'node:os';
@@ -359,6 +360,69 @@ export function createListSubAgentsTool(agentRegistry) {
359
360
  },
360
361
  };
361
362
  }
363
+ // ── 异步子 Agent 任务管理工具 ──
364
+ /**
365
+ * list_sub_agent_tasks — 列出所有异步子 Agent 任务及其状态。
366
+ */
367
+ export function createListSubAgentTasksTool() {
368
+ return {
369
+ name: 'list_sub_agent_tasks',
370
+ description: '列出所有通过 delegate_to_agent(async=true) 启动的异步子 Agent 任务。包含句柄、Agent 名称、任务描述、状态(running/completed/failed)、启动时间和结果摘要。',
371
+ inputSchema: { type: 'object', properties: {} },
372
+ async execute(_args) {
373
+ const tasks = listAsyncTasks();
374
+ if (tasks.length === 0) {
375
+ return '暂无异步子 Agent 任务。使用 delegate_to_agent 并设置 async=true 来启动异步任务。';
376
+ }
377
+ const lines = [`异步子 Agent 任务(共 ${tasks.length} 个):`];
378
+ for (const t of tasks) {
379
+ const statusLabel = t.status === 'running' ? '执行中' : t.status === 'completed' ? '已完成' : '失败';
380
+ const statusIcon = t.status === 'running' ? '🔄' : t.status === 'completed' ? '✅' : '❌';
381
+ lines.push(` ${statusIcon} ${t.handle} [${statusLabel}] ${t.agentName} (instance: ${t.instanceId}): ${t.task.slice(0, 60)}${t.task.length > 60 ? '...' : ''}`);
382
+ if (t.status !== 'running' && t.result) {
383
+ const summary = t.result.split('\n').find(l => l.trim())?.slice(0, 80) ?? '';
384
+ lines.push(` ↳ ${summary}${summary.length >= 80 ? '...' : ''}`);
385
+ }
386
+ }
387
+ lines.push('');
388
+ lines.push('使用 get_sub_agent_result <handle> 获取已完成任务的完整结果。');
389
+ return lines.join('\n');
390
+ },
391
+ };
392
+ }
393
+ /**
394
+ * get_sub_agent_result — 获取异步子 Agent 任务的执行结果。
395
+ */
396
+ export function createGetSubAgentResultTool() {
397
+ return {
398
+ name: 'get_sub_agent_result',
399
+ description: '获取指定异步子 Agent 任务的执行结果。running 时返回仍在执行中,completed 时返回完整结果,failed 时返回错误信息。句柄从 list_sub_agent_tasks 获取。',
400
+ inputSchema: {
401
+ type: 'object',
402
+ properties: {
403
+ handle: {
404
+ type: 'string',
405
+ description: '异步任务句柄(如 sub_001),从 list_sub_agent_tasks 获取。',
406
+ },
407
+ },
408
+ required: ['handle'],
409
+ },
410
+ async execute(args) {
411
+ const handle = args.handle;
412
+ const task = getAsyncTask(handle);
413
+ if (!task) {
414
+ return `错误:未找到异步任务 "${handle}"。使用 list_sub_agent_tasks 查看所有任务及其句柄。`;
415
+ }
416
+ if (task.status === 'running') {
417
+ return `任务 ${handle}(${task.agentName})仍在执行中。任务:${task.task.slice(0, 100)}${task.task.length > 100 ? '...' : ''}\n启动时间:${task.startTime}\n请稍后再次调用 get_sub_agent_result 查询。`;
418
+ }
419
+ if (task.status === 'failed') {
420
+ return `任务 ${handle}(${task.agentName})执行失败。\n错误:${task.error}\n任务:${task.task}`;
421
+ }
422
+ return `任务 ${handle}(${task.agentName})已完成。\n\n${task.result}`;
423
+ },
424
+ };
425
+ }
362
426
  /**
363
427
  * spawn_sub_agent — clone an existing sub-agent to create a parallel instance (分身).
364
428
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyacinth-ai",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
4
4
  "description": "Hyacinth 🪻 — multi-provider AI agent with tool system and context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",