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
|
|
628
|
+
// Collect notification targets from incoming messages
|
|
599
629
|
api.on("message_received", (_event, ctx) => {
|
|
600
|
-
if (service
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
41
|
+
2. 收到回复后,调用 multiclaws_notify(message="结果内容") 将结果推送给用户
|
|
42
42
|
3. 如果需要进一步沟通,可再次调用 multiclaws_delegate_send(最多 5 轮)
|
|
43
|
-
4.
|
|
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},
|
|
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", `
|
|
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("
|
|
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", `
|
|
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},
|
|
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", `
|
|
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("
|
|
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", `
|
|
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},
|
|
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", `
|
|
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",
|
|
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
|
-
|
|
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
|
|
389
|
+
sessionKey,
|
|
367
390
|
timeoutMs: 15_000,
|
|
368
391
|
});
|
|
369
|
-
this.log("info", `
|
|
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("
|
|
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("
|
|
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", `
|
|
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
|
-
/**
|
|
948
|
+
/** Resolve a pending A2A callback from sub-agent. */
|
|
923
949
|
resolveA2ACallback(taskId, result) {
|
|
924
|
-
|
|
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
|
-
|
|
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)) {
|