multiclaws 0.4.32 → 0.4.34

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/dist/index.js CHANGED
@@ -234,6 +234,35 @@ function createTools(getService, logger) {
234
234
  }
235
235
  },
236
236
  };
237
+ const multiclawsNotify = {
238
+ name: "multiclaws_notify",
239
+ description: "Send a notification message to the local user's WebUI. " +
240
+ "Used by sub-agents to deliver delegation results back to the user. " +
241
+ "Broadcasts to all known channels so the user sees the message regardless of which channel they are on.",
242
+ parameters: {
243
+ type: "object",
244
+ additionalProperties: false,
245
+ properties: {
246
+ message: { type: "string", description: "The message to send to the user." },
247
+ },
248
+ required: ["message"],
249
+ },
250
+ execute: async (_toolCallId, args) => {
251
+ const msg = typeof args.message === "string" ? args.message.trim() : "";
252
+ log("info", `tool:multiclaws_notify(len=${msg.length})`);
253
+ try {
254
+ const service = requireService(getService());
255
+ if (!msg)
256
+ throw new Error("message is required");
257
+ await service.notifyUser(msg);
258
+ return textResult("Notification sent.");
259
+ }
260
+ catch (err) {
261
+ log("error", `tool:multiclaws_notify failed: ${err instanceof Error ? err.message : String(err)}`);
262
+ throw err;
263
+ }
264
+ },
265
+ };
237
266
  const multiclawsTaskStatus = {
238
267
  name: "multiclaws_task_status",
239
268
  description: "Check the status of a delegated task.",
@@ -471,6 +500,7 @@ function createTools(getService, logger) {
471
500
  multiclawsDelegate,
472
501
  multiclawsDelegateSend,
473
502
  multiclawsA2ACallback,
503
+ multiclawsNotify,
474
504
  multiclawsTaskStatus,
475
505
  multiclawsTeamCreate,
476
506
  multiclawsTeamJoin,
@@ -595,16 +625,25 @@ const plugin = {
595
625
  api.on("gateway_stop", () => {
596
626
  structured.logger.info("[multiclaws] gateway_stop observed");
597
627
  });
598
- // Collect notification targets from incoming messages (external channels)
628
+ // Collect notification targets from incoming messages
599
629
  api.on("message_received", (_event, ctx) => {
600
- if (service && ctx.channelId && ctx.channelId !== "webchat" && ctx.conversationId) {
630
+ if (!service || !ctx.channelId)
631
+ return;
632
+ if (ctx.channelId === "webchat" && ctx.conversationId) {
633
+ // WebChat: use conversationId with the message tool
634
+ service.addNotificationTarget(`webchat:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
635
+ }
636
+ else if (ctx.channelId !== "webchat" && ctx.conversationId) {
637
+ // External channels (Telegram, etc.)
601
638
  service.addNotificationTarget(`${ctx.channelId}:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
602
639
  }
603
640
  });
604
641
  // Inject onboarding prompt when profile is pending first-run setup
605
- // Also capture web session targets for notifications
642
+ // Also capture web session targets for notifications (skip internal sub-agent sessions)
643
+ const INTERNAL_SESSION_PREFIXES = ["delegate-", "a2a-"];
606
644
  api.on("before_prompt_build", async (_event, ctx) => {
607
- if (service && ctx.sessionKey) {
645
+ if (service && ctx.sessionKey &&
646
+ !INTERNAL_SESSION_PREFIXES.some((p) => ctx.sessionKey.startsWith(p))) {
608
647
  service.addNotificationTarget(`web:${ctx.sessionKey}`, { type: "web", sessionKey: ctx.sessionKey });
609
648
  }
610
649
  if (!service)
@@ -37,33 +37,43 @@ class OpenClawAgentExecutor {
37
37
  async execute(context, eventBus) {
38
38
  const taskText = extractTextFromMessage(context.userMessage);
39
39
  const taskId = context.taskId;
40
+ this.logger.info(`[a2a-adapter] ▶ execute() called — taskId=${taskId}, textLen=${taskText.length}`);
40
41
  if (!taskText.trim()) {
42
+ this.logger.warn(`[a2a-adapter] ✗ empty task text, rejecting — taskId=${taskId}`);
41
43
  this.publishMessage(eventBus, "Error: empty task received.");
42
44
  eventBus.finished();
43
45
  return;
44
46
  }
45
- const fromAgent = context.userMessage.metadata?.agentUrl ?? "unknown";
47
+ const meta = context.userMessage.metadata ?? {};
48
+ const fromAgentUrl = meta.agentUrl ?? "unknown";
49
+ const fromAgentName = meta.agentName || fromAgentUrl;
50
+ this.logger.info(`[a2a-adapter] task ${taskId} from ${fromAgentName} (${fromAgentUrl}): ${taskText.slice(0, 120)}`);
46
51
  this.taskTracker.create({
47
- fromPeerId: fromAgent,
52
+ fromPeerId: fromAgentUrl,
48
53
  toPeerId: "local",
49
54
  task: taskText,
50
55
  });
56
+ this.logger.info(`[a2a-adapter] task ${taskId} tracked`);
51
57
  if (!this.gatewayConfig) {
52
- this.logger.error("[a2a-adapter] gateway config not available, cannot execute task");
58
+ this.logger.error(`[a2a-adapter] gateway config not available taskId=${taskId}`);
53
59
  this.taskTracker.update(taskId, { status: "failed", error: "gateway config not available" });
54
60
  this.publishMessage(eventBus, "Error: gateway config not available, cannot execute task.");
55
61
  eventBus.finished();
56
62
  return;
57
63
  }
58
64
  // Notify local user about incoming task
59
- void this.notifyUser(`📨 收到来自 **${fromAgent}** 的委派任务:${taskText.slice(0, 200)}`);
65
+ const notifyTargets = this.getNotificationTargets();
66
+ this.logger.info(`[a2a-adapter] task ${taskId} notifying user (${notifyTargets.size} targets)`);
67
+ void this.notifyUser(`📨 收到来自 **${fromAgentName}** 的委派任务:${taskText.slice(0, 200)}`);
60
68
  try {
61
- this.logger.info(`[a2a-adapter] executing task ${taskId}: ${taskText.slice(0, 100)}`);
62
69
  // Create a promise that resolves when sub-agent calls multiclaws_a2a_callback
63
- const resultPromise = this.createCallback(taskId, 180_000);
70
+ const timeoutMs = 180_000;
71
+ const resultPromise = this.createCallback(taskId, timeoutMs);
72
+ this.logger.info(`[a2a-adapter] task ${taskId} callback registered (timeout=${timeoutMs / 1000}s)`);
64
73
  // Spawn the subagent with instructions to call back when done
65
74
  const prompt = buildA2ASubagentPrompt(taskId, taskText);
66
- await (0, gateway_client_1.invokeGatewayTool)({
75
+ this.logger.info(`[a2a-adapter] task ${taskId} spawning sub-agent via sessions_spawn (cwd=${this.cwd}, sessionKey=a2a-${taskId})`);
76
+ const spawnResult = await (0, gateway_client_1.invokeGatewayTool)({
67
77
  gateway: this.gatewayConfig,
68
78
  tool: "sessions_spawn",
69
79
  args: {
@@ -74,20 +84,22 @@ class OpenClawAgentExecutor {
74
84
  sessionKey: `a2a-${taskId}`,
75
85
  timeoutMs: 15_000,
76
86
  });
77
- this.logger.info(`[a2a-adapter] task ${taskId} spawned, waiting for callback...`);
87
+ this.logger.info(`[a2a-adapter] task ${taskId} sub-agent spawned result=${JSON.stringify(spawnResult).slice(0, 200)}`);
88
+ this.logger.info(`[a2a-adapter] task ${taskId} waiting for callback from sub-agent...`);
78
89
  // Wait for the sub-agent to call back
79
90
  const output = await resultPromise;
80
91
  // Return result
81
92
  this.taskTracker.update(taskId, { status: "completed", result: output });
82
- this.logger.info(`[a2a-adapter] task ${taskId} completed, resultLen=${output.length}`);
93
+ this.logger.info(`[a2a-adapter] task ${taskId} completed resultLen=${output.length}, preview=${output.slice(0, 120)}`);
83
94
  this.publishMessage(eventBus, output || "Task completed with no output.");
84
95
  }
85
96
  catch (err) {
86
97
  const errorMsg = err instanceof Error ? err.message : String(err);
87
- this.logger.error(`[a2a-adapter] task execution failed: ${errorMsg}`);
98
+ this.logger.error(`[a2a-adapter] task ${taskId} failed: ${errorMsg}`);
88
99
  this.taskTracker.update(taskId, { status: "failed", error: errorMsg });
89
100
  this.publishMessage(eventBus, `Error: ${errorMsg}`);
90
101
  }
102
+ this.logger.info(`[a2a-adapter] task ${taskId} eventBus.finished()`);
91
103
  eventBus.finished();
92
104
  }
93
105
  /**
@@ -96,10 +108,13 @@ class OpenClawAgentExecutor {
96
108
  */
97
109
  resolveCallback(taskId, result) {
98
110
  const pending = this.pendingCallbacks.get(taskId);
99
- if (!pending)
111
+ if (!pending) {
112
+ this.logger.warn(`[a2a-adapter] resolveCallback: no pending callback for taskId=${taskId} (may have timed out)`);
100
113
  return false;
114
+ }
101
115
  clearTimeout(pending.timer);
102
116
  this.pendingCallbacks.delete(taskId);
117
+ this.logger.info(`[a2a-adapter] resolveCallback: taskId=${taskId} resolved — resultLen=${result.length}`);
103
118
  pending.resolve(result);
104
119
  return true;
105
120
  }
@@ -111,6 +126,7 @@ class OpenClawAgentExecutor {
111
126
  clearTimeout(pending.timer);
112
127
  this.pendingCallbacks.delete(taskId);
113
128
  pending.reject(new Error("canceled"));
129
+ this.logger.info(`[a2a-adapter] cancelTask: pending callback rejected for taskId=${taskId}`);
114
130
  }
115
131
  this.taskTracker.update(taskId, { status: "failed", error: "canceled" });
116
132
  this.publishMessage(eventBus, "Task was canceled.");
@@ -127,6 +143,7 @@ class OpenClawAgentExecutor {
127
143
  return new Promise((resolve, reject) => {
128
144
  const timer = setTimeout(() => {
129
145
  this.pendingCallbacks.delete(taskId);
146
+ this.logger.error(`[a2a-adapter] ✗ task ${taskId} callback timed out after ${timeoutMs / 1000}s — pending callbacks remaining: ${this.pendingCallbacks.size}`);
130
147
  reject(new Error(`task timed out after ${timeoutMs / 1000}s waiting for sub-agent callback`));
131
148
  }, timeoutMs);
132
149
  this.pendingCallbacks.set(taskId, { resolve, reject, timer });
@@ -135,21 +152,36 @@ class OpenClawAgentExecutor {
135
152
  /** Send a notification to all known targets. Individual failures are silently ignored. */
136
153
  async notifyUser(message) {
137
154
  const targets = this.getNotificationTargets();
138
- if (!this.gatewayConfig || targets.size === 0)
155
+ if (!this.gatewayConfig || targets.size === 0) {
156
+ this.logger.info(`[a2a-adapter] notifyUser: skipped (gateway=${!!this.gatewayConfig}, targets=${targets.size})`);
139
157
  return;
140
- await Promise.allSettled([...targets.values()].map((target) => target.type === "channel"
141
- ? (0, gateway_client_1.invokeGatewayTool)({
142
- gateway: this.gatewayConfig,
143
- tool: "message",
144
- args: { action: "send", target: target.conversationId, message },
145
- timeoutMs: 5_000,
146
- })
147
- : (0, gateway_client_1.invokeGatewayTool)({
148
- gateway: this.gatewayConfig,
149
- tool: "chat.send",
150
- args: { sessionKey: target.sessionKey, message },
151
- timeoutMs: 5_000,
152
- })));
158
+ }
159
+ const results = await Promise.allSettled([...targets.entries()].map(async ([key, target]) => {
160
+ this.logger.info(`[a2a-adapter] notifyUser: sending to ${key} (type=${target.type})`);
161
+ try {
162
+ await (target.type === "channel"
163
+ ? (0, gateway_client_1.invokeGatewayTool)({
164
+ gateway: this.gatewayConfig,
165
+ tool: "message",
166
+ args: { action: "send", target: target.conversationId, message },
167
+ timeoutMs: 5_000,
168
+ })
169
+ : (0, gateway_client_1.invokeGatewayTool)({
170
+ gateway: this.gatewayConfig,
171
+ tool: "chat.send",
172
+ args: { sessionKey: target.sessionKey, message },
173
+ timeoutMs: 5_000,
174
+ }));
175
+ this.logger.info(`[a2a-adapter] notifyUser: sent to ${key} ✓`);
176
+ }
177
+ catch (err) {
178
+ this.logger.warn(`[a2a-adapter] notifyUser: failed to send to ${key}: ${err instanceof Error ? err.message : String(err)}`);
179
+ throw err;
180
+ }
181
+ }));
182
+ const ok = results.filter((r) => r.status === "fulfilled").length;
183
+ const fail = results.filter((r) => r.status === "rejected").length;
184
+ this.logger.info(`[a2a-adapter] notifyUser: done (${ok} ok, ${fail} failed)`);
153
185
  }
154
186
  publishMessage(eventBus, text) {
155
187
  const message = {
@@ -133,10 +133,10 @@ export declare class MulticlawsService extends EventEmitter {
133
133
  private extractArtifactText;
134
134
  /** Fetch with up to 2 retries and exponential backoff. */
135
135
  private fetchWithRetry;
136
- /** Register a channel ID for notifications. */
136
+ /** Resolve a pending A2A callback from sub-agent. */
137
137
  resolveA2ACallback(taskId: string, result: string): boolean;
138
138
  addNotificationTarget(key: string, target: NotificationTarget): void;
139
139
  /** Send a notification to all known targets. Individual failures are silently ignored. */
140
- private notifyUser;
140
+ notifyUser(message: string): Promise<void>;
141
141
  private log;
142
142
  }
@@ -38,14 +38,15 @@ function buildDelegationPrompt(agent, task) {
38
38
 
39
39
  ## 执行步骤
40
40
  1. 调用 multiclaws_delegate_send(agentUrl="${agent.url}", task="${task.replace(/"/g, '\\"')}") 发送任务
41
- 2. 收到回复后,用 message 工具将结果汇报给用户
41
+ 2. 收到回复后,调用 multiclaws_notify(message="结果内容") 将结果推送给用户
42
42
  3. 如果需要进一步沟通,可再次调用 multiclaws_delegate_send(最多 5 轮)
43
- 4. 每次收到回复后立即用 message 汇报进展
43
+ 4. 每次收到回复后立即调用 multiclaws_notify 推送进展
44
44
 
45
45
  ## 规则
46
46
  - 使用 multiclaws_delegate_send(不是 multiclaws_delegate)发送任务
47
+ - 使用 multiclaws_notify(不是 message)将结果推送给用户
47
48
  - 最多 5 轮沟通
48
- - 遇到错误时在汇报中说明原因`;
49
+ - 遇到错误时在 multiclaws_notify 中说明失败原因`;
49
50
  }
50
51
  /* ------------------------------------------------------------------ */
51
52
  /* Service */
@@ -255,26 +256,32 @@ class MulticlawsService extends node_events_1.EventEmitter {
255
256
  /* Task delegation */
256
257
  /* ---------------------------------------------------------------- */
257
258
  async delegateTask(params) {
258
- this.log("info", `delegateTask(agentUrl=${params.agentUrl}, task=${params.task.slice(0, 80)})`);
259
+ this.log("info", `[delegate] ▶ delegateTask(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
260
+ this.log("info", `[delegate] task preview: ${params.task.slice(0, 120)}`);
259
261
  await this.requireCompleteProfile();
260
262
  const agentRecord = await this.agentRegistry.get(params.agentUrl);
261
263
  if (!agentRecord) {
262
- this.log("warn", `delegateTask: unknown agent ${params.agentUrl}`);
264
+ this.log("warn", `[delegate] unknown agent: ${params.agentUrl}`);
263
265
  return { status: "failed", error: `unknown agent: ${params.agentUrl}` };
264
266
  }
267
+ this.log("info", `[delegate] agent found: ${agentRecord.name} (${agentRecord.url})`);
265
268
  const track = this.taskTracker.create({
266
269
  fromPeerId: "local",
267
270
  toPeerId: params.agentUrl,
268
271
  task: params.task,
269
272
  });
270
273
  this.taskTracker.update(track.taskId, { status: "running" });
274
+ this.log("info", `[delegate] task tracked: ${track.taskId}, status=running`);
271
275
  try {
276
+ this.log("info", `[delegate] ${track.taskId} creating A2A client for ${agentRecord.url}`);
272
277
  const client = await this.createA2AClient(agentRecord);
278
+ this.log("info", `[delegate] ${track.taskId} A2A client created, starting fire-and-forget send`);
273
279
  // Fire-and-forget execution: keep running in the background so that
274
280
  // the gateway call can return quickly and the task can outlive
275
281
  // the gateway's HTTP timeout.
276
282
  void (async () => {
277
283
  try {
284
+ this.log("info", `[delegate] ${track.taskId} sending A2A message (background)...`);
278
285
  const result = await client.sendMessage({
279
286
  message: {
280
287
  kind: "message",
@@ -283,22 +290,24 @@ class MulticlawsService extends node_events_1.EventEmitter {
283
290
  messageId: track.taskId,
284
291
  },
285
292
  });
293
+ this.log("info", `[delegate] ${track.taskId} A2A response received (background)`);
286
294
  this.processTaskResult(track.taskId, result);
287
295
  }
288
296
  catch (err) {
289
297
  const errorMsg = err instanceof Error ? err.message : String(err);
290
298
  this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
291
- this.log("warn", `delegateTask background execution for ${track.taskId} failed: ${errorMsg}`);
299
+ this.log("error", `[delegate] ${track.taskId} background send failed: ${errorMsg}`);
292
300
  }
293
301
  })();
294
302
  // Return immediately so that gateway tool invocations are fast and
295
303
  // do not depend on the remote agent's total execution time.
304
+ this.log("info", `[delegate] ${track.taskId} returned immediately (fire-and-forget)`);
296
305
  return { taskId: track.taskId, status: "running" };
297
306
  }
298
307
  catch (err) {
299
308
  const errorMsg = err instanceof Error ? err.message : String(err);
300
309
  this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
301
- this.log("error", `delegateTask failed for ${track.taskId}: ${errorMsg}`);
310
+ this.log("error", `[delegate] ${track.taskId} failed: ${errorMsg}`);
302
311
  return { taskId: track.taskId, status: "failed", error: errorMsg };
303
312
  }
304
313
  }
@@ -307,37 +316,47 @@ class MulticlawsService extends node_events_1.EventEmitter {
307
316
  * Used by sub-agents internally via the multiclaws_delegate_send tool.
308
317
  */
309
318
  async delegateTaskSync(params) {
310
- this.log("info", `delegateTaskSync(agentUrl=${params.agentUrl}, task=${params.task.slice(0, 80)})`);
319
+ this.log("info", `[delegate-sync] ▶ delegateTaskSync(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
320
+ this.log("info", `[delegate-sync] task preview: ${params.task.slice(0, 120)}`);
311
321
  await this.requireCompleteProfile();
312
322
  const agentRecord = await this.agentRegistry.get(params.agentUrl);
313
323
  if (!agentRecord) {
314
- this.log("warn", `delegateTaskSync: unknown agent ${params.agentUrl}`);
324
+ this.log("warn", `[delegate-sync] unknown agent: ${params.agentUrl}`);
315
325
  return { status: "failed", error: `unknown agent: ${params.agentUrl}` };
316
326
  }
327
+ this.log("info", `[delegate-sync] agent found: ${agentRecord.name} (${agentRecord.url})`);
317
328
  const track = this.taskTracker.create({
318
329
  fromPeerId: "local",
319
330
  toPeerId: params.agentUrl,
320
331
  task: params.task,
321
332
  });
322
333
  this.taskTracker.update(track.taskId, { status: "running" });
334
+ this.log("info", `[delegate-sync] task tracked: ${track.taskId}, status=running`);
323
335
  try {
336
+ this.log("info", `[delegate-sync] ${track.taskId} creating A2A client for ${agentRecord.url}`);
324
337
  const client = await this.createA2AClient(agentRecord);
338
+ this.log("info", `[delegate-sync] ${track.taskId} sending A2A message (sync, with metadata: selfUrl=${this.selfUrl}, selfName=${this.agentCard?.name ?? "unknown"})...`);
325
339
  const result = await client.sendMessage({
326
340
  message: {
327
341
  kind: "message",
328
342
  role: "user",
329
343
  parts: [{ kind: "text", text: params.task }],
330
344
  messageId: track.taskId,
345
+ metadata: {
346
+ agentUrl: this.selfUrl,
347
+ agentName: this.agentCard?.name ?? "unknown",
348
+ },
331
349
  },
332
350
  });
351
+ this.log("info", `[delegate-sync] ${track.taskId} A2A response received`);
333
352
  const taskResult = this.processTaskResult(track.taskId, result);
334
- this.log("debug", `delegateTaskSync completed for ${track.taskId}`);
353
+ this.log("info", `[delegate-sync] ${track.taskId} completed — status=${taskResult.status}, outputLen=${taskResult.output?.length ?? 0}`);
335
354
  return taskResult;
336
355
  }
337
356
  catch (err) {
338
357
  const errorMsg = err instanceof Error ? err.message : String(err);
339
358
  this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
340
- this.log("error", `delegateTaskSync failed for ${track.taskId}: ${errorMsg}`);
359
+ this.log("error", `[delegate-sync] ${track.taskId} failed: ${errorMsg}`);
341
360
  return { taskId: track.taskId, status: "failed", error: errorMsg };
342
361
  }
343
362
  }
@@ -347,26 +366,30 @@ class MulticlawsService extends node_events_1.EventEmitter {
347
366
  * reports results back to the user via the message tool.
348
367
  */
349
368
  async spawnDelegation(params) {
350
- this.log("info", `spawnDelegation(agentUrl=${params.agentUrl}, task=${params.task.slice(0, 80)})`);
369
+ this.log("info", `[spawn-delegate] ▶ spawnDelegation(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
370
+ this.log("info", `[spawn-delegate] task preview: ${params.task.slice(0, 120)}`);
351
371
  await this.requireCompleteProfile();
352
372
  const agent = await this.agentRegistry.get(params.agentUrl);
353
373
  if (!agent) {
354
- this.log("warn", `spawnDelegation: unknown agent ${params.agentUrl}`);
374
+ this.log("warn", `[spawn-delegate] unknown agent: ${params.agentUrl}`);
355
375
  throw new Error(`unknown agent: ${params.agentUrl}`);
356
376
  }
377
+ this.log("info", `[spawn-delegate] agent found: ${agent.name} (${agent.url})`);
357
378
  if (!this.gatewayConfig) {
358
- this.log("error", "spawnDelegation: gateway config not available");
379
+ this.log("error", `[spawn-delegate] gateway config not available`);
359
380
  throw new Error("gateway config not available — cannot spawn sub-agent");
360
381
  }
361
382
  const prompt = buildDelegationPrompt(agent, params.task);
362
- await (0, gateway_client_1.invokeGatewayTool)({
383
+ const sessionKey = `delegate-${Date.now()}`;
384
+ this.log("info", `[spawn-delegate] spawning sub-agent via sessions_spawn (cwd=${this.resolvedCwd}, sessionKey=${sessionKey}, promptLen=${prompt.length})`);
385
+ const spawnResult = await (0, gateway_client_1.invokeGatewayTool)({
363
386
  gateway: this.gatewayConfig,
364
387
  tool: "sessions_spawn",
365
388
  args: { task: prompt, mode: "run", cwd: this.resolvedCwd },
366
- sessionKey: `delegate-${Date.now()}`,
389
+ sessionKey,
367
390
  timeoutMs: 15_000,
368
391
  });
369
- this.log("info", `spawnDelegation completed: sub-agent spawned for ${agent.name}`);
392
+ this.log("info", `[spawn-delegate] sub-agent spawned for ${agent.name} — result=${JSON.stringify(spawnResult).slice(0, 200)}`);
370
393
  return { message: `已启动子 agent 向 ${agent.name} 委派任务` };
371
394
  }
372
395
  getTaskStatus(taskId) {
@@ -857,24 +880,27 @@ class MulticlawsService extends node_events_1.EventEmitter {
857
880
  * return the final Task or Message as soon as B signals completion.
858
881
  */
859
882
  processTaskResult(trackId, result) {
860
- this.log("debug", `processTaskResult(trackId=${trackId})`);
883
+ this.log("info", `[process-result] processing result for ${trackId}, resultType=${("status" in result && result.status) ? "Task" : "Message"}`);
861
884
  try {
862
885
  if ("status" in result && result.status) {
863
886
  const task = result;
864
887
  const state = task.status?.state ?? "unknown";
865
888
  const output = this.extractArtifactText(task);
889
+ this.log("info", `[process-result] ${trackId} Task response — state=${state}, outputLen=${output.length}, preview=${output.slice(0, 120)}`);
866
890
  if (state === "completed") {
867
891
  this.taskTracker.update(trackId, { status: "completed", result: output });
892
+ this.log("info", `[process-result] ✓ ${trackId} marked completed`);
868
893
  }
869
894
  else if (state === "failed") {
870
895
  this.taskTracker.update(trackId, { status: "failed", error: output || "remote task failed" });
896
+ this.log("warn", `[process-result] ✗ ${trackId} marked failed — error=${output || "remote task failed"}`);
871
897
  }
872
898
  else {
873
899
  // For any other state (unknown, working, etc.), mark as failed to avoid
874
900
  // tasks stuck in "running" forever until TTL prune.
875
901
  this.taskTracker.update(trackId, { status: "failed", error: `unexpected remote state: ${state}` });
902
+ this.log("warn", `[process-result] ✗ ${trackId} unexpected state=${state}, marked failed`);
876
903
  }
877
- this.log("debug", `processTaskResult completed, status=${state}`);
878
904
  return { taskId: task.id, output, status: state };
879
905
  }
880
906
  const msg = result;
@@ -883,11 +909,11 @@ class MulticlawsService extends node_events_1.EventEmitter {
883
909
  .map((p) => p.text)
884
910
  .join("\n") ?? "";
885
911
  this.taskTracker.update(trackId, { status: "completed", result: text });
886
- this.log("debug", "processTaskResult completed, status=completed (message)");
912
+ this.log("info", `[process-result] ✓ ${trackId} Message response — completed, textLen=${text.length}, preview=${text.slice(0, 120)}`);
887
913
  return { taskId: trackId, output: text, status: "completed" };
888
914
  }
889
915
  catch (err) {
890
- this.log("error", `processTaskResult failed for ${trackId}: ${err instanceof Error ? err.message : String(err)}`);
916
+ this.log("error", `[process-result] ${trackId} processing failed: ${err instanceof Error ? err.message : String(err)}`);
891
917
  throw err;
892
918
  }
893
919
  }
@@ -919,11 +945,16 @@ class MulticlawsService extends node_events_1.EventEmitter {
919
945
  }
920
946
  throw lastError;
921
947
  }
922
- /** Register a channel ID for notifications. */
948
+ /** Resolve a pending A2A callback from sub-agent. */
923
949
  resolveA2ACallback(taskId, result) {
924
- if (!this.agentExecutor)
950
+ this.log("info", `[a2a-callback] resolveA2ACallback(taskId=${taskId}, resultLen=${result.length})`);
951
+ if (!this.agentExecutor) {
952
+ this.log("warn", `[a2a-callback] ✗ no agentExecutor available for taskId=${taskId}`);
925
953
  return false;
926
- return this.agentExecutor.resolveCallback(taskId, result);
954
+ }
955
+ const resolved = this.agentExecutor.resolveCallback(taskId, result);
956
+ this.log("info", `[a2a-callback] ${resolved ? "✓" : "✗"} taskId=${taskId} ${resolved ? "resolved" : "no pending callback found"}`);
957
+ return resolved;
927
958
  }
928
959
  addNotificationTarget(key, target) {
929
960
  if (!this.notificationTargets.has(key)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multiclaws",
3
- "version": "0.4.32",
3
+ "version": "0.4.34",
4
4
  "description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",