multiclaws 0.4.30 → 0.4.31
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
|
@@ -200,6 +200,40 @@ function createTools(getService, logger) {
|
|
|
200
200
|
}
|
|
201
201
|
},
|
|
202
202
|
};
|
|
203
|
+
const multiclawsA2ACallback = {
|
|
204
|
+
name: "multiclaws_a2a_callback",
|
|
205
|
+
description: "Report the result of an incoming A2A delegated task. " +
|
|
206
|
+
"Called by sub-agents spawned to handle remote tasks. " +
|
|
207
|
+
"Do NOT call this directly.",
|
|
208
|
+
parameters: {
|
|
209
|
+
type: "object",
|
|
210
|
+
additionalProperties: false,
|
|
211
|
+
properties: {
|
|
212
|
+
taskId: { type: "string" },
|
|
213
|
+
result: { type: "string" },
|
|
214
|
+
},
|
|
215
|
+
required: ["taskId", "result"],
|
|
216
|
+
},
|
|
217
|
+
execute: async (_toolCallId, args) => {
|
|
218
|
+
const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
|
|
219
|
+
const result = typeof args.result === "string" ? args.result : "";
|
|
220
|
+
log("info", `tool:multiclaws_a2a_callback(taskId=${taskId})`);
|
|
221
|
+
try {
|
|
222
|
+
const service = requireService(getService());
|
|
223
|
+
if (!taskId || !result)
|
|
224
|
+
throw new Error("taskId and result are required");
|
|
225
|
+
const resolved = service.resolveA2ACallback(taskId, result);
|
|
226
|
+
if (!resolved) {
|
|
227
|
+
return textResult(`No pending callback for task ${taskId}. It may have timed out.`);
|
|
228
|
+
}
|
|
229
|
+
return textResult(`Task ${taskId} result reported successfully.`);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
log("error", `tool:multiclaws_a2a_callback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
203
237
|
const multiclawsTaskStatus = {
|
|
204
238
|
name: "multiclaws_task_status",
|
|
205
239
|
description: "Check the status of a delegated task.",
|
|
@@ -436,6 +470,7 @@ function createTools(getService, logger) {
|
|
|
436
470
|
multiclawsRemoveAgent,
|
|
437
471
|
multiclawsDelegate,
|
|
438
472
|
multiclawsDelegateSend,
|
|
473
|
+
multiclawsA2ACallback,
|
|
439
474
|
multiclawsTaskStatus,
|
|
440
475
|
multiclawsTeamCreate,
|
|
441
476
|
multiclawsTeamJoin,
|
|
@@ -458,16 +493,34 @@ const plugin = {
|
|
|
458
493
|
let service = null;
|
|
459
494
|
// Ensure required tools are in gateway.tools.allow at registration time
|
|
460
495
|
// so the gateway starts with them already present (no restart needed).
|
|
496
|
+
//
|
|
497
|
+
// Two categories:
|
|
498
|
+
// 1. Adapter-internal tools: sessions_spawn, sessions_history, message
|
|
499
|
+
// — needed by a2a-adapter itself to spawn/poll/notify.
|
|
500
|
+
// 2. A2A execution tools: exec, read, write, glob, grep
|
|
501
|
+
// — needed by spawned sub-agents to actually perform delegated tasks.
|
|
502
|
+
// Without these the sub-agent session hits "permission denied" because
|
|
503
|
+
// gateway.tools.allow restricts which tools the session can invoke.
|
|
504
|
+
//
|
|
505
|
+
// Users can override the execution tool list via plugin config:
|
|
506
|
+
// plugins.multiclaws.a2aAllowedTools: ["exec", "read", "write", ...]
|
|
461
507
|
if (api.config) {
|
|
462
508
|
const gw = api.config.gateway;
|
|
463
509
|
if (gw) {
|
|
464
510
|
const tools = (gw.tools ?? {});
|
|
465
511
|
const allow = Array.isArray(tools.allow) ? tools.allow : [];
|
|
466
|
-
const
|
|
512
|
+
const adapterRequired = ["sessions_spawn", "sessions_history", "message"];
|
|
513
|
+
const defaultA2AExecutionTools = ["exec", "read", "write", "edit", "process"];
|
|
514
|
+
const pluginConf = api.pluginConfig ?? {};
|
|
515
|
+
const a2aExecTools = Array.isArray(pluginConf.a2aAllowedTools)
|
|
516
|
+
? pluginConf.a2aAllowedTools
|
|
517
|
+
: defaultA2AExecutionTools;
|
|
518
|
+
const required = [...new Set([...adapterRequired, ...a2aExecTools])];
|
|
467
519
|
const missing = required.filter((t) => !allow.includes(t));
|
|
468
520
|
if (missing.length > 0) {
|
|
469
521
|
tools.allow = [...allow, ...missing];
|
|
470
522
|
gw.tools = tools;
|
|
523
|
+
structured.logger.info(`auto-added gateway tools: ${missing.join(", ")}`);
|
|
471
524
|
}
|
|
472
525
|
}
|
|
473
526
|
}
|
|
@@ -19,7 +19,7 @@ export type A2AAdapterOptions = {
|
|
|
19
19
|
* this executor:
|
|
20
20
|
* 1. Records the task via TaskTracker
|
|
21
21
|
* 2. Calls OpenClaw's `sessions_spawn` (run mode) to start execution
|
|
22
|
-
* 3.
|
|
22
|
+
* 3. Waits for the sub-agent to call back via `multiclaws_a2a_callback`
|
|
23
23
|
* 4. Returns the final result as a Message
|
|
24
24
|
*/
|
|
25
25
|
export declare class OpenClawAgentExecutor implements AgentExecutor {
|
|
@@ -28,25 +28,21 @@ export declare class OpenClawAgentExecutor implements AgentExecutor {
|
|
|
28
28
|
private readonly getChannelIds;
|
|
29
29
|
private readonly logger;
|
|
30
30
|
private readonly cwd;
|
|
31
|
+
private readonly pendingCallbacks;
|
|
31
32
|
constructor(options: A2AAdapterOptions);
|
|
32
33
|
execute(context: RequestContext, eventBus: ExecutionEventBus): Promise<void>;
|
|
33
34
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* Called by the `multiclaws_a2a_callback` tool when a sub-agent reports its result.
|
|
36
|
+
* Returns true if a pending callback was found and resolved.
|
|
36
37
|
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Extract all assistant text from session history once the session is complete.
|
|
40
|
-
* Returns null if the session is still running.
|
|
41
|
-
* Returns all assistant text messages joined (not just the last one).
|
|
42
|
-
*
|
|
43
|
-
* Gateway /tools/invoke returns: { content: [...], details: { messages: [...], isComplete?: boolean } }
|
|
44
|
-
*/
|
|
45
|
-
private extractCompletedResult;
|
|
46
|
-
/** Extract text content from a single history message. */
|
|
47
|
-
private extractTextFromHistoryMessage;
|
|
38
|
+
resolveCallback(taskId: string, result: string): boolean;
|
|
48
39
|
cancelTask(taskId: string, eventBus: ExecutionEventBus): Promise<void>;
|
|
49
40
|
updateGatewayConfig(config: GatewayConfig): void;
|
|
41
|
+
/**
|
|
42
|
+
* Create a pending callback that resolves when the sub-agent reports back,
|
|
43
|
+
* or rejects on timeout.
|
|
44
|
+
*/
|
|
45
|
+
private createCallback;
|
|
50
46
|
/** Send a notification to all known channels. Individual failures are silently ignored. */
|
|
51
47
|
private notifyUser;
|
|
52
48
|
private publishMessage;
|
|
@@ -10,24 +10,6 @@ function extractTextFromMessage(message) {
|
|
|
10
10
|
.map((p) => p.text)
|
|
11
11
|
.join("\n");
|
|
12
12
|
}
|
|
13
|
-
function sleep(ms) {
|
|
14
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Extract the details object from a gateway /tools/invoke result.
|
|
18
|
-
* The result shape is: { content: [...], details: { ...actual data... } }
|
|
19
|
-
*/
|
|
20
|
-
function extractDetails(result) {
|
|
21
|
-
if (!result || typeof result !== "object")
|
|
22
|
-
return null;
|
|
23
|
-
const r = result;
|
|
24
|
-
// Direct details from /tools/invoke
|
|
25
|
-
if (r.details && typeof r.details === "object") {
|
|
26
|
-
return r.details;
|
|
27
|
-
}
|
|
28
|
-
// Fallback: result itself might be the details
|
|
29
|
-
return r;
|
|
30
|
-
}
|
|
31
13
|
/**
|
|
32
14
|
* Bridges the A2A protocol to OpenClaw's sessions_spawn gateway tool.
|
|
33
15
|
*
|
|
@@ -35,7 +17,7 @@ function extractDetails(result) {
|
|
|
35
17
|
* this executor:
|
|
36
18
|
* 1. Records the task via TaskTracker
|
|
37
19
|
* 2. Calls OpenClaw's `sessions_spawn` (run mode) to start execution
|
|
38
|
-
* 3.
|
|
20
|
+
* 3. Waits for the sub-agent to call back via `multiclaws_a2a_callback`
|
|
39
21
|
* 4. Returns the final result as a Message
|
|
40
22
|
*/
|
|
41
23
|
class OpenClawAgentExecutor {
|
|
@@ -44,6 +26,7 @@ class OpenClawAgentExecutor {
|
|
|
44
26
|
getChannelIds;
|
|
45
27
|
logger;
|
|
46
28
|
cwd;
|
|
29
|
+
pendingCallbacks = new Map();
|
|
47
30
|
constructor(options) {
|
|
48
31
|
this.gatewayConfig = options.gatewayConfig;
|
|
49
32
|
this.taskTracker = options.taskTracker;
|
|
@@ -76,34 +59,27 @@ class OpenClawAgentExecutor {
|
|
|
76
59
|
void this.notifyUser(`📨 收到来自 **${fromAgent}** 的委派任务:${taskText.slice(0, 200)}`);
|
|
77
60
|
try {
|
|
78
61
|
this.logger.info(`[a2a-adapter] executing task ${taskId}: ${taskText.slice(0, 100)}`);
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
62
|
+
// Create a promise that resolves when sub-agent calls multiclaws_a2a_callback
|
|
63
|
+
const resultPromise = this.createCallback(taskId, 180_000);
|
|
64
|
+
// Spawn the subagent with instructions to call back when done
|
|
65
|
+
const prompt = buildA2ASubagentPrompt(taskId, taskText);
|
|
66
|
+
await (0, gateway_client_1.invokeGatewayTool)({
|
|
84
67
|
gateway: this.gatewayConfig,
|
|
85
68
|
tool: "sessions_spawn",
|
|
86
69
|
args: {
|
|
87
|
-
task:
|
|
70
|
+
task: prompt,
|
|
88
71
|
mode: "run",
|
|
89
72
|
cwd: this.cwd,
|
|
90
73
|
},
|
|
91
74
|
sessionKey: `a2a-${taskId}`,
|
|
92
75
|
timeoutMs: 15_000,
|
|
93
76
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
throw new Error("sessions_spawn did not return a childSessionKey");
|
|
99
|
-
}
|
|
100
|
-
// 2. Poll for completion
|
|
101
|
-
const gatewaySessionKey = `a2a-${taskId}`;
|
|
102
|
-
this.logger.info(`[a2a-adapter] task ${taskId} spawned as ${childSessionKey}, waiting for result...`);
|
|
103
|
-
const output = await this.waitForCompletion(childSessionKey, 180_000, gatewaySessionKey);
|
|
104
|
-
// 3. Return result
|
|
77
|
+
this.logger.info(`[a2a-adapter] task ${taskId} spawned, waiting for callback...`);
|
|
78
|
+
// Wait for the sub-agent to call back
|
|
79
|
+
const output = await resultPromise;
|
|
80
|
+
// Return result
|
|
105
81
|
this.taskTracker.update(taskId, { status: "completed", result: output });
|
|
106
|
-
this.logger.info(`[a2a-adapter] task ${taskId} completed`);
|
|
82
|
+
this.logger.info(`[a2a-adapter] task ${taskId} completed, resultLen=${output.length}`);
|
|
107
83
|
this.publishMessage(eventBus, output || "Task completed with no output.");
|
|
108
84
|
}
|
|
109
85
|
catch (err) {
|
|
@@ -115,146 +91,27 @@ class OpenClawAgentExecutor {
|
|
|
115
91
|
eventBus.finished();
|
|
116
92
|
}
|
|
117
93
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
94
|
+
* Called by the `multiclaws_a2a_callback` tool when a sub-agent reports its result.
|
|
95
|
+
* Returns true if a pending callback was found and resolved.
|
|
120
96
|
*/
|
|
121
|
-
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await sleep(delay);
|
|
130
|
-
attempt++;
|
|
131
|
-
try {
|
|
132
|
-
const histResult = await (0, gateway_client_1.invokeGatewayTool)({
|
|
133
|
-
gateway,
|
|
134
|
-
tool: "sessions_history",
|
|
135
|
-
args: {
|
|
136
|
-
sessionKey,
|
|
137
|
-
limit: 50,
|
|
138
|
-
includeTools: false,
|
|
139
|
-
},
|
|
140
|
-
sessionKey: gatewaySessionKey,
|
|
141
|
-
timeoutMs: 8_000,
|
|
142
|
-
});
|
|
143
|
-
const result = this.extractCompletedResult(histResult);
|
|
144
|
-
if (result !== null) {
|
|
145
|
-
this.logger.info(`[a2a-adapter] poll attempt ${attempt}: session ${sessionKey} completed, resultLen=${result.length}`);
|
|
146
|
-
return result;
|
|
147
|
-
}
|
|
148
|
-
// Log details on first attempt, then every 10 attempts for diagnosis
|
|
149
|
-
if (attempt === 1 || attempt % 10 === 0) {
|
|
150
|
-
const details = extractDetails(histResult);
|
|
151
|
-
const messages = (details?.messages ?? []);
|
|
152
|
-
const lastMsg = messages[messages.length - 1];
|
|
153
|
-
this.logger.info(`[a2a-adapter] poll attempt ${attempt}: session ${sessionKey} still running. ` +
|
|
154
|
-
`isComplete=${details?.isComplete}, status=${details?.status}, ` +
|
|
155
|
-
`msgCount=${messages.length}, lastRole=${lastMsg?.role}, ` +
|
|
156
|
-
`lastContentTypes=${JSON.stringify(Array.isArray(lastMsg?.content)
|
|
157
|
-
? lastMsg.content.map((c) => c?.type)
|
|
158
|
-
: typeof lastMsg?.content)}`);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
this.logger.info(`[a2a-adapter] poll attempt ${attempt}: session ${sessionKey} still running...`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch (err) {
|
|
165
|
-
this.logger.warn(`[a2a-adapter] poll attempt ${attempt} error: ${err instanceof Error ? err.message : err}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
169
|
-
this.logger.error(`[a2a-adapter] waitForCompletion timed out: session=${sessionKey}, elapsed=${elapsed}s, attempts=${attempt}`);
|
|
170
|
-
throw new Error(`task timed out after ${elapsed}s (${attempt} attempts) waiting for subagent`);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Extract all assistant text from session history once the session is complete.
|
|
174
|
-
* Returns null if the session is still running.
|
|
175
|
-
* Returns all assistant text messages joined (not just the last one).
|
|
176
|
-
*
|
|
177
|
-
* Gateway /tools/invoke returns: { content: [...], details: { messages: [...], isComplete?: boolean } }
|
|
178
|
-
*/
|
|
179
|
-
extractCompletedResult(histResult) {
|
|
180
|
-
const details = extractDetails(histResult);
|
|
181
|
-
if (!details)
|
|
182
|
-
return null;
|
|
183
|
-
// Check for session-level error/status from gateway
|
|
184
|
-
const sessionError = details.error;
|
|
185
|
-
const sessionStatus = details.status;
|
|
186
|
-
// Immediately fail on terminal session statuses — do NOT keep polling
|
|
187
|
-
const terminalStatuses = ["forbidden", "failed", "error", "not_found", "unauthorized"];
|
|
188
|
-
if (sessionStatus && terminalStatuses.includes(sessionStatus)) {
|
|
189
|
-
this.logger.warn(`[a2a-adapter] extractCompletedResult: terminal status="${sessionStatus}", error="${sessionError ?? "none"}"`);
|
|
190
|
-
return `Error: session status "${sessionStatus}"${sessionError ? `: ${sessionError}` : ""}`;
|
|
191
|
-
}
|
|
192
|
-
const messages = (details.messages ?? []);
|
|
193
|
-
if (messages.length === 0 && !details.isComplete)
|
|
194
|
-
return null;
|
|
195
|
-
// If session is not explicitly complete, use heuristic: check if the session is still executing
|
|
196
|
-
if (details.isComplete !== true) {
|
|
197
|
-
if (messages.length === 0)
|
|
198
|
-
return null;
|
|
199
|
-
const lastMsg = messages[messages.length - 1];
|
|
200
|
-
if (lastMsg && Array.isArray(lastMsg.content)) {
|
|
201
|
-
const content = lastMsg.content;
|
|
202
|
-
const hasToolCalls = content.some((c) => c?.type === "toolCall" || c?.type === "tool_use");
|
|
203
|
-
// If the last message only has tool calls (no text), still running
|
|
204
|
-
const hasText = content.some((c) => c?.type === "text" && typeof c.text === "string" && c.text.trim());
|
|
205
|
-
if (hasToolCalls && !hasText)
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
// If the last message is a user message, the agent hasn't responded yet
|
|
209
|
-
if (lastMsg?.role === "user")
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
// Session is complete — collect ALL assistant text messages in order
|
|
213
|
-
const allTexts = [];
|
|
214
|
-
for (const msg of messages) {
|
|
215
|
-
if (msg.role !== "assistant")
|
|
216
|
-
continue;
|
|
217
|
-
const text = this.extractTextFromHistoryMessage(msg);
|
|
218
|
-
if (text)
|
|
219
|
-
allTexts.push(text);
|
|
220
|
-
}
|
|
221
|
-
// If we have assistant text, return it (even if there's also an error)
|
|
222
|
-
if (allTexts.length > 0) {
|
|
223
|
-
// Append error info if present so the delegating agent sees both
|
|
224
|
-
if (sessionError) {
|
|
225
|
-
allTexts.push(`[session error: ${sessionError}]`);
|
|
226
|
-
}
|
|
227
|
-
return allTexts.join("\n\n");
|
|
228
|
-
}
|
|
229
|
-
// No assistant text — check if the session reported an error
|
|
230
|
-
if (sessionError) {
|
|
231
|
-
return `Error: ${sessionError}`;
|
|
232
|
-
}
|
|
233
|
-
if (sessionStatus === "failed" || sessionStatus === "error") {
|
|
234
|
-
return `Error: session ended with status "${sessionStatus}"`;
|
|
235
|
-
}
|
|
236
|
-
// Session truly completed with no output at all
|
|
237
|
-
return "(task completed with no text output)";
|
|
238
|
-
}
|
|
239
|
-
/** Extract text content from a single history message. */
|
|
240
|
-
extractTextFromHistoryMessage(msg) {
|
|
241
|
-
const content = msg.content;
|
|
242
|
-
if (typeof content === "string" && content.trim()) {
|
|
243
|
-
return content;
|
|
244
|
-
}
|
|
245
|
-
if (Array.isArray(content)) {
|
|
246
|
-
const parts = content;
|
|
247
|
-
const textParts = parts
|
|
248
|
-
.filter((c) => c?.type === "text" && typeof c.text === "string" && c.text.trim())
|
|
249
|
-
.map((c) => c.text);
|
|
250
|
-
if (textParts.length > 0) {
|
|
251
|
-
return textParts.join("\n");
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
97
|
+
resolveCallback(taskId, result) {
|
|
98
|
+
const pending = this.pendingCallbacks.get(taskId);
|
|
99
|
+
if (!pending)
|
|
100
|
+
return false;
|
|
101
|
+
clearTimeout(pending.timer);
|
|
102
|
+
this.pendingCallbacks.delete(taskId);
|
|
103
|
+
pending.resolve(result);
|
|
104
|
+
return true;
|
|
255
105
|
}
|
|
256
106
|
async cancelTask(taskId, eventBus) {
|
|
257
107
|
this.logger.info(`[a2a-adapter] cancelTask(taskId=${taskId})`);
|
|
108
|
+
// Reject pending callback if any
|
|
109
|
+
const pending = this.pendingCallbacks.get(taskId);
|
|
110
|
+
if (pending) {
|
|
111
|
+
clearTimeout(pending.timer);
|
|
112
|
+
this.pendingCallbacks.delete(taskId);
|
|
113
|
+
pending.reject(new Error("canceled"));
|
|
114
|
+
}
|
|
258
115
|
this.taskTracker.update(taskId, { status: "failed", error: "canceled" });
|
|
259
116
|
this.publishMessage(eventBus, "Task was canceled.");
|
|
260
117
|
eventBus.finished();
|
|
@@ -262,6 +119,19 @@ class OpenClawAgentExecutor {
|
|
|
262
119
|
updateGatewayConfig(config) {
|
|
263
120
|
this.gatewayConfig = config;
|
|
264
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a pending callback that resolves when the sub-agent reports back,
|
|
124
|
+
* or rejects on timeout.
|
|
125
|
+
*/
|
|
126
|
+
createCallback(taskId, timeoutMs) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
this.pendingCallbacks.delete(taskId);
|
|
130
|
+
reject(new Error(`task timed out after ${timeoutMs / 1000}s waiting for sub-agent callback`));
|
|
131
|
+
}, timeoutMs);
|
|
132
|
+
this.pendingCallbacks.set(taskId, { resolve, reject, timer });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
265
135
|
/** Send a notification to all known channels. Individual failures are silently ignored. */
|
|
266
136
|
async notifyUser(message) {
|
|
267
137
|
const channels = this.getChannelIds();
|
|
@@ -285,3 +155,28 @@ class OpenClawAgentExecutor {
|
|
|
285
155
|
}
|
|
286
156
|
}
|
|
287
157
|
exports.OpenClawAgentExecutor = OpenClawAgentExecutor;
|
|
158
|
+
/**
|
|
159
|
+
* Build the prompt for the sub-agent that handles an incoming A2A task.
|
|
160
|
+
* The sub-agent must call `multiclaws_a2a_callback` to report its result.
|
|
161
|
+
*/
|
|
162
|
+
function buildA2ASubagentPrompt(taskId, taskText) {
|
|
163
|
+
return `你收到了一个来自远端智能体的委派任务。请完成任务并汇报结果。
|
|
164
|
+
|
|
165
|
+
## 任务内容
|
|
166
|
+
|
|
167
|
+
${taskText}
|
|
168
|
+
|
|
169
|
+
## 完成后必做
|
|
170
|
+
|
|
171
|
+
完成任务后,你**必须**调用 \`multiclaws_a2a_callback\` 工具汇报结果:
|
|
172
|
+
|
|
173
|
+
\`\`\`
|
|
174
|
+
multiclaws_a2a_callback(taskId="${taskId}", result="你的完整回复内容")
|
|
175
|
+
\`\`\`
|
|
176
|
+
|
|
177
|
+
**重要**:
|
|
178
|
+
- 无论任务成功还是失败,都必须调用 \`multiclaws_a2a_callback\`
|
|
179
|
+
- result 参数填写你的完整回复文本
|
|
180
|
+
- 如果任务失败,在 result 中说明失败原因
|
|
181
|
+
- 这是唯一的结果回传方式,不调用则结果会丢失`;
|
|
182
|
+
}
|
|
@@ -127,6 +127,7 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
127
127
|
/** Fetch with up to 2 retries and exponential backoff. */
|
|
128
128
|
private fetchWithRetry;
|
|
129
129
|
/** Register a channel ID for notifications. */
|
|
130
|
+
resolveA2ACallback(taskId: string, result: string): boolean;
|
|
130
131
|
addChannelId(channelId: string): void;
|
|
131
132
|
/** Send a notification to all known channels. Individual failures are silently ignored. */
|
|
132
133
|
private notifyUser;
|
|
@@ -920,6 +920,11 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
920
920
|
throw lastError;
|
|
921
921
|
}
|
|
922
922
|
/** Register a channel ID for notifications. */
|
|
923
|
+
resolveA2ACallback(taskId, result) {
|
|
924
|
+
if (!this.agentExecutor)
|
|
925
|
+
return false;
|
|
926
|
+
return this.agentExecutor.resolveCallback(taskId, result);
|
|
927
|
+
}
|
|
923
928
|
addChannelId(channelId) {
|
|
924
929
|
if (!this.knownChannelIds.has(channelId)) {
|
|
925
930
|
this.knownChannelIds.add(channelId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaws",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
4
4
|
"description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
"openclaw.plugin.json",
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
18
24
|
"keywords": [
|
|
19
25
|
"openclaw",
|
|
20
26
|
"plugin",
|
|
@@ -46,11 +52,5 @@
|
|
|
46
52
|
"@types/proper-lockfile": "^4.1.4",
|
|
47
53
|
"typescript": "^5.9.2",
|
|
48
54
|
"vitest": "^3.2.4"
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "tsc -p tsconfig.json",
|
|
52
|
-
"test": "vitest run",
|
|
53
|
-
"test:watch": "vitest",
|
|
54
|
-
"clean": "rm -rf dist"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|