@elizaos/plugin-agent-orchestrator 2.0.0-alpha.2 → 2.0.0-alpha.4
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.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3065 -19
- package/dist/index.js.map +16 -6
- package/dist/src/actions/messaging.d.ts +24 -0
- package/dist/src/actions/messaging.d.ts.map +1 -0
- package/dist/src/actions/subagent-management.d.ts +7 -0
- package/dist/src/actions/subagent-management.d.ts.map +1 -0
- package/dist/src/actions/task-management.d.ts.map +1 -1
- package/dist/src/providers/orchestrator-config.d.ts +80 -0
- package/dist/src/providers/orchestrator-config.d.ts.map +1 -0
- package/dist/src/services/messaging-service.d.ts +111 -0
- package/dist/src/services/messaging-service.d.ts.map +1 -0
- package/dist/src/services/sandbox-service.d.ts +103 -0
- package/dist/src/services/sandbox-service.d.ts.map +1 -0
- package/dist/src/services/subagent-service.d.ts +110 -0
- package/dist/src/services/subagent-service.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +12 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/messaging.d.ts +202 -0
- package/dist/src/types/messaging.d.ts.map +1 -0
- package/dist/src/types/sandbox.d.ts +228 -0
- package/dist/src/types/sandbox.d.ts.map +1 -0
- package/dist/src/types/subagent.d.ts +224 -0
- package/dist/src/types/subagent.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +7 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/session.d.ts +106 -0
- package/dist/src/utils/session.d.ts.map +1 -0
- package/package.json +6 -5
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,3 +1,1032 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { getSessionProviders as getSessionProviders2 } from "@elizaos/core";
|
|
3
|
+
|
|
4
|
+
// src/actions/messaging.ts
|
|
5
|
+
function extractMessagingParams(message, _state) {
|
|
6
|
+
const params = message.content?.params;
|
|
7
|
+
if (!params) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
let target;
|
|
11
|
+
if (params.target) {
|
|
12
|
+
target = params.target;
|
|
13
|
+
} else {
|
|
14
|
+
target = {};
|
|
15
|
+
if (params.channel)
|
|
16
|
+
target.channel = params.channel;
|
|
17
|
+
if (params.to)
|
|
18
|
+
target.to = params.to;
|
|
19
|
+
if (params.threadId !== undefined)
|
|
20
|
+
target.threadId = params.threadId;
|
|
21
|
+
if (params.replyTo)
|
|
22
|
+
target.replyToMessageId = params.replyTo;
|
|
23
|
+
}
|
|
24
|
+
let content;
|
|
25
|
+
if (params.content) {
|
|
26
|
+
content = params.content;
|
|
27
|
+
} else {
|
|
28
|
+
content = {};
|
|
29
|
+
const text = params.text ?? message.content?.text;
|
|
30
|
+
if (text)
|
|
31
|
+
content.text = text;
|
|
32
|
+
}
|
|
33
|
+
return { target, content };
|
|
34
|
+
}
|
|
35
|
+
var sendCrossPlatformMessageAction = {
|
|
36
|
+
name: "SEND_CROSS_PLATFORM_MESSAGE",
|
|
37
|
+
similes: [
|
|
38
|
+
"CROSS_PLATFORM_MESSAGE",
|
|
39
|
+
"UNIFIED_SEND",
|
|
40
|
+
"SEND_TO_CHANNEL",
|
|
41
|
+
"RELAY_MESSAGE",
|
|
42
|
+
"BROADCAST_MESSAGE"
|
|
43
|
+
],
|
|
44
|
+
description: "Send a message to any supported platform (Discord, Telegram, Slack, WhatsApp, Twitch). " + "Requires specifying the target channel/platform and recipient.",
|
|
45
|
+
validate: async (runtime, message, _state) => {
|
|
46
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
47
|
+
if (!messagingService) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const params = message.content?.params;
|
|
51
|
+
if (!params) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const hasTarget = params.target || params.channel && params.to;
|
|
55
|
+
const hasContent = params.content || params.text || message.content?.text;
|
|
56
|
+
return !!(hasTarget && hasContent);
|
|
57
|
+
},
|
|
58
|
+
handler: async (runtime, message, state, _options, callback) => {
|
|
59
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
60
|
+
if (!messagingService) {
|
|
61
|
+
if (callback) {
|
|
62
|
+
await callback({
|
|
63
|
+
text: "Messaging service is not available. Please ensure the orchestrator plugin is properly configured."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return { success: false, error: "Messaging service not available" };
|
|
67
|
+
}
|
|
68
|
+
const { target, content } = extractMessagingParams(message, state);
|
|
69
|
+
if (!target?.channel) {
|
|
70
|
+
if (callback) {
|
|
71
|
+
await callback({
|
|
72
|
+
text: "Please specify the target channel (discord, telegram, slack, whatsapp, twitch)."
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return { success: false, error: "Missing target channel" };
|
|
76
|
+
}
|
|
77
|
+
if (!target?.to) {
|
|
78
|
+
if (callback) {
|
|
79
|
+
await callback({
|
|
80
|
+
text: "Please specify the recipient (channel ID, chat ID, or room ID)."
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return { success: false, error: "Missing recipient" };
|
|
84
|
+
}
|
|
85
|
+
if (!content?.text) {
|
|
86
|
+
if (callback) {
|
|
87
|
+
await callback({
|
|
88
|
+
text: "Please provide the message text to send."
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return { success: false, error: "Missing message text" };
|
|
92
|
+
}
|
|
93
|
+
const sendTarget = {
|
|
94
|
+
channel: target.channel,
|
|
95
|
+
to: target.to
|
|
96
|
+
};
|
|
97
|
+
if (target.threadId !== undefined)
|
|
98
|
+
sendTarget.threadId = target.threadId;
|
|
99
|
+
if (target.replyToMessageId)
|
|
100
|
+
sendTarget.replyToMessageId = target.replyToMessageId;
|
|
101
|
+
const sendContent = {
|
|
102
|
+
text: content.text
|
|
103
|
+
};
|
|
104
|
+
if (content.attachments)
|
|
105
|
+
sendContent.attachments = content.attachments;
|
|
106
|
+
if (content.embed)
|
|
107
|
+
sendContent.embed = content.embed;
|
|
108
|
+
if (content.buttons)
|
|
109
|
+
sendContent.buttons = content.buttons;
|
|
110
|
+
if (content.disableLinkPreview !== undefined)
|
|
111
|
+
sendContent.disableLinkPreview = content.disableLinkPreview;
|
|
112
|
+
if (content.silent !== undefined)
|
|
113
|
+
sendContent.silent = content.silent;
|
|
114
|
+
const result = await messagingService.send({
|
|
115
|
+
target: sendTarget,
|
|
116
|
+
content: sendContent
|
|
117
|
+
});
|
|
118
|
+
if (callback) {
|
|
119
|
+
if (result.success) {
|
|
120
|
+
const callbackData = {};
|
|
121
|
+
if (result.messageId)
|
|
122
|
+
callbackData.messageId = result.messageId;
|
|
123
|
+
if (result.sentAt)
|
|
124
|
+
callbackData.sentAt = result.sentAt;
|
|
125
|
+
await callback({
|
|
126
|
+
text: `Message sent successfully to ${target.channel}.`,
|
|
127
|
+
data: callbackData
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
await callback({
|
|
131
|
+
text: `Failed to send message: ${result.error}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
success: result.success,
|
|
137
|
+
data: result,
|
|
138
|
+
...result.error !== undefined ? { error: result.error } : {}
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
examples: [
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
name: "{{name1}}",
|
|
145
|
+
content: {
|
|
146
|
+
text: "Send 'Hello from the agent!' to Discord channel 123456789",
|
|
147
|
+
params: {
|
|
148
|
+
channel: "discord",
|
|
149
|
+
to: "123456789",
|
|
150
|
+
text: "Hello from the agent!"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "{{agentName}}",
|
|
156
|
+
content: {
|
|
157
|
+
text: "Message sent successfully to Discord.",
|
|
158
|
+
actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
{
|
|
164
|
+
name: "{{name1}}",
|
|
165
|
+
content: {
|
|
166
|
+
text: "Send a Telegram message",
|
|
167
|
+
params: {
|
|
168
|
+
channel: "telegram",
|
|
169
|
+
to: "-1001234567890",
|
|
170
|
+
text: "Automated notification from your AI assistant."
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "{{agentName}}",
|
|
176
|
+
content: {
|
|
177
|
+
text: "Message sent successfully to Telegram.",
|
|
178
|
+
actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
]
|
|
183
|
+
};
|
|
184
|
+
var sendToDeliveryContextAction = {
|
|
185
|
+
name: "SEND_TO_DELIVERY_CONTEXT",
|
|
186
|
+
similes: ["DELIVER_MESSAGE", "SEND_TO_CONTEXT", "ROUTE_MESSAGE"],
|
|
187
|
+
description: "Send a message using a delivery context that specifies the target channel and recipient. " + "Useful for routing messages back to the original requester or to a specific context.",
|
|
188
|
+
validate: async (runtime, message, _state) => {
|
|
189
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
190
|
+
if (!messagingService) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const params = message.content?.params;
|
|
194
|
+
if (!params?.deliveryContext) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const hasText = params.text || message.content?.text;
|
|
198
|
+
return !!hasText;
|
|
199
|
+
},
|
|
200
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
201
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
202
|
+
if (!messagingService) {
|
|
203
|
+
if (callback) {
|
|
204
|
+
await callback({
|
|
205
|
+
text: "Messaging service is not available."
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return { success: false, error: "Messaging service not available" };
|
|
209
|
+
}
|
|
210
|
+
const params = message.content?.params;
|
|
211
|
+
const deliveryContext = params?.deliveryContext;
|
|
212
|
+
const text = params?.text ?? message.content?.text;
|
|
213
|
+
if (!deliveryContext) {
|
|
214
|
+
if (callback) {
|
|
215
|
+
await callback({
|
|
216
|
+
text: "Please provide a delivery context with channel and recipient information."
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return { success: false, error: "Missing delivery context" };
|
|
220
|
+
}
|
|
221
|
+
if (!text) {
|
|
222
|
+
if (callback) {
|
|
223
|
+
await callback({
|
|
224
|
+
text: "Please provide the message text to send."
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return { success: false, error: "Missing message text" };
|
|
228
|
+
}
|
|
229
|
+
const result = await messagingService.sendToDeliveryContext(deliveryContext, {
|
|
230
|
+
text
|
|
231
|
+
});
|
|
232
|
+
if (callback) {
|
|
233
|
+
if (result.success) {
|
|
234
|
+
const callbackData = {};
|
|
235
|
+
if (result.messageId)
|
|
236
|
+
callbackData.messageId = result.messageId;
|
|
237
|
+
await callback({
|
|
238
|
+
text: `Message delivered successfully via ${result.channel}.`,
|
|
239
|
+
data: callbackData
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
await callback({
|
|
243
|
+
text: `Failed to deliver message: ${result.error}`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
success: result.success,
|
|
249
|
+
data: result,
|
|
250
|
+
...result.error !== undefined ? { error: result.error } : {}
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
examples: [
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
name: "{{name1}}",
|
|
257
|
+
content: {
|
|
258
|
+
text: "Send result to delivery context",
|
|
259
|
+
params: {
|
|
260
|
+
deliveryContext: {
|
|
261
|
+
channel: "discord",
|
|
262
|
+
to: "123456789"
|
|
263
|
+
},
|
|
264
|
+
text: "Task completed successfully!"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "{{agentName}}",
|
|
270
|
+
content: {
|
|
271
|
+
text: "Message delivered successfully via discord.",
|
|
272
|
+
actions: ["SEND_TO_DELIVERY_CONTEXT"]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
]
|
|
277
|
+
};
|
|
278
|
+
var sendToRoomAction = {
|
|
279
|
+
name: "SEND_TO_ROOM",
|
|
280
|
+
similes: ["MESSAGE_ROOM", "ROOM_MESSAGE", "NOTIFY_ROOM"],
|
|
281
|
+
description: "Send a message to an Eliza room. The room's metadata determines which platform and recipient to use.",
|
|
282
|
+
validate: async (runtime, message, _state) => {
|
|
283
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
284
|
+
if (!messagingService) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const params = message.content?.params;
|
|
288
|
+
if (!params?.roomId) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
const hasText = params.text || message.content?.text;
|
|
292
|
+
return !!hasText;
|
|
293
|
+
},
|
|
294
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
295
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
296
|
+
if (!messagingService) {
|
|
297
|
+
if (callback) {
|
|
298
|
+
await callback({
|
|
299
|
+
text: "Messaging service is not available."
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return { success: false, error: "Messaging service not available" };
|
|
303
|
+
}
|
|
304
|
+
const params = message.content?.params;
|
|
305
|
+
const roomId = params?.roomId;
|
|
306
|
+
const text = params?.text ?? message.content?.text;
|
|
307
|
+
if (!roomId) {
|
|
308
|
+
if (callback) {
|
|
309
|
+
await callback({
|
|
310
|
+
text: "Please specify the room ID to send the message to."
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return { success: false, error: "Missing room ID" };
|
|
314
|
+
}
|
|
315
|
+
if (!text) {
|
|
316
|
+
if (callback) {
|
|
317
|
+
await callback({
|
|
318
|
+
text: "Please provide the message text to send."
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return { success: false, error: "Missing message text" };
|
|
322
|
+
}
|
|
323
|
+
const result = await messagingService.sendToRoom(roomId, { text });
|
|
324
|
+
if (callback) {
|
|
325
|
+
if (result.success) {
|
|
326
|
+
const callbackData = {};
|
|
327
|
+
if (result.messageId)
|
|
328
|
+
callbackData.messageId = result.messageId;
|
|
329
|
+
await callback({
|
|
330
|
+
text: `Message sent to room via ${result.channel}.`,
|
|
331
|
+
data: callbackData
|
|
332
|
+
});
|
|
333
|
+
} else {
|
|
334
|
+
await callback({
|
|
335
|
+
text: `Failed to send to room: ${result.error}`
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
success: result.success,
|
|
341
|
+
data: result,
|
|
342
|
+
...result.error !== undefined ? { error: result.error } : {}
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
examples: [
|
|
346
|
+
[
|
|
347
|
+
{
|
|
348
|
+
name: "{{name1}}",
|
|
349
|
+
content: {
|
|
350
|
+
text: "Send to room",
|
|
351
|
+
params: {
|
|
352
|
+
roomId: "550e8400-e29b-41d4-a716-446655440000",
|
|
353
|
+
text: "Hello, room!"
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "{{agentName}}",
|
|
359
|
+
content: {
|
|
360
|
+
text: "Message sent to room via discord.",
|
|
361
|
+
actions: ["SEND_TO_ROOM"]
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
]
|
|
366
|
+
};
|
|
367
|
+
var sendToSessionMessageAction = {
|
|
368
|
+
name: "SEND_TO_SESSION_MESSAGE",
|
|
369
|
+
similes: ["SESSION_MESSAGE", "MESSAGE_SESSION", "NOTIFY_SESSION"],
|
|
370
|
+
description: "Send a message to a session by its session key. The session key is mapped to an Eliza room.",
|
|
371
|
+
validate: async (runtime, message, _state) => {
|
|
372
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
373
|
+
if (!messagingService) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const params = message.content?.params;
|
|
377
|
+
if (!params?.sessionKey) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
const hasText = params.text || message.content?.text;
|
|
381
|
+
return !!hasText;
|
|
382
|
+
},
|
|
383
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
384
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
385
|
+
if (!messagingService) {
|
|
386
|
+
if (callback) {
|
|
387
|
+
await callback({
|
|
388
|
+
text: "Messaging service is not available."
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return { success: false, error: "Messaging service not available" };
|
|
392
|
+
}
|
|
393
|
+
const params = message.content?.params;
|
|
394
|
+
const sessionKey = params?.sessionKey;
|
|
395
|
+
const text = params?.text ?? message.content?.text;
|
|
396
|
+
if (!sessionKey) {
|
|
397
|
+
if (callback) {
|
|
398
|
+
await callback({
|
|
399
|
+
text: "Please specify the session key to send the message to."
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return { success: false, error: "Missing session key" };
|
|
403
|
+
}
|
|
404
|
+
if (!text) {
|
|
405
|
+
if (callback) {
|
|
406
|
+
await callback({
|
|
407
|
+
text: "Please provide the message text to send."
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return { success: false, error: "Missing message text" };
|
|
411
|
+
}
|
|
412
|
+
const result = await messagingService.sendToSession(sessionKey, { text });
|
|
413
|
+
if (callback) {
|
|
414
|
+
if (result.success) {
|
|
415
|
+
const callbackData = {};
|
|
416
|
+
if (result.messageId)
|
|
417
|
+
callbackData.messageId = result.messageId;
|
|
418
|
+
await callback({
|
|
419
|
+
text: `Message sent to session via ${result.channel}.`,
|
|
420
|
+
data: callbackData
|
|
421
|
+
});
|
|
422
|
+
} else {
|
|
423
|
+
await callback({
|
|
424
|
+
text: `Failed to send to session: ${result.error}`
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
success: result.success,
|
|
430
|
+
data: result,
|
|
431
|
+
...result.error !== undefined ? { error: result.error } : {}
|
|
432
|
+
};
|
|
433
|
+
},
|
|
434
|
+
examples: [
|
|
435
|
+
[
|
|
436
|
+
{
|
|
437
|
+
name: "{{name1}}",
|
|
438
|
+
content: {
|
|
439
|
+
text: "Send to session",
|
|
440
|
+
params: {
|
|
441
|
+
sessionKey: "agent:main:dm:user123",
|
|
442
|
+
text: "Update from your subagent!"
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "{{agentName}}",
|
|
448
|
+
content: {
|
|
449
|
+
text: "Message sent to session via discord.",
|
|
450
|
+
actions: ["SEND_TO_SESSION_MESSAGE"]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
]
|
|
455
|
+
};
|
|
456
|
+
var listMessagingChannelsAction = {
|
|
457
|
+
name: "LIST_MESSAGING_CHANNELS",
|
|
458
|
+
similes: ["AVAILABLE_CHANNELS", "GET_CHANNELS", "MESSAGING_PLATFORMS"],
|
|
459
|
+
description: "List all available messaging channels/platforms that the agent can send messages to.",
|
|
460
|
+
validate: async (runtime, _message, _state) => {
|
|
461
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
462
|
+
return !!messagingService;
|
|
463
|
+
},
|
|
464
|
+
handler: async (runtime, _message, _state, _options, callback) => {
|
|
465
|
+
const messagingService = runtime.getService("MESSAGING");
|
|
466
|
+
if (!messagingService) {
|
|
467
|
+
if (callback) {
|
|
468
|
+
await callback({
|
|
469
|
+
text: "Messaging service is not available."
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return { success: false, error: "Messaging service not available" };
|
|
473
|
+
}
|
|
474
|
+
const channels = messagingService.getAvailableChannels();
|
|
475
|
+
if (callback) {
|
|
476
|
+
if (channels.length > 0) {
|
|
477
|
+
await callback({
|
|
478
|
+
text: `Available messaging channels: ${channels.join(", ")}`,
|
|
479
|
+
data: { channels }
|
|
480
|
+
});
|
|
481
|
+
} else {
|
|
482
|
+
await callback({
|
|
483
|
+
text: "No messaging channels are currently available.",
|
|
484
|
+
data: { channels: [] }
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
success: true,
|
|
490
|
+
data: { channels }
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
examples: [
|
|
494
|
+
[
|
|
495
|
+
{
|
|
496
|
+
name: "{{name1}}",
|
|
497
|
+
content: {
|
|
498
|
+
text: "What messaging platforms can you use?"
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: "{{agentName}}",
|
|
503
|
+
content: {
|
|
504
|
+
text: "Available messaging channels: discord, telegram, internal",
|
|
505
|
+
actions: ["LIST_MESSAGING_CHANNELS"]
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
]
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// src/utils/session.ts
|
|
513
|
+
import crypto from "node:crypto";
|
|
514
|
+
import {
|
|
515
|
+
buildAcpSessionKey,
|
|
516
|
+
buildAgentMainSessionKey,
|
|
517
|
+
buildAgentPeerSessionKey,
|
|
518
|
+
buildAgentSessionKey,
|
|
519
|
+
buildGroupHistoryKey,
|
|
520
|
+
buildSubagentSessionKey,
|
|
521
|
+
createSendPolicyProvider,
|
|
522
|
+
createSessionEntry,
|
|
523
|
+
createSessionProvider,
|
|
524
|
+
createSessionSkillsProvider,
|
|
525
|
+
deleteSessionEntry,
|
|
526
|
+
extractSessionContext,
|
|
527
|
+
getSessionEntry,
|
|
528
|
+
getSessionProviders,
|
|
529
|
+
isAcpSessionKey,
|
|
530
|
+
isSubagentSessionKey,
|
|
531
|
+
isValidSessionEntry,
|
|
532
|
+
listSessionKeys,
|
|
533
|
+
loadSessionStore,
|
|
534
|
+
mergeSessionEntry,
|
|
535
|
+
normalizeAccountId,
|
|
536
|
+
normalizeAgentId,
|
|
537
|
+
normalizeMainKey,
|
|
538
|
+
parseAgentSessionKey,
|
|
539
|
+
resolveAgentIdFromSessionKey,
|
|
540
|
+
resolveAgentSessionsDir,
|
|
541
|
+
resolveDefaultSessionStorePath,
|
|
542
|
+
resolveSessionTranscriptPath,
|
|
543
|
+
resolveStateDir,
|
|
544
|
+
resolveStorePath,
|
|
545
|
+
resolveThreadParentSessionKey,
|
|
546
|
+
resolveThreadSessionKeys,
|
|
547
|
+
SessionStateManager,
|
|
548
|
+
saveSessionStore,
|
|
549
|
+
toAgentRequestSessionKey,
|
|
550
|
+
toAgentStoreSessionKey,
|
|
551
|
+
updateSessionStore,
|
|
552
|
+
updateSessionStoreEntry,
|
|
553
|
+
upsertSessionEntry
|
|
554
|
+
} from "@elizaos/core";
|
|
555
|
+
function hashToUUID(input) {
|
|
556
|
+
const hash = crypto.createHash("sha256").update(input).digest("hex");
|
|
557
|
+
const uuid = [
|
|
558
|
+
hash.slice(0, 8),
|
|
559
|
+
hash.slice(8, 12),
|
|
560
|
+
`5${hash.slice(13, 16)}`,
|
|
561
|
+
`${(parseInt(hash.slice(16, 17), 16) & 3 | 8).toString(16)}${hash.slice(17, 20)}`,
|
|
562
|
+
hash.slice(20, 32)
|
|
563
|
+
].join("-");
|
|
564
|
+
return uuid;
|
|
565
|
+
}
|
|
566
|
+
function sessionKeyToRoomId(sessionKey, agentId) {
|
|
567
|
+
const normalized = normalizeSessionKey(sessionKey);
|
|
568
|
+
const input = agentId ? `${agentId}:${normalized}` : normalized;
|
|
569
|
+
return hashToUUID(input);
|
|
570
|
+
}
|
|
571
|
+
function normalizeSessionKey(sessionKey) {
|
|
572
|
+
const trimmed = sessionKey.trim();
|
|
573
|
+
if (!trimmed) {
|
|
574
|
+
return "";
|
|
575
|
+
}
|
|
576
|
+
if (trimmed.startsWith("agent:")) {
|
|
577
|
+
return trimmed;
|
|
578
|
+
}
|
|
579
|
+
return trimmed;
|
|
580
|
+
}
|
|
581
|
+
function parseSessionKey(sessionKey) {
|
|
582
|
+
const trimmed = sessionKey.trim();
|
|
583
|
+
if (!trimmed) {
|
|
584
|
+
return {
|
|
585
|
+
agentId: "unknown",
|
|
586
|
+
keyType: "unknown",
|
|
587
|
+
identifier: ""
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
if (trimmed.startsWith("agent:")) {
|
|
591
|
+
const parts = trimmed.split(":");
|
|
592
|
+
if (parts.length >= 4) {
|
|
593
|
+
const agentId = parts[1];
|
|
594
|
+
const keyTypeRaw = parts[2];
|
|
595
|
+
const identifier = parts.slice(3).join(":");
|
|
596
|
+
const keyType = normalizeKeyType(keyTypeRaw);
|
|
597
|
+
return {
|
|
598
|
+
agentId,
|
|
599
|
+
keyType,
|
|
600
|
+
identifier
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (parts.length >= 2) {
|
|
604
|
+
return {
|
|
605
|
+
agentId: parts[1],
|
|
606
|
+
keyType: "unknown",
|
|
607
|
+
identifier: parts.slice(2).join(":")
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const colonIndex = trimmed.indexOf(":");
|
|
612
|
+
if (colonIndex > 0) {
|
|
613
|
+
const keyTypeRaw = trimmed.slice(0, colonIndex);
|
|
614
|
+
const identifier = trimmed.slice(colonIndex + 1);
|
|
615
|
+
const keyType = normalizeKeyType(keyTypeRaw);
|
|
616
|
+
return {
|
|
617
|
+
agentId: "unknown",
|
|
618
|
+
keyType,
|
|
619
|
+
identifier
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
agentId: "unknown",
|
|
624
|
+
keyType: "unknown",
|
|
625
|
+
identifier: trimmed
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function normalizeKeyType(raw) {
|
|
629
|
+
const lower = raw.toLowerCase();
|
|
630
|
+
switch (lower) {
|
|
631
|
+
case "dm":
|
|
632
|
+
case "direct":
|
|
633
|
+
return "dm";
|
|
634
|
+
case "subagent":
|
|
635
|
+
case "sub":
|
|
636
|
+
return "subagent";
|
|
637
|
+
case "group":
|
|
638
|
+
case "server":
|
|
639
|
+
return "group";
|
|
640
|
+
case "channel":
|
|
641
|
+
return "channel";
|
|
642
|
+
default:
|
|
643
|
+
return "unknown";
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function buildSessionKey(agentId, keyType, identifier) {
|
|
647
|
+
const normalizedAgentId = normalizeAgentId2(agentId);
|
|
648
|
+
return `agent:${normalizedAgentId}:${keyType}:${identifier}`;
|
|
649
|
+
}
|
|
650
|
+
function normalizeAgentId2(agentId) {
|
|
651
|
+
return agentId.trim().toLowerCase();
|
|
652
|
+
}
|
|
653
|
+
function isSubagentSessionKey2(sessionKey) {
|
|
654
|
+
const parsed = parseSessionKey(sessionKey);
|
|
655
|
+
return parsed.keyType === "subagent";
|
|
656
|
+
}
|
|
657
|
+
function extractAgentIdFromSessionKey(sessionKey) {
|
|
658
|
+
const parsed = parseSessionKey(sessionKey);
|
|
659
|
+
return parsed.agentId;
|
|
660
|
+
}
|
|
661
|
+
function createSubagentSessionKey(agentId) {
|
|
662
|
+
const uuid = crypto.randomUUID();
|
|
663
|
+
return buildSessionKey(agentId, "subagent", uuid);
|
|
664
|
+
}
|
|
665
|
+
function normalizeDeliveryContext(context) {
|
|
666
|
+
if (!context) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const result = {};
|
|
670
|
+
if (context.channel && typeof context.channel === "string") {
|
|
671
|
+
const trimmed = context.channel.trim();
|
|
672
|
+
if (trimmed)
|
|
673
|
+
result.channel = trimmed;
|
|
674
|
+
}
|
|
675
|
+
if (context.accountId && typeof context.accountId === "string") {
|
|
676
|
+
const trimmed = context.accountId.trim();
|
|
677
|
+
if (trimmed)
|
|
678
|
+
result.accountId = trimmed;
|
|
679
|
+
}
|
|
680
|
+
if (context.to && typeof context.to === "string") {
|
|
681
|
+
const trimmed = context.to.trim();
|
|
682
|
+
if (trimmed)
|
|
683
|
+
result.to = trimmed;
|
|
684
|
+
}
|
|
685
|
+
if (context.threadId !== undefined && context.threadId !== null) {
|
|
686
|
+
result.threadId = context.threadId;
|
|
687
|
+
}
|
|
688
|
+
if (context.groupId && typeof context.groupId === "string") {
|
|
689
|
+
const trimmed = context.groupId.trim();
|
|
690
|
+
if (trimmed)
|
|
691
|
+
result.groupId = trimmed;
|
|
692
|
+
}
|
|
693
|
+
if (context.groupChannel && typeof context.groupChannel === "string") {
|
|
694
|
+
const trimmed = context.groupChannel.trim();
|
|
695
|
+
if (trimmed)
|
|
696
|
+
result.groupChannel = trimmed;
|
|
697
|
+
}
|
|
698
|
+
if (context.groupSpace && typeof context.groupSpace === "string") {
|
|
699
|
+
const trimmed = context.groupSpace.trim();
|
|
700
|
+
if (trimmed)
|
|
701
|
+
result.groupSpace = trimmed;
|
|
702
|
+
}
|
|
703
|
+
const hasValues = Object.values(result).some((v) => v !== undefined && v !== null);
|
|
704
|
+
return hasValues ? result : undefined;
|
|
705
|
+
}
|
|
706
|
+
function mergeDeliveryContext(primary, secondary) {
|
|
707
|
+
if (!primary && !secondary) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (!primary) {
|
|
711
|
+
return normalizeDeliveryContext(secondary);
|
|
712
|
+
}
|
|
713
|
+
if (!secondary) {
|
|
714
|
+
return normalizeDeliveryContext(primary);
|
|
715
|
+
}
|
|
716
|
+
const merged = {};
|
|
717
|
+
const ch = primary.channel || secondary.channel;
|
|
718
|
+
if (ch)
|
|
719
|
+
merged.channel = ch;
|
|
720
|
+
const acct = primary.accountId || secondary.accountId;
|
|
721
|
+
if (acct)
|
|
722
|
+
merged.accountId = acct;
|
|
723
|
+
const to = primary.to || secondary.to;
|
|
724
|
+
if (to)
|
|
725
|
+
merged.to = to;
|
|
726
|
+
const tid = primary.threadId ?? secondary.threadId;
|
|
727
|
+
if (tid !== undefined)
|
|
728
|
+
merged.threadId = tid;
|
|
729
|
+
const gid = primary.groupId || secondary.groupId;
|
|
730
|
+
if (gid)
|
|
731
|
+
merged.groupId = gid;
|
|
732
|
+
const gch = primary.groupChannel || secondary.groupChannel;
|
|
733
|
+
if (gch)
|
|
734
|
+
merged.groupChannel = gch;
|
|
735
|
+
const gsp = primary.groupSpace || secondary.groupSpace;
|
|
736
|
+
if (gsp)
|
|
737
|
+
merged.groupSpace = gsp;
|
|
738
|
+
return normalizeDeliveryContext(merged);
|
|
739
|
+
}
|
|
740
|
+
function formatDurationShort(ms) {
|
|
741
|
+
if (!ms || !Number.isFinite(ms) || ms <= 0) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const totalSeconds = Math.round(ms / 1000);
|
|
745
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
746
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
747
|
+
const seconds = totalSeconds % 60;
|
|
748
|
+
if (hours > 0) {
|
|
749
|
+
return `${hours}h${minutes}m`;
|
|
750
|
+
}
|
|
751
|
+
if (minutes > 0) {
|
|
752
|
+
return `${minutes}m${seconds}s`;
|
|
753
|
+
}
|
|
754
|
+
return `${seconds}s`;
|
|
755
|
+
}
|
|
756
|
+
function formatTokenCount(count) {
|
|
757
|
+
if (!count || !Number.isFinite(count)) {
|
|
758
|
+
return "0";
|
|
759
|
+
}
|
|
760
|
+
if (count >= 1e6) {
|
|
761
|
+
return `${(count / 1e6).toFixed(1)}m`;
|
|
762
|
+
}
|
|
763
|
+
if (count >= 1000) {
|
|
764
|
+
return `${(count / 1000).toFixed(1)}k`;
|
|
765
|
+
}
|
|
766
|
+
return String(Math.round(count));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/actions/subagent-management.ts
|
|
770
|
+
function getSubagentService(runtime) {
|
|
771
|
+
const svc = runtime.getService("SUBAGENT");
|
|
772
|
+
if (!svc) {
|
|
773
|
+
throw new Error("SubagentService not available (SUBAGENT)");
|
|
774
|
+
}
|
|
775
|
+
return svc;
|
|
776
|
+
}
|
|
777
|
+
function extractSessionContext2(_runtime, message) {
|
|
778
|
+
const metadata = message.content?.metadata;
|
|
779
|
+
const sessionKey = typeof metadata?.sessionKey === "string" ? metadata.sessionKey : undefined;
|
|
780
|
+
const result = {};
|
|
781
|
+
if (sessionKey)
|
|
782
|
+
result.sessionKey = sessionKey;
|
|
783
|
+
if (message.roomId)
|
|
784
|
+
result.roomId = message.roomId;
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
var spawnSubagentAction = {
|
|
788
|
+
name: "SPAWN_SUBAGENT",
|
|
789
|
+
similes: ["SPAWN_TASK", "BACKGROUND_TASK", "START_SUBAGENT", "SESSIONS_SPAWN", "CREATE_SUBAGENT"],
|
|
790
|
+
description: "Spawn a background sub-agent run to execute a task asynchronously. The subagent will complete the task and announce results back.",
|
|
791
|
+
validate: async (runtime, message) => {
|
|
792
|
+
const svc = runtime.getService("SUBAGENT");
|
|
793
|
+
if (!svc) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
const text = message.content.text?.toLowerCase() ?? "";
|
|
797
|
+
const hasSpawnIntent = text.includes("spawn") || text.includes("background") || text.includes("subagent") || text.includes("async task");
|
|
798
|
+
const hasTaskWords = text.includes("task") || text.includes("research") || text.includes("investigate") || text.includes("analyze") || text.includes("look into");
|
|
799
|
+
return hasSpawnIntent || hasTaskWords && text.includes("in background");
|
|
800
|
+
},
|
|
801
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
802
|
+
const svc = getSubagentService(runtime);
|
|
803
|
+
const context = extractSessionContext2(runtime, message);
|
|
804
|
+
const opts = options;
|
|
805
|
+
const task = opts?.task ?? message.content.text ?? "";
|
|
806
|
+
if (!task.trim()) {
|
|
807
|
+
const msg2 = "Please specify a task for the subagent to execute.";
|
|
808
|
+
await callback?.({ content: { text: msg2 } });
|
|
809
|
+
return { success: false, text: msg2 };
|
|
810
|
+
}
|
|
811
|
+
const spawnParams = {
|
|
812
|
+
task,
|
|
813
|
+
runTimeoutSeconds: opts?.timeoutSeconds ?? 300,
|
|
814
|
+
cleanup: opts?.cleanup ?? "keep"
|
|
815
|
+
};
|
|
816
|
+
if (opts?.label)
|
|
817
|
+
spawnParams.label = opts.label;
|
|
818
|
+
if (opts?.agentId)
|
|
819
|
+
spawnParams.agentId = opts.agentId;
|
|
820
|
+
if (opts?.model)
|
|
821
|
+
spawnParams.model = opts.model;
|
|
822
|
+
if (opts?.thinking)
|
|
823
|
+
spawnParams.thinking = opts.thinking;
|
|
824
|
+
const result = await svc.spawnSubagent(spawnParams, context);
|
|
825
|
+
if (result.status !== "accepted") {
|
|
826
|
+
const msg2 = `Failed to spawn subagent: ${result.error ?? "unknown error"}`;
|
|
827
|
+
await callback?.({ content: { text: msg2 } });
|
|
828
|
+
return { success: false, text: msg2 };
|
|
829
|
+
}
|
|
830
|
+
const labelText = opts?.label ? ` "${opts.label}"` : "";
|
|
831
|
+
const msg = `Spawned background task${labelText}. Run ID: ${result.runId?.slice(0, 8)}...
|
|
832
|
+
I'll announce results when complete.`;
|
|
833
|
+
await callback?.({ content: { text: msg } });
|
|
834
|
+
return {
|
|
835
|
+
success: true,
|
|
836
|
+
text: msg,
|
|
837
|
+
data: {
|
|
838
|
+
runId: result.runId,
|
|
839
|
+
childSessionKey: result.childSessionKey,
|
|
840
|
+
childRoomId: result.childRoomId
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
var sendToSessionAction = {
|
|
846
|
+
name: "SEND_TO_SESSION",
|
|
847
|
+
similes: ["SESSIONS_SEND", "SEND_MESSAGE", "MESSAGE_AGENT", "A2A_SEND"],
|
|
848
|
+
description: "Send a message to another agent session. Use sessionKey or label to identify the target.",
|
|
849
|
+
validate: async (runtime, message) => {
|
|
850
|
+
const svc = runtime.getService("SUBAGENT");
|
|
851
|
+
if (!svc) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const text = message.content.text?.toLowerCase() ?? "";
|
|
855
|
+
return text.includes("send to session") || text.includes("message session") || text.includes("send") && text.includes("agent") || text.includes("sessions_send");
|
|
856
|
+
},
|
|
857
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
858
|
+
const svc = getSubagentService(runtime);
|
|
859
|
+
const context = extractSessionContext2(runtime, message);
|
|
860
|
+
const opts = options;
|
|
861
|
+
const targetMessage = opts?.message ?? message.content.text ?? "";
|
|
862
|
+
if (!targetMessage.trim()) {
|
|
863
|
+
const msg2 = "Please specify a message to send.";
|
|
864
|
+
await callback?.({ content: { text: msg2 } });
|
|
865
|
+
return { success: false, text: msg2 };
|
|
866
|
+
}
|
|
867
|
+
if (!opts?.sessionKey && !opts?.label) {
|
|
868
|
+
const msg2 = "Please specify either a sessionKey or label to identify the target session.";
|
|
869
|
+
await callback?.({ content: { text: msg2 } });
|
|
870
|
+
return { success: false, text: msg2 };
|
|
871
|
+
}
|
|
872
|
+
const sendParams = {
|
|
873
|
+
message: targetMessage,
|
|
874
|
+
timeoutSeconds: opts?.timeoutSeconds ?? 30
|
|
875
|
+
};
|
|
876
|
+
if (opts?.sessionKey)
|
|
877
|
+
sendParams.sessionKey = opts.sessionKey;
|
|
878
|
+
if (opts?.label)
|
|
879
|
+
sendParams.label = opts.label;
|
|
880
|
+
if (opts?.agentId)
|
|
881
|
+
sendParams.agentId = opts.agentId;
|
|
882
|
+
const result = await svc.sendToAgent(sendParams, context);
|
|
883
|
+
if (result.status === "forbidden") {
|
|
884
|
+
const msg2 = `Access denied: ${result.error}`;
|
|
885
|
+
await callback?.({ content: { text: msg2 } });
|
|
886
|
+
return { success: false, text: msg2 };
|
|
887
|
+
}
|
|
888
|
+
if (result.status === "error") {
|
|
889
|
+
const msg2 = `Failed to send message: ${result.error ?? "unknown error"}`;
|
|
890
|
+
await callback?.({ content: { text: msg2 } });
|
|
891
|
+
return { success: false, text: msg2 };
|
|
892
|
+
}
|
|
893
|
+
if (result.status === "timeout") {
|
|
894
|
+
const msg2 = `Request timed out waiting for response.`;
|
|
895
|
+
await callback?.({ content: { text: msg2 } });
|
|
896
|
+
return { success: false, text: msg2 };
|
|
897
|
+
}
|
|
898
|
+
const replyText = result.reply ? `
|
|
899
|
+
|
|
900
|
+
Reply: ${result.reply}` : "";
|
|
901
|
+
const msg = `Message sent to ${result.sessionKey}.${replyText}`;
|
|
902
|
+
await callback?.({ content: { text: msg } });
|
|
903
|
+
return {
|
|
904
|
+
success: true,
|
|
905
|
+
text: msg,
|
|
906
|
+
data: {
|
|
907
|
+
runId: result.runId,
|
|
908
|
+
sessionKey: result.sessionKey,
|
|
909
|
+
reply: result.reply
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
var listSubagentsAction = {
|
|
915
|
+
name: "LIST_SUBAGENTS",
|
|
916
|
+
similes: ["SHOW_SUBAGENTS", "SUBAGENT_STATUS", "RUNNING_TASKS"],
|
|
917
|
+
description: "List active and recent subagent runs.",
|
|
918
|
+
validate: async (runtime, message) => {
|
|
919
|
+
const svc = runtime.getService("SUBAGENT");
|
|
920
|
+
if (!svc) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
const text = message.content.text?.toLowerCase() ?? "";
|
|
924
|
+
return text.includes("list subagent") || text.includes("show subagent") || text.includes("subagent status") || text.includes("running task") || text.includes("background task");
|
|
925
|
+
},
|
|
926
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
927
|
+
const svc = getSubagentService(runtime);
|
|
928
|
+
const context = extractSessionContext2(runtime, message);
|
|
929
|
+
const runs = svc.listSubagentRuns(context.sessionKey);
|
|
930
|
+
if (runs.length === 0) {
|
|
931
|
+
const msg2 = "No subagent runs found.";
|
|
932
|
+
await callback?.({ content: { text: msg2 } });
|
|
933
|
+
return { success: true, text: msg2 };
|
|
934
|
+
}
|
|
935
|
+
const lines = ["Subagent Runs:"];
|
|
936
|
+
for (const run of runs.slice(0, 20)) {
|
|
937
|
+
const status = run.outcome?.status ?? (run.endedAt ? "done" : "running");
|
|
938
|
+
const duration = run.endedAt ? formatDurationShort(run.endedAt - (run.startedAt ?? run.createdAt)) : "...";
|
|
939
|
+
const label = run.label || run.task.slice(0, 40);
|
|
940
|
+
lines.push(`- [${status}] ${label} (${duration})`);
|
|
941
|
+
}
|
|
942
|
+
const msg = lines.join(`
|
|
943
|
+
`);
|
|
944
|
+
await callback?.({ content: { text: msg } });
|
|
945
|
+
return {
|
|
946
|
+
success: true,
|
|
947
|
+
text: msg,
|
|
948
|
+
data: { count: runs.length }
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
var cancelSubagentAction = {
|
|
953
|
+
name: "CANCEL_SUBAGENT",
|
|
954
|
+
similes: ["STOP_SUBAGENT", "ABORT_TASK", "KILL_SUBAGENT"],
|
|
955
|
+
description: "Cancel a running subagent by its run ID.",
|
|
956
|
+
validate: async (runtime, message) => {
|
|
957
|
+
const svc = runtime.getService("SUBAGENT");
|
|
958
|
+
if (!svc) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
const text = message.content.text?.toLowerCase() ?? "";
|
|
962
|
+
return (text.includes("cancel") || text.includes("stop") || text.includes("abort")) && (text.includes("subagent") || text.includes("background"));
|
|
963
|
+
},
|
|
964
|
+
handler: async (runtime, _message, _state, options, callback) => {
|
|
965
|
+
const svc = getSubagentService(runtime);
|
|
966
|
+
const opts = options;
|
|
967
|
+
const runId = opts?.runId;
|
|
968
|
+
if (!runId) {
|
|
969
|
+
const msg2 = "Please specify the run ID to cancel. Use LIST_SUBAGENTS to see active runs.";
|
|
970
|
+
await callback?.({ content: { text: msg2 } });
|
|
971
|
+
return { success: false, text: msg2 };
|
|
972
|
+
}
|
|
973
|
+
const cancelled = svc.cancelSubagentRun(runId);
|
|
974
|
+
if (!cancelled) {
|
|
975
|
+
const msg2 = `No active run found with ID: ${runId.slice(0, 8)}...`;
|
|
976
|
+
await callback?.({ content: { text: msg2 } });
|
|
977
|
+
return { success: false, text: msg2 };
|
|
978
|
+
}
|
|
979
|
+
const msg = `Cancelled subagent run: ${runId.slice(0, 8)}...`;
|
|
980
|
+
await callback?.({ content: { text: msg } });
|
|
981
|
+
return { success: true, text: msg, data: { runId } };
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
var getSubagentStatusAction = {
|
|
985
|
+
name: "GET_SUBAGENT_STATUS",
|
|
986
|
+
similes: ["SUBAGENT_INFO", "TASK_STATUS", "CHECK_SUBAGENT"],
|
|
987
|
+
description: "Get detailed status of a specific subagent run.",
|
|
988
|
+
validate: async (runtime, message) => {
|
|
989
|
+
const svc = runtime.getService("SUBAGENT");
|
|
990
|
+
if (!svc) {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
const text = message.content.text?.toLowerCase() ?? "";
|
|
994
|
+
return text.includes("subagent status") || text.includes("task status") || text.includes("check subagent") || text.includes("subagent info");
|
|
995
|
+
},
|
|
996
|
+
handler: async (runtime, _message, _state, options, callback) => {
|
|
997
|
+
const svc = getSubagentService(runtime);
|
|
998
|
+
const opts = options;
|
|
999
|
+
const runId = opts?.runId;
|
|
1000
|
+
if (!runId) {
|
|
1001
|
+
const msg2 = "Please specify the run ID to check. Use LIST_SUBAGENTS to see available runs.";
|
|
1002
|
+
await callback?.({ content: { text: msg2 } });
|
|
1003
|
+
return { success: false, text: msg2 };
|
|
1004
|
+
}
|
|
1005
|
+
const run = svc.getSubagentRun(runId);
|
|
1006
|
+
if (!run) {
|
|
1007
|
+
const msg2 = `No run found with ID: ${runId.slice(0, 8)}...`;
|
|
1008
|
+
await callback?.({ content: { text: msg2 } });
|
|
1009
|
+
return { success: false, text: msg2 };
|
|
1010
|
+
}
|
|
1011
|
+
const status = run.outcome?.status ?? (run.endedAt ? "done" : "running");
|
|
1012
|
+
const duration = run.endedAt ? formatDurationShort(run.endedAt - (run.startedAt ?? run.createdAt)) : "in progress";
|
|
1013
|
+
const lines = [
|
|
1014
|
+
`## Subagent Run: ${runId.slice(0, 8)}...`,
|
|
1015
|
+
"",
|
|
1016
|
+
`**Status:** ${status}`,
|
|
1017
|
+
`**Task:** ${run.task}`,
|
|
1018
|
+
run.label ? `**Label:** ${run.label}` : undefined,
|
|
1019
|
+
`**Duration:** ${duration}`,
|
|
1020
|
+
`**Session:** ${run.childSessionKey}`,
|
|
1021
|
+
run.outcome?.error ? `**Error:** ${run.outcome.error}` : undefined
|
|
1022
|
+
].filter((l) => l !== undefined);
|
|
1023
|
+
const msg = lines.join(`
|
|
1024
|
+
`);
|
|
1025
|
+
await callback?.({ content: { text: msg } });
|
|
1026
|
+
return { success: true, text: msg, data: { run } };
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1
1030
|
// src/actions/task-management.ts
|
|
2
1031
|
function getService(runtime) {
|
|
3
1032
|
const svc = runtime.getService("CODE_TASK");
|
|
@@ -13,7 +1042,11 @@ var createTaskAction = {
|
|
|
13
1042
|
name: "CREATE_TASK",
|
|
14
1043
|
similes: ["START_TASK", "SPAWN_TASK", "NEW_TASK", "BEGIN_TASK"],
|
|
15
1044
|
description: "Create an orchestrated background task to be executed by a selected agent provider.",
|
|
16
|
-
validate: async (
|
|
1045
|
+
validate: async (runtime, message) => {
|
|
1046
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1047
|
+
if (!svc) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
17
1050
|
const text = message.content.text?.toLowerCase() ?? "";
|
|
18
1051
|
const hasExplicit = text.includes("create task") || text.includes("new task") || text.includes("start a task");
|
|
19
1052
|
const hasIntent = text.includes("implement") || text.includes("build") || text.includes("create") || text.includes("develop") || text.includes("refactor") || text.includes("fix") || text.includes("add");
|
|
@@ -44,7 +1077,10 @@ ${stepLines.map((s, i) => `${i + 1}. ${s}`).join(`
|
|
|
44
1077
|
Provider: ${task.metadata.providerLabel ?? task.metadata.providerId}
|
|
45
1078
|
Starting execution…`;
|
|
46
1079
|
await callback?.({ content: { text: msg } });
|
|
47
|
-
svc.startTaskExecution(task.id ?? "").catch(() => {
|
|
1080
|
+
svc.startTaskExecution(task.id ?? "").catch((err) => {
|
|
1081
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1082
|
+
svc.appendOutput(task.id ?? "", `Execution error: ${errorMsg}`).catch(() => {});
|
|
1083
|
+
});
|
|
48
1084
|
return { success: true, text: msg, data: { taskId: task.id ?? "" } };
|
|
49
1085
|
}
|
|
50
1086
|
};
|
|
@@ -52,8 +1088,12 @@ var listTasksAction = {
|
|
|
52
1088
|
name: "LIST_TASKS",
|
|
53
1089
|
similes: ["SHOW_TASKS", "GET_TASKS", "TASKS", "VIEW_TASKS"],
|
|
54
1090
|
description: "List tasks managed by the orchestrator.",
|
|
55
|
-
validate: async (
|
|
56
|
-
const
|
|
1091
|
+
validate: async (runtime, message) => {
|
|
1092
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1093
|
+
if (!svc) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
const t = message.content.text?.toLowerCase() ?? "";
|
|
57
1097
|
return t.includes("list task") || t.includes("show task") || t === "tasks" || t.includes("my task");
|
|
58
1098
|
},
|
|
59
1099
|
handler: async (runtime, _message, _state, _options, callback) => {
|
|
@@ -80,7 +1120,11 @@ var switchTaskAction = {
|
|
|
80
1120
|
name: "SWITCH_TASK",
|
|
81
1121
|
similes: ["SELECT_TASK", "SET_TASK", "CHANGE_TASK", "GO_TO_TASK"],
|
|
82
1122
|
description: "Switch the current task context to a different task.",
|
|
83
|
-
validate: async (
|
|
1123
|
+
validate: async (runtime, message) => {
|
|
1124
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1125
|
+
if (!svc) {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
84
1128
|
const t = message.content.text?.toLowerCase() ?? "";
|
|
85
1129
|
return t.includes("switch to task") || t.includes("select task") || t.includes("task") && t.includes("switch");
|
|
86
1130
|
},
|
|
@@ -109,7 +1153,11 @@ var searchTasksAction = {
|
|
|
109
1153
|
name: "SEARCH_TASKS",
|
|
110
1154
|
similes: ["FIND_TASK", "LOOKUP_TASK"],
|
|
111
1155
|
description: "Search tasks by query.",
|
|
112
|
-
validate: async (
|
|
1156
|
+
validate: async (runtime, message) => {
|
|
1157
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1158
|
+
if (!svc) {
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
113
1161
|
const t = message.content.text?.toLowerCase() ?? "";
|
|
114
1162
|
return t.includes("search task") || t.includes("find task") || t.includes("look for task");
|
|
115
1163
|
},
|
|
@@ -142,7 +1190,11 @@ var pauseTaskAction = {
|
|
|
142
1190
|
name: "PAUSE_TASK",
|
|
143
1191
|
similes: ["STOP_TASK", "HALT_TASK"],
|
|
144
1192
|
description: "Pause a running task.",
|
|
145
|
-
validate: async (
|
|
1193
|
+
validate: async (runtime, message) => {
|
|
1194
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1195
|
+
if (!svc) {
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
146
1198
|
const t = message.content.text?.toLowerCase() ?? "";
|
|
147
1199
|
return (t.includes("pause") || t.includes("stop") || t.includes("halt")) && t.includes("task");
|
|
148
1200
|
},
|
|
@@ -165,7 +1217,11 @@ var resumeTaskAction = {
|
|
|
165
1217
|
name: "RESUME_TASK",
|
|
166
1218
|
similes: ["CONTINUE_TASK", "RESTART_TASK", "RUN_TASK"],
|
|
167
1219
|
description: "Resume a paused task.",
|
|
168
|
-
validate: async (
|
|
1220
|
+
validate: async (runtime, message) => {
|
|
1221
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1222
|
+
if (!svc) {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
169
1225
|
const t = message.content.text?.toLowerCase() ?? "";
|
|
170
1226
|
return t.includes("task") && (t.includes("resume") || t.includes("restart") || t.includes("continue"));
|
|
171
1227
|
},
|
|
@@ -179,7 +1235,11 @@ var resumeTaskAction = {
|
|
|
179
1235
|
return { success: false, text: msg2 };
|
|
180
1236
|
}
|
|
181
1237
|
await svc.resumeTask(task.id);
|
|
182
|
-
|
|
1238
|
+
const taskId = task.id;
|
|
1239
|
+
svc.startTaskExecution(taskId).catch((err) => {
|
|
1240
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1241
|
+
svc.appendOutput(taskId, `Execution error: ${errorMsg}`).catch(() => {});
|
|
1242
|
+
});
|
|
183
1243
|
const msg = `Resumed task: ${task.name}`;
|
|
184
1244
|
await callback?.({ content: { text: msg } });
|
|
185
1245
|
return { success: true, text: msg };
|
|
@@ -189,7 +1249,11 @@ var cancelTaskAction = {
|
|
|
189
1249
|
name: "CANCEL_TASK",
|
|
190
1250
|
similes: ["DELETE_TASK", "REMOVE_TASK", "ABORT_TASK"],
|
|
191
1251
|
description: "Cancel a task.",
|
|
192
|
-
validate: async (
|
|
1252
|
+
validate: async (runtime, message) => {
|
|
1253
|
+
const svc = runtime.getService("CODE_TASK");
|
|
1254
|
+
if (!svc) {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
193
1257
|
const t = message.content.text?.toLowerCase() ?? "";
|
|
194
1258
|
return (t.includes("cancel") || t.includes("delete") || t.includes("remove")) && t.includes("task");
|
|
195
1259
|
},
|
|
@@ -218,6 +1282,96 @@ function getConfiguredAgentOrchestratorOptions() {
|
|
|
218
1282
|
return globalOptions;
|
|
219
1283
|
}
|
|
220
1284
|
|
|
1285
|
+
// src/providers/orchestrator-config.ts
|
|
1286
|
+
var DEFAULT_CONFIG = {
|
|
1287
|
+
subagents: {
|
|
1288
|
+
enabled: true,
|
|
1289
|
+
timeoutSeconds: 300,
|
|
1290
|
+
allowAgents: [],
|
|
1291
|
+
archiveAfterMinutes: 60
|
|
1292
|
+
},
|
|
1293
|
+
agentToAgent: {
|
|
1294
|
+
enabled: false,
|
|
1295
|
+
allow: []
|
|
1296
|
+
},
|
|
1297
|
+
sandbox: {
|
|
1298
|
+
mode: "off",
|
|
1299
|
+
scope: "session",
|
|
1300
|
+
workspaceAccess: "rw"
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
function getOrchestratorConfig(runtime) {
|
|
1304
|
+
const settings = runtime.character?.settings;
|
|
1305
|
+
if (!settings) {
|
|
1306
|
+
return DEFAULT_CONFIG;
|
|
1307
|
+
}
|
|
1308
|
+
const subagentsRaw = settings.subagents ?? {};
|
|
1309
|
+
const a2aRaw = settings.agentToAgent ?? {};
|
|
1310
|
+
const sandboxRaw = settings.sandbox ?? {};
|
|
1311
|
+
const subagents = {
|
|
1312
|
+
enabled: subagentsRaw.enabled ?? true,
|
|
1313
|
+
timeoutSeconds: subagentsRaw.timeoutSeconds ?? 300,
|
|
1314
|
+
allowAgents: subagentsRaw.allowAgents ?? [],
|
|
1315
|
+
archiveAfterMinutes: subagentsRaw.archiveAfterMinutes ?? 60
|
|
1316
|
+
};
|
|
1317
|
+
if (subagentsRaw.model)
|
|
1318
|
+
subagents.model = subagentsRaw.model;
|
|
1319
|
+
if (subagentsRaw.thinking)
|
|
1320
|
+
subagents.thinking = subagentsRaw.thinking;
|
|
1321
|
+
return {
|
|
1322
|
+
subagents,
|
|
1323
|
+
agentToAgent: {
|
|
1324
|
+
enabled: a2aRaw.enabled ?? DEFAULT_CONFIG.agentToAgent.enabled,
|
|
1325
|
+
allow: a2aRaw.allow ?? DEFAULT_CONFIG.agentToAgent.allow
|
|
1326
|
+
},
|
|
1327
|
+
sandbox: {
|
|
1328
|
+
...DEFAULT_CONFIG.sandbox,
|
|
1329
|
+
...sandboxRaw
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function formatConfigContext(config) {
|
|
1334
|
+
const lines = ["## Orchestrator Configuration", ""];
|
|
1335
|
+
lines.push("### Subagents");
|
|
1336
|
+
lines.push(`- Enabled: ${config.subagents.enabled}`);
|
|
1337
|
+
if (config.subagents.model) {
|
|
1338
|
+
lines.push(`- Default Model: ${config.subagents.model}`);
|
|
1339
|
+
}
|
|
1340
|
+
if (config.subagents.thinking) {
|
|
1341
|
+
lines.push(`- Thinking Level: ${config.subagents.thinking}`);
|
|
1342
|
+
}
|
|
1343
|
+
lines.push(`- Timeout: ${config.subagents.timeoutSeconds}s`);
|
|
1344
|
+
const allowAgents = config.subagents.allowAgents ?? [];
|
|
1345
|
+
if (allowAgents.length > 0) {
|
|
1346
|
+
lines.push(`- Allowed Agents: ${allowAgents.join(", ")}`);
|
|
1347
|
+
}
|
|
1348
|
+
lines.push("");
|
|
1349
|
+
lines.push("### Agent-to-Agent Communication");
|
|
1350
|
+
lines.push(`- Enabled: ${config.agentToAgent.enabled}`);
|
|
1351
|
+
if (config.agentToAgent.allow.length > 0) {
|
|
1352
|
+
lines.push("- Allow Rules:");
|
|
1353
|
+
for (const rule of config.agentToAgent.allow) {
|
|
1354
|
+
lines.push(` - ${rule.source} → ${rule.target}`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
lines.push("");
|
|
1358
|
+
lines.push("### Sandbox");
|
|
1359
|
+
lines.push(`- Mode: ${config.sandbox.mode ?? "off"}`);
|
|
1360
|
+
lines.push(`- Scope: ${config.sandbox.scope ?? "session"}`);
|
|
1361
|
+
lines.push(`- Workspace Access: ${config.sandbox.workspaceAccess ?? "rw"}`);
|
|
1362
|
+
lines.push("");
|
|
1363
|
+
return lines.join(`
|
|
1364
|
+
`);
|
|
1365
|
+
}
|
|
1366
|
+
var orchestratorConfigProvider = {
|
|
1367
|
+
name: "orchestrator_config",
|
|
1368
|
+
description: "Provides orchestrator configuration including subagents, A2A, and sandbox settings",
|
|
1369
|
+
get: async (runtime, _message, _state) => {
|
|
1370
|
+
const config = getOrchestratorConfig(runtime);
|
|
1371
|
+
return { text: formatConfigContext(config) };
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
221
1375
|
// src/providers/task-context.ts
|
|
222
1376
|
function getService2(runtime) {
|
|
223
1377
|
return runtime.getService("CODE_TASK");
|
|
@@ -279,7 +1433,7 @@ import {
|
|
|
279
1433
|
Service
|
|
280
1434
|
} from "@elizaos/core";
|
|
281
1435
|
|
|
282
|
-
// ../../../node_modules/uuid/dist-node/stringify.js
|
|
1436
|
+
// ../../../node_modules/.bun/uuid@13.0.0/node_modules/uuid/dist-node/stringify.js
|
|
283
1437
|
var byteToHex = [];
|
|
284
1438
|
for (let i = 0;i < 256; ++i) {
|
|
285
1439
|
byteToHex.push((i + 256).toString(16).slice(1));
|
|
@@ -288,7 +1442,7 @@ function unsafeStringify(arr, offset = 0) {
|
|
|
288
1442
|
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
289
1443
|
}
|
|
290
1444
|
|
|
291
|
-
// ../../../node_modules/uuid/dist-node/rng.js
|
|
1445
|
+
// ../../../node_modules/.bun/uuid@13.0.0/node_modules/uuid/dist-node/rng.js
|
|
292
1446
|
import { randomFillSync } from "node:crypto";
|
|
293
1447
|
var rnds8Pool = new Uint8Array(256);
|
|
294
1448
|
var poolPtr = rnds8Pool.length;
|
|
@@ -300,11 +1454,11 @@ function rng() {
|
|
|
300
1454
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
301
1455
|
}
|
|
302
1456
|
|
|
303
|
-
// ../../../node_modules/uuid/dist-node/native.js
|
|
1457
|
+
// ../../../node_modules/.bun/uuid@13.0.0/node_modules/uuid/dist-node/native.js
|
|
304
1458
|
import { randomUUID } from "node:crypto";
|
|
305
1459
|
var native_default = { randomUUID };
|
|
306
1460
|
|
|
307
|
-
// ../../../node_modules/uuid/dist-node/v4.js
|
|
1461
|
+
// ../../../node_modules/.bun/uuid@13.0.0/node_modules/uuid/dist-node/v4.js
|
|
308
1462
|
function _v4(options, buf, offset) {
|
|
309
1463
|
options = options || {};
|
|
310
1464
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
@@ -782,12 +1936,1824 @@ Provider: ${provider.label} (${provider.id})`);
|
|
|
782
1936
|
`).trim();
|
|
783
1937
|
}
|
|
784
1938
|
}
|
|
1939
|
+
|
|
1940
|
+
// src/services/messaging-service.ts
|
|
1941
|
+
import crypto2 from "node:crypto";
|
|
1942
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
1943
|
+
import { EventType, Service as Service2 } from "@elizaos/core";
|
|
1944
|
+
class MessagingService extends Service2 {
|
|
1945
|
+
static serviceType = "MESSAGING";
|
|
1946
|
+
capabilityDescription = "Unified cross-platform messaging for sending messages to any supported channel";
|
|
1947
|
+
emitter = new EventEmitter2;
|
|
1948
|
+
adapters = new Map;
|
|
1949
|
+
pendingDeliveries = new Map;
|
|
1950
|
+
initialized = false;
|
|
1951
|
+
static async start(runtime) {
|
|
1952
|
+
const service = new MessagingService(runtime);
|
|
1953
|
+
await service.initialize();
|
|
1954
|
+
return service;
|
|
1955
|
+
}
|
|
1956
|
+
async initialize() {
|
|
1957
|
+
if (this.initialized) {
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
this.initialized = true;
|
|
1961
|
+
this.registerBuiltInAdapters();
|
|
1962
|
+
}
|
|
1963
|
+
registerBuiltInAdapters() {
|
|
1964
|
+
this.registerAdapter({
|
|
1965
|
+
channel: "discord",
|
|
1966
|
+
isAvailable: () => {
|
|
1967
|
+
const service = this.runtime.getService("DISCORD");
|
|
1968
|
+
return !!service;
|
|
1969
|
+
},
|
|
1970
|
+
send: async (params) => this.sendViaDiscord(params)
|
|
1971
|
+
});
|
|
1972
|
+
this.registerAdapter({
|
|
1973
|
+
channel: "telegram",
|
|
1974
|
+
isAvailable: () => {
|
|
1975
|
+
const service = this.runtime.getService("TELEGRAM");
|
|
1976
|
+
return !!service;
|
|
1977
|
+
},
|
|
1978
|
+
send: async (params) => this.sendViaTelegram(params)
|
|
1979
|
+
});
|
|
1980
|
+
this.registerAdapter({
|
|
1981
|
+
channel: "slack",
|
|
1982
|
+
isAvailable: () => {
|
|
1983
|
+
const service = this.runtime.getService("slack");
|
|
1984
|
+
return !!service;
|
|
1985
|
+
},
|
|
1986
|
+
send: async (params) => this.sendViaSlack(params)
|
|
1987
|
+
});
|
|
1988
|
+
this.registerAdapter({
|
|
1989
|
+
channel: "whatsapp",
|
|
1990
|
+
isAvailable: () => {
|
|
1991
|
+
const service = this.runtime.getService("whatsapp");
|
|
1992
|
+
return !!service;
|
|
1993
|
+
},
|
|
1994
|
+
send: async (params) => this.sendViaWhatsApp(params)
|
|
1995
|
+
});
|
|
1996
|
+
this.registerAdapter({
|
|
1997
|
+
channel: "twitch",
|
|
1998
|
+
isAvailable: () => {
|
|
1999
|
+
const service = this.runtime.getService("twitch");
|
|
2000
|
+
return !!service;
|
|
2001
|
+
},
|
|
2002
|
+
send: async (params) => this.sendViaTwitch(params)
|
|
2003
|
+
});
|
|
2004
|
+
this.registerAdapter({
|
|
2005
|
+
channel: "internal",
|
|
2006
|
+
isAvailable: () => true,
|
|
2007
|
+
send: async (params) => this.sendViaInternal(params)
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
registerAdapter(adapter) {
|
|
2011
|
+
this.adapters.set(adapter.channel, adapter);
|
|
2012
|
+
}
|
|
2013
|
+
getAdapter(channel) {
|
|
2014
|
+
return this.adapters.get(channel);
|
|
2015
|
+
}
|
|
2016
|
+
getAvailableChannels() {
|
|
2017
|
+
const channels = [];
|
|
2018
|
+
for (const [channel, adapter] of this.adapters) {
|
|
2019
|
+
if (adapter.isAvailable()) {
|
|
2020
|
+
channels.push(channel);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return channels;
|
|
2024
|
+
}
|
|
2025
|
+
async send(params) {
|
|
2026
|
+
const idempotencyKey = params.idempotencyKey ?? crypto2.randomUUID();
|
|
2027
|
+
const channel = params.target.channel;
|
|
2028
|
+
const existing = this.pendingDeliveries.get(idempotencyKey);
|
|
2029
|
+
if (existing) {
|
|
2030
|
+
const result = {
|
|
2031
|
+
success: existing.status.status === "sent" || existing.status.status === "delivered",
|
|
2032
|
+
channel,
|
|
2033
|
+
targetId: params.target.to
|
|
2034
|
+
};
|
|
2035
|
+
if (existing.status.messageId)
|
|
2036
|
+
result.messageId = existing.status.messageId;
|
|
2037
|
+
if (existing.status.error)
|
|
2038
|
+
result.error = existing.status.error;
|
|
2039
|
+
if (existing.status.status === "sent")
|
|
2040
|
+
result.sentAt = existing.status.updatedAt;
|
|
2041
|
+
return result;
|
|
2042
|
+
}
|
|
2043
|
+
const status = {
|
|
2044
|
+
status: "pending",
|
|
2045
|
+
updatedAt: Date.now()
|
|
2046
|
+
};
|
|
2047
|
+
this.pendingDeliveries.set(idempotencyKey, { params, status });
|
|
2048
|
+
this.emitMessagingEvent("MESSAGING_SEND_REQUESTED", {
|
|
2049
|
+
idempotencyKey,
|
|
2050
|
+
channel,
|
|
2051
|
+
targetId: params.target.to,
|
|
2052
|
+
status: "pending"
|
|
2053
|
+
});
|
|
2054
|
+
const adapter = this.adapters.get(channel);
|
|
2055
|
+
if (!adapter) {
|
|
2056
|
+
const errorMsg = `No adapter registered for channel: ${channel}`;
|
|
2057
|
+
const result = {
|
|
2058
|
+
success: false,
|
|
2059
|
+
channel,
|
|
2060
|
+
targetId: params.target.to,
|
|
2061
|
+
error: errorMsg
|
|
2062
|
+
};
|
|
2063
|
+
status.status = "failed";
|
|
2064
|
+
status.error = errorMsg;
|
|
2065
|
+
status.updatedAt = Date.now();
|
|
2066
|
+
this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
|
|
2067
|
+
idempotencyKey,
|
|
2068
|
+
channel,
|
|
2069
|
+
targetId: params.target.to,
|
|
2070
|
+
status: "failed",
|
|
2071
|
+
error: errorMsg
|
|
2072
|
+
});
|
|
2073
|
+
return result;
|
|
2074
|
+
}
|
|
2075
|
+
if (!adapter.isAvailable()) {
|
|
2076
|
+
const errorMsg = `${channel} service is not available`;
|
|
2077
|
+
const result = {
|
|
2078
|
+
success: false,
|
|
2079
|
+
channel,
|
|
2080
|
+
targetId: params.target.to,
|
|
2081
|
+
error: errorMsg
|
|
2082
|
+
};
|
|
2083
|
+
status.status = "failed";
|
|
2084
|
+
status.error = errorMsg;
|
|
2085
|
+
status.updatedAt = Date.now();
|
|
2086
|
+
this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
|
|
2087
|
+
idempotencyKey,
|
|
2088
|
+
channel,
|
|
2089
|
+
targetId: params.target.to,
|
|
2090
|
+
status: "failed",
|
|
2091
|
+
error: errorMsg
|
|
2092
|
+
});
|
|
2093
|
+
return result;
|
|
2094
|
+
}
|
|
2095
|
+
try {
|
|
2096
|
+
const result = await adapter.send({ ...params, idempotencyKey });
|
|
2097
|
+
status.status = result.success ? "sent" : "failed";
|
|
2098
|
+
if (result.messageId)
|
|
2099
|
+
status.messageId = result.messageId;
|
|
2100
|
+
if (result.error)
|
|
2101
|
+
status.error = result.error;
|
|
2102
|
+
status.updatedAt = Date.now();
|
|
2103
|
+
if (result.success) {
|
|
2104
|
+
const sentPayload = {
|
|
2105
|
+
idempotencyKey,
|
|
2106
|
+
channel,
|
|
2107
|
+
targetId: params.target.to,
|
|
2108
|
+
status: "sent"
|
|
2109
|
+
};
|
|
2110
|
+
if (result.messageId)
|
|
2111
|
+
sentPayload.messageId = result.messageId;
|
|
2112
|
+
if (result.sentAt)
|
|
2113
|
+
sentPayload.sentAt = result.sentAt;
|
|
2114
|
+
this.emitMessagingEvent("MESSAGING_SENT", sentPayload);
|
|
2115
|
+
} else {
|
|
2116
|
+
const failedPayload = {
|
|
2117
|
+
idempotencyKey,
|
|
2118
|
+
channel,
|
|
2119
|
+
targetId: params.target.to,
|
|
2120
|
+
status: "failed"
|
|
2121
|
+
};
|
|
2122
|
+
if (result.error)
|
|
2123
|
+
failedPayload.error = result.error;
|
|
2124
|
+
this.emitMessagingEvent("MESSAGING_SEND_FAILED", failedPayload);
|
|
2125
|
+
}
|
|
2126
|
+
return result;
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2129
|
+
status.status = "failed";
|
|
2130
|
+
status.error = errorMessage;
|
|
2131
|
+
status.updatedAt = Date.now();
|
|
2132
|
+
this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
|
|
2133
|
+
idempotencyKey,
|
|
2134
|
+
channel,
|
|
2135
|
+
targetId: params.target.to,
|
|
2136
|
+
status: "failed",
|
|
2137
|
+
error: errorMessage
|
|
2138
|
+
});
|
|
2139
|
+
return {
|
|
2140
|
+
success: false,
|
|
2141
|
+
channel,
|
|
2142
|
+
targetId: params.target.to,
|
|
2143
|
+
error: errorMessage
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
async sendToDeliveryContext(deliveryContext, content, options) {
|
|
2148
|
+
const channel = this.normalizeChannel(deliveryContext.channel);
|
|
2149
|
+
const to = deliveryContext.to ?? deliveryContext.accountId ?? "";
|
|
2150
|
+
if (!to) {
|
|
2151
|
+
return {
|
|
2152
|
+
success: false,
|
|
2153
|
+
channel,
|
|
2154
|
+
targetId: "",
|
|
2155
|
+
error: "No recipient specified in delivery context"
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
return this.send({
|
|
2159
|
+
target: {
|
|
2160
|
+
channel,
|
|
2161
|
+
to,
|
|
2162
|
+
...deliveryContext.accountId ? { accountId: deliveryContext.accountId } : {},
|
|
2163
|
+
...deliveryContext.threadId !== undefined ? { threadId: deliveryContext.threadId } : {}
|
|
2164
|
+
},
|
|
2165
|
+
content,
|
|
2166
|
+
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
|
|
2167
|
+
...options?.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
async sendToRoom(roomId, content, options) {
|
|
2171
|
+
const room = await this.runtime.getRoom(roomId);
|
|
2172
|
+
if (!room) {
|
|
2173
|
+
return {
|
|
2174
|
+
success: false,
|
|
2175
|
+
channel: "unknown",
|
|
2176
|
+
targetId: roomId,
|
|
2177
|
+
error: `Room not found: ${roomId}`
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
const metadata = room.metadata;
|
|
2181
|
+
const channel = this.normalizeChannel(metadata?.messagingChannel);
|
|
2182
|
+
const to = metadata?.messagingTo ?? room.channelId ?? "";
|
|
2183
|
+
if (!to) {
|
|
2184
|
+
return {
|
|
2185
|
+
success: false,
|
|
2186
|
+
channel,
|
|
2187
|
+
targetId: roomId,
|
|
2188
|
+
error: "Room has no messaging target configured"
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
return this.send({
|
|
2192
|
+
target: {
|
|
2193
|
+
channel,
|
|
2194
|
+
to,
|
|
2195
|
+
...metadata?.messagingAccountId ? { accountId: metadata.messagingAccountId } : {},
|
|
2196
|
+
...metadata?.messagingThreadId !== undefined ? { threadId: metadata.messagingThreadId } : {}
|
|
2197
|
+
},
|
|
2198
|
+
content,
|
|
2199
|
+
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
|
|
2200
|
+
...options?.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
async sendToSession(sessionKey, content, options) {
|
|
2204
|
+
const agentId = extractAgentIdFromSessionKey(sessionKey);
|
|
2205
|
+
const roomId = sessionKeyToRoomId(sessionKey, agentId);
|
|
2206
|
+
return this.sendToRoom(roomId, content, options);
|
|
2207
|
+
}
|
|
2208
|
+
async sendViaDiscord(params) {
|
|
2209
|
+
const discordService = this.runtime.getService("DISCORD");
|
|
2210
|
+
if (!discordService?.client) {
|
|
2211
|
+
return {
|
|
2212
|
+
success: false,
|
|
2213
|
+
channel: "discord",
|
|
2214
|
+
targetId: params.target.to,
|
|
2215
|
+
error: "Discord service not available"
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
try {
|
|
2219
|
+
const channel = await discordService.client.channels.fetch(params.target.to);
|
|
2220
|
+
if (!channel?.send) {
|
|
2221
|
+
return {
|
|
2222
|
+
success: false,
|
|
2223
|
+
channel: "discord",
|
|
2224
|
+
targetId: params.target.to,
|
|
2225
|
+
error: "Channel not found or not a text channel"
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
const message = await channel.send({
|
|
2229
|
+
content: params.content.text,
|
|
2230
|
+
...params.target.replyToMessageId ? { reply: { messageReference: params.target.replyToMessageId } } : {}
|
|
2231
|
+
});
|
|
2232
|
+
return {
|
|
2233
|
+
success: true,
|
|
2234
|
+
messageId: message.id,
|
|
2235
|
+
channel: "discord",
|
|
2236
|
+
targetId: params.target.to,
|
|
2237
|
+
sentAt: Date.now()
|
|
2238
|
+
};
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
return {
|
|
2241
|
+
success: false,
|
|
2242
|
+
channel: "discord",
|
|
2243
|
+
targetId: params.target.to,
|
|
2244
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
async sendViaTelegram(params) {
|
|
2249
|
+
const telegramService = this.runtime.getService("TELEGRAM");
|
|
2250
|
+
if (!telegramService?.bot?.telegram) {
|
|
2251
|
+
return {
|
|
2252
|
+
success: false,
|
|
2253
|
+
channel: "telegram",
|
|
2254
|
+
targetId: params.target.to,
|
|
2255
|
+
error: "Telegram service not available"
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
try {
|
|
2259
|
+
const chatId = Number.isNaN(Number(params.target.to)) ? params.target.to : Number(params.target.to);
|
|
2260
|
+
const result = await telegramService.bot.telegram.sendMessage(chatId, params.content.text, {
|
|
2261
|
+
reply_to_message_id: params.target.replyToMessageId ? Number(params.target.replyToMessageId) : undefined,
|
|
2262
|
+
disable_web_page_preview: params.content.disableLinkPreview,
|
|
2263
|
+
disable_notification: params.content.silent
|
|
2264
|
+
});
|
|
2265
|
+
return {
|
|
2266
|
+
success: true,
|
|
2267
|
+
messageId: String(result.message_id),
|
|
2268
|
+
channel: "telegram",
|
|
2269
|
+
targetId: params.target.to,
|
|
2270
|
+
sentAt: Date.now()
|
|
2271
|
+
};
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
return {
|
|
2274
|
+
success: false,
|
|
2275
|
+
channel: "telegram",
|
|
2276
|
+
targetId: params.target.to,
|
|
2277
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
async sendViaSlack(params) {
|
|
2282
|
+
const slackService = this.runtime.getService("slack");
|
|
2283
|
+
if (!slackService?.sendMessage) {
|
|
2284
|
+
return {
|
|
2285
|
+
success: false,
|
|
2286
|
+
channel: "slack",
|
|
2287
|
+
targetId: params.target.to,
|
|
2288
|
+
error: "Slack service not available or sendMessage method not found"
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
try {
|
|
2292
|
+
const result = await slackService.sendMessage(params.target.to, params.content.text, {
|
|
2293
|
+
...params.target.threadId ? { threadTs: String(params.target.threadId) } : {},
|
|
2294
|
+
replyBroadcast: false
|
|
2295
|
+
});
|
|
2296
|
+
return {
|
|
2297
|
+
success: true,
|
|
2298
|
+
messageId: result.ts,
|
|
2299
|
+
channel: "slack",
|
|
2300
|
+
targetId: params.target.to,
|
|
2301
|
+
sentAt: Date.now()
|
|
2302
|
+
};
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
return {
|
|
2305
|
+
success: false,
|
|
2306
|
+
channel: "slack",
|
|
2307
|
+
targetId: params.target.to,
|
|
2308
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
async sendViaWhatsApp(params) {
|
|
2313
|
+
const whatsappService = this.runtime.getService("whatsapp");
|
|
2314
|
+
if (!whatsappService?.sendText) {
|
|
2315
|
+
return {
|
|
2316
|
+
success: false,
|
|
2317
|
+
channel: "whatsapp",
|
|
2318
|
+
targetId: params.target.to,
|
|
2319
|
+
error: "WhatsApp service not available or sendText method not found"
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
try {
|
|
2323
|
+
const result = await whatsappService.sendText(params.target.to, params.content.text);
|
|
2324
|
+
const messageId = result.messages?.[0]?.id;
|
|
2325
|
+
return {
|
|
2326
|
+
success: true,
|
|
2327
|
+
...messageId ? { messageId } : {},
|
|
2328
|
+
channel: "whatsapp",
|
|
2329
|
+
targetId: params.target.to,
|
|
2330
|
+
sentAt: Date.now()
|
|
2331
|
+
};
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
return {
|
|
2334
|
+
success: false,
|
|
2335
|
+
channel: "whatsapp",
|
|
2336
|
+
targetId: params.target.to,
|
|
2337
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
async sendViaTwitch(params) {
|
|
2342
|
+
const twitchService = this.runtime.getService("twitch");
|
|
2343
|
+
if (!twitchService?.sendMessage) {
|
|
2344
|
+
return {
|
|
2345
|
+
success: false,
|
|
2346
|
+
channel: "twitch",
|
|
2347
|
+
targetId: params.target.to,
|
|
2348
|
+
error: "Twitch service not available or sendMessage method not found"
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
try {
|
|
2352
|
+
const result = await twitchService.sendMessage(params.content.text, {
|
|
2353
|
+
channel: params.target.to,
|
|
2354
|
+
...params.target.replyToMessageId ? { replyTo: params.target.replyToMessageId } : {}
|
|
2355
|
+
});
|
|
2356
|
+
return {
|
|
2357
|
+
success: result.success,
|
|
2358
|
+
...result.messageId !== undefined ? { messageId: result.messageId } : {},
|
|
2359
|
+
channel: "twitch",
|
|
2360
|
+
targetId: params.target.to,
|
|
2361
|
+
sentAt: Date.now()
|
|
2362
|
+
};
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
return {
|
|
2365
|
+
success: false,
|
|
2366
|
+
channel: "twitch",
|
|
2367
|
+
targetId: params.target.to,
|
|
2368
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async sendViaInternal(params) {
|
|
2373
|
+
const roomId = params.target.to;
|
|
2374
|
+
const messageId = crypto2.randomUUID();
|
|
2375
|
+
try {
|
|
2376
|
+
const memory = {
|
|
2377
|
+
id: messageId,
|
|
2378
|
+
entityId: this.runtime.agentId,
|
|
2379
|
+
agentId: this.runtime.agentId,
|
|
2380
|
+
roomId,
|
|
2381
|
+
content: {
|
|
2382
|
+
text: params.content.text,
|
|
2383
|
+
type: "text",
|
|
2384
|
+
source: "internal",
|
|
2385
|
+
metadata: {
|
|
2386
|
+
isInternalMessage: true,
|
|
2387
|
+
idempotencyKey: params.idempotencyKey
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
createdAt: Date.now()
|
|
2391
|
+
};
|
|
2392
|
+
await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
|
|
2393
|
+
runtime: this.runtime,
|
|
2394
|
+
message: memory,
|
|
2395
|
+
source: "internal_messaging"
|
|
2396
|
+
});
|
|
2397
|
+
return {
|
|
2398
|
+
success: true,
|
|
2399
|
+
messageId,
|
|
2400
|
+
channel: "internal",
|
|
2401
|
+
targetId: params.target.to,
|
|
2402
|
+
sentAt: Date.now()
|
|
2403
|
+
};
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
return {
|
|
2406
|
+
success: false,
|
|
2407
|
+
channel: "internal",
|
|
2408
|
+
targetId: params.target.to,
|
|
2409
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
normalizeChannel(channel) {
|
|
2414
|
+
if (!channel) {
|
|
2415
|
+
return "unknown";
|
|
2416
|
+
}
|
|
2417
|
+
const lower = channel.toLowerCase();
|
|
2418
|
+
if (lower === "discord" || lower.includes("discord")) {
|
|
2419
|
+
return "discord";
|
|
2420
|
+
}
|
|
2421
|
+
if (lower === "telegram" || lower.includes("telegram")) {
|
|
2422
|
+
return "telegram";
|
|
2423
|
+
}
|
|
2424
|
+
if (lower === "slack" || lower.includes("slack")) {
|
|
2425
|
+
return "slack";
|
|
2426
|
+
}
|
|
2427
|
+
if (lower === "whatsapp" || lower.includes("whatsapp")) {
|
|
2428
|
+
return "whatsapp";
|
|
2429
|
+
}
|
|
2430
|
+
if (lower === "twitch" || lower.includes("twitch")) {
|
|
2431
|
+
return "twitch";
|
|
2432
|
+
}
|
|
2433
|
+
if (lower === "google_chat" || lower.includes("google") || lower.includes("gchat")) {
|
|
2434
|
+
return "google_chat";
|
|
2435
|
+
}
|
|
2436
|
+
if (lower === "internal" || lower === "a2a") {
|
|
2437
|
+
return "internal";
|
|
2438
|
+
}
|
|
2439
|
+
return "unknown";
|
|
2440
|
+
}
|
|
2441
|
+
on(event, handler) {
|
|
2442
|
+
this.emitter.on(event, handler);
|
|
2443
|
+
}
|
|
2444
|
+
off(event, handler) {
|
|
2445
|
+
this.emitter.off(event, handler);
|
|
2446
|
+
}
|
|
2447
|
+
emitMessagingEvent(type, payload) {
|
|
2448
|
+
this.emitter.emit(type, payload);
|
|
2449
|
+
this.emitter.emit("messaging", { type, ...payload });
|
|
2450
|
+
}
|
|
2451
|
+
async stop() {
|
|
2452
|
+
this.pendingDeliveries.clear();
|
|
2453
|
+
this.emitter.removeAllListeners();
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/services/sandbox-service.ts
|
|
2458
|
+
import { spawn } from "node:child_process";
|
|
2459
|
+
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
2460
|
+
import fs from "node:fs/promises";
|
|
2461
|
+
import os from "node:os";
|
|
2462
|
+
import path from "node:path";
|
|
2463
|
+
import { Service as Service3 } from "@elizaos/core";
|
|
2464
|
+
var DEFAULT_DOCKER_CONFIG = {
|
|
2465
|
+
image: "ubuntu:22.04",
|
|
2466
|
+
containerPrefix: "eliza-sandbox",
|
|
2467
|
+
workdir: "/workspace",
|
|
2468
|
+
autoRemove: true,
|
|
2469
|
+
memoryLimit: "2g",
|
|
2470
|
+
cpuLimit: "2",
|
|
2471
|
+
network: "none",
|
|
2472
|
+
env: {},
|
|
2473
|
+
mounts: [],
|
|
2474
|
+
extraArgs: []
|
|
2475
|
+
};
|
|
2476
|
+
var DEFAULT_BROWSER_CONFIG = {
|
|
2477
|
+
enabled: false,
|
|
2478
|
+
image: "browserless/chrome:latest",
|
|
2479
|
+
containerPrefix: "eliza-browser",
|
|
2480
|
+
cdpPort: 9222,
|
|
2481
|
+
vncPort: 5900,
|
|
2482
|
+
noVncPort: 6080,
|
|
2483
|
+
headless: true,
|
|
2484
|
+
enableNoVnc: false,
|
|
2485
|
+
allowHostControl: false,
|
|
2486
|
+
autoStart: false,
|
|
2487
|
+
autoStartTimeoutMs: 30000
|
|
2488
|
+
};
|
|
2489
|
+
var DEFAULT_PRUNE_CONFIG = {
|
|
2490
|
+
idleHours: 1,
|
|
2491
|
+
maxAgeDays: 7
|
|
2492
|
+
};
|
|
2493
|
+
|
|
2494
|
+
class SandboxService extends Service3 {
|
|
2495
|
+
static serviceType = "SANDBOX";
|
|
2496
|
+
capabilityDescription = "Manages sandboxed execution environments for secure tool execution";
|
|
2497
|
+
emitter = new EventEmitter3;
|
|
2498
|
+
contexts = new Map;
|
|
2499
|
+
activeContainers = new Set;
|
|
2500
|
+
containerLocks = new Map;
|
|
2501
|
+
sweeper = null;
|
|
2502
|
+
initialized = false;
|
|
2503
|
+
static async start(runtime) {
|
|
2504
|
+
const service = new SandboxService(runtime);
|
|
2505
|
+
await service.initialize();
|
|
2506
|
+
return service;
|
|
2507
|
+
}
|
|
2508
|
+
async initialize() {
|
|
2509
|
+
if (this.initialized) {
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
this.initialized = true;
|
|
2513
|
+
this.startSweeper();
|
|
2514
|
+
}
|
|
2515
|
+
getConfig() {
|
|
2516
|
+
const settings = this.runtime.character?.settings;
|
|
2517
|
+
const sandbox = settings?.sandbox ?? {};
|
|
2518
|
+
return {
|
|
2519
|
+
mode: sandbox.mode ?? "off",
|
|
2520
|
+
scope: sandbox.scope ?? "session",
|
|
2521
|
+
workspaceAccess: sandbox.workspaceAccess ?? "rw",
|
|
2522
|
+
workspaceRoot: sandbox.workspaceRoot ?? path.join(os.homedir(), ".eliza", "sandboxes"),
|
|
2523
|
+
docker: { ...DEFAULT_DOCKER_CONFIG, ...sandbox.docker },
|
|
2524
|
+
browser: { ...DEFAULT_BROWSER_CONFIG, ...sandbox.browser },
|
|
2525
|
+
tools: sandbox.tools ?? { allow: [], deny: [] },
|
|
2526
|
+
prune: { ...DEFAULT_PRUNE_CONFIG, ...sandbox.prune }
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
shouldSandbox(sessionKey) {
|
|
2530
|
+
const config = this.getConfig();
|
|
2531
|
+
if (config.mode === "off") {
|
|
2532
|
+
return false;
|
|
2533
|
+
}
|
|
2534
|
+
if (config.mode === "all") {
|
|
2535
|
+
return true;
|
|
2536
|
+
}
|
|
2537
|
+
const parsed = parseSessionKey(sessionKey);
|
|
2538
|
+
if (parsed.keyType === "subagent") {
|
|
2539
|
+
return true;
|
|
2540
|
+
}
|
|
2541
|
+
const identifier = parsed.identifier.toLowerCase();
|
|
2542
|
+
if (identifier === "main" || identifier === "global") {
|
|
2543
|
+
return false;
|
|
2544
|
+
}
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2547
|
+
isToolAllowed(toolName, policy) {
|
|
2548
|
+
const config = this.getConfig();
|
|
2549
|
+
const p = policy ?? config.tools;
|
|
2550
|
+
if (p.allow && p.allow.length > 0) {
|
|
2551
|
+
const allowed = p.allow.some((pattern) => pattern === "*" || pattern.toLowerCase() === toolName.toLowerCase() || pattern.endsWith("*") && toolName.toLowerCase().startsWith(pattern.slice(0, -1).toLowerCase()));
|
|
2552
|
+
if (allowed) {
|
|
2553
|
+
return true;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (p.deny && p.deny.length > 0) {
|
|
2557
|
+
const denied = p.deny.some((pattern) => pattern === "*" || pattern.toLowerCase() === toolName.toLowerCase() || pattern.endsWith("*") && toolName.toLowerCase().startsWith(pattern.slice(0, -1).toLowerCase()));
|
|
2558
|
+
if (denied) {
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
return !p.allow || p.allow.length === 0;
|
|
2563
|
+
}
|
|
2564
|
+
async getSandboxContext(sessionKey, options) {
|
|
2565
|
+
const trimmedKey = sessionKey.trim();
|
|
2566
|
+
if (!trimmedKey) {
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
if (!this.shouldSandbox(trimmedKey)) {
|
|
2570
|
+
return null;
|
|
2571
|
+
}
|
|
2572
|
+
const cached = this.contexts.get(trimmedKey);
|
|
2573
|
+
if (cached) {
|
|
2574
|
+
cached.lastAccessedAt = Date.now();
|
|
2575
|
+
return cached;
|
|
2576
|
+
}
|
|
2577
|
+
const config = this.getConfig();
|
|
2578
|
+
const agentId = extractAgentIdFromSessionKey(trimmedKey);
|
|
2579
|
+
const agentWorkspaceDir = options?.workspaceDir ?? path.join(os.homedir(), ".eliza", "workspace");
|
|
2580
|
+
const scopeKey = this.resolveScopeKey(config.scope, trimmedKey);
|
|
2581
|
+
const sandboxWorkspaceDir = config.scope === "shared" ? config.workspaceRoot : path.join(config.workspaceRoot, scopeKey);
|
|
2582
|
+
const workspaceDir = config.workspaceAccess === "rw" ? agentWorkspaceDir : sandboxWorkspaceDir;
|
|
2583
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
2584
|
+
const containerName = `${config.docker.containerPrefix}-${scopeKey.slice(0, 12)}`;
|
|
2585
|
+
const context = {
|
|
2586
|
+
enabled: true,
|
|
2587
|
+
sessionKey: trimmedKey,
|
|
2588
|
+
roomId: options?.roomId ?? sessionKeyToRoomId(trimmedKey, agentId),
|
|
2589
|
+
workspaceDir,
|
|
2590
|
+
agentWorkspaceDir,
|
|
2591
|
+
workspaceAccess: config.workspaceAccess,
|
|
2592
|
+
containerName,
|
|
2593
|
+
containerWorkdir: config.docker.workdir,
|
|
2594
|
+
docker: config.docker,
|
|
2595
|
+
tools: config.tools,
|
|
2596
|
+
browserAllowHostControl: config.browser.allowHostControl,
|
|
2597
|
+
createdAt: Date.now(),
|
|
2598
|
+
lastAccessedAt: Date.now()
|
|
2599
|
+
};
|
|
2600
|
+
this.contexts.set(trimmedKey, context);
|
|
2601
|
+
this.emitSandboxEvent("SANDBOX_CREATED", {
|
|
2602
|
+
sessionKey: trimmedKey,
|
|
2603
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2604
|
+
containerName
|
|
2605
|
+
});
|
|
2606
|
+
return context;
|
|
2607
|
+
}
|
|
2608
|
+
resolveScopeKey(scope, sessionKey) {
|
|
2609
|
+
const parsed = parseSessionKey(sessionKey);
|
|
2610
|
+
switch (scope) {
|
|
2611
|
+
case "session":
|
|
2612
|
+
return hashToUUID(sessionKey).slice(0, 16);
|
|
2613
|
+
case "agent":
|
|
2614
|
+
return hashToUUID(parsed.agentId).slice(0, 16);
|
|
2615
|
+
case "shared":
|
|
2616
|
+
return "shared";
|
|
2617
|
+
default:
|
|
2618
|
+
return hashToUUID(sessionKey).slice(0, 16);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
async destroySandbox(sessionKey) {
|
|
2622
|
+
const context = this.contexts.get(sessionKey);
|
|
2623
|
+
if (!context) {
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
if (this.activeContainers.has(context.containerName)) {
|
|
2627
|
+
await this.stopContainer(context.containerName);
|
|
2628
|
+
}
|
|
2629
|
+
if (context.browser) {
|
|
2630
|
+
await this.stopContainer(context.browser.containerName);
|
|
2631
|
+
}
|
|
2632
|
+
this.contexts.delete(sessionKey);
|
|
2633
|
+
this.emitSandboxEvent("SANDBOX_DESTROYED", {
|
|
2634
|
+
sessionKey,
|
|
2635
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2636
|
+
containerName: context.containerName
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
async execute(sessionKey, params) {
|
|
2640
|
+
const context = await this.getSandboxContext(sessionKey);
|
|
2641
|
+
if (!context) {
|
|
2642
|
+
return this.executeLocal(params);
|
|
2643
|
+
}
|
|
2644
|
+
const startTime = Date.now();
|
|
2645
|
+
this.emitSandboxEvent("SANDBOX_COMMAND_STARTED", {
|
|
2646
|
+
sessionKey,
|
|
2647
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2648
|
+
containerName: context.containerName,
|
|
2649
|
+
command: params.command
|
|
2650
|
+
});
|
|
2651
|
+
try {
|
|
2652
|
+
const result = await this.executeInContainer(context, params);
|
|
2653
|
+
this.emitSandboxEvent("SANDBOX_COMMAND_COMPLETED", {
|
|
2654
|
+
sessionKey,
|
|
2655
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2656
|
+
containerName: context.containerName,
|
|
2657
|
+
command: params.command,
|
|
2658
|
+
result
|
|
2659
|
+
});
|
|
2660
|
+
return result;
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2663
|
+
const result = {
|
|
2664
|
+
success: false,
|
|
2665
|
+
exitCode: 1,
|
|
2666
|
+
stdout: "",
|
|
2667
|
+
stderr: errorMessage,
|
|
2668
|
+
durationMs: Date.now() - startTime,
|
|
2669
|
+
timedOut: false,
|
|
2670
|
+
error: errorMessage
|
|
2671
|
+
};
|
|
2672
|
+
this.emitSandboxEvent("SANDBOX_COMMAND_FAILED", {
|
|
2673
|
+
sessionKey,
|
|
2674
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2675
|
+
containerName: context.containerName,
|
|
2676
|
+
command: params.command,
|
|
2677
|
+
result,
|
|
2678
|
+
error: errorMessage
|
|
2679
|
+
});
|
|
2680
|
+
return result;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
executeLocal(params) {
|
|
2684
|
+
return new Promise((resolve) => {
|
|
2685
|
+
const startTime = Date.now();
|
|
2686
|
+
const timeout = params.timeoutMs ?? 60000;
|
|
2687
|
+
const child = spawn(params.command, params.args ?? [], {
|
|
2688
|
+
cwd: params.cwd,
|
|
2689
|
+
env: { ...process.env, ...params.env },
|
|
2690
|
+
shell: true
|
|
2691
|
+
});
|
|
2692
|
+
let stdout = "";
|
|
2693
|
+
let stderr = "";
|
|
2694
|
+
let timedOut = false;
|
|
2695
|
+
const timeoutId = setTimeout(() => {
|
|
2696
|
+
timedOut = true;
|
|
2697
|
+
child.kill("SIGKILL");
|
|
2698
|
+
}, timeout);
|
|
2699
|
+
child.stdout?.on("data", (data) => {
|
|
2700
|
+
stdout += data.toString();
|
|
2701
|
+
});
|
|
2702
|
+
child.stderr?.on("data", (data) => {
|
|
2703
|
+
stderr += data.toString();
|
|
2704
|
+
});
|
|
2705
|
+
if (params.stdin) {
|
|
2706
|
+
child.stdin?.write(params.stdin);
|
|
2707
|
+
child.stdin?.end();
|
|
2708
|
+
}
|
|
2709
|
+
child.on("close", (code) => {
|
|
2710
|
+
clearTimeout(timeoutId);
|
|
2711
|
+
const result = {
|
|
2712
|
+
success: code === 0 && !timedOut,
|
|
2713
|
+
exitCode: code ?? 1,
|
|
2714
|
+
stdout: stdout.slice(0, 1e5),
|
|
2715
|
+
stderr: stderr.slice(0, 1e5),
|
|
2716
|
+
durationMs: Date.now() - startTime,
|
|
2717
|
+
timedOut
|
|
2718
|
+
};
|
|
2719
|
+
if (timedOut)
|
|
2720
|
+
result.error = "Command timed out";
|
|
2721
|
+
resolve(result);
|
|
2722
|
+
});
|
|
2723
|
+
child.on("error", (error) => {
|
|
2724
|
+
clearTimeout(timeoutId);
|
|
2725
|
+
resolve({
|
|
2726
|
+
success: false,
|
|
2727
|
+
exitCode: 1,
|
|
2728
|
+
stdout,
|
|
2729
|
+
stderr: error.message,
|
|
2730
|
+
durationMs: Date.now() - startTime,
|
|
2731
|
+
timedOut: false,
|
|
2732
|
+
error: error.message
|
|
2733
|
+
});
|
|
2734
|
+
});
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
executeDockerCommand(args, options) {
|
|
2738
|
+
return new Promise((resolve) => {
|
|
2739
|
+
const startTime = Date.now();
|
|
2740
|
+
const timeout = options?.timeoutMs ?? 60000;
|
|
2741
|
+
const child = spawn("docker", args, {
|
|
2742
|
+
env: process.env
|
|
2743
|
+
});
|
|
2744
|
+
let stdout = "";
|
|
2745
|
+
let stderr = "";
|
|
2746
|
+
let timedOut = false;
|
|
2747
|
+
const timeoutId = setTimeout(() => {
|
|
2748
|
+
timedOut = true;
|
|
2749
|
+
child.kill("SIGKILL");
|
|
2750
|
+
}, timeout);
|
|
2751
|
+
child.stdout?.on("data", (data) => {
|
|
2752
|
+
stdout += data.toString();
|
|
2753
|
+
});
|
|
2754
|
+
child.stderr?.on("data", (data) => {
|
|
2755
|
+
stderr += data.toString();
|
|
2756
|
+
});
|
|
2757
|
+
child.on("close", (code) => {
|
|
2758
|
+
clearTimeout(timeoutId);
|
|
2759
|
+
const result = {
|
|
2760
|
+
success: code === 0 && !timedOut,
|
|
2761
|
+
exitCode: code ?? 1,
|
|
2762
|
+
stdout: stdout.slice(0, 1e5),
|
|
2763
|
+
stderr: stderr.slice(0, 1e5),
|
|
2764
|
+
durationMs: Date.now() - startTime,
|
|
2765
|
+
timedOut
|
|
2766
|
+
};
|
|
2767
|
+
if (timedOut)
|
|
2768
|
+
result.error = "Command timed out";
|
|
2769
|
+
resolve(result);
|
|
2770
|
+
});
|
|
2771
|
+
child.on("error", (error) => {
|
|
2772
|
+
clearTimeout(timeoutId);
|
|
2773
|
+
resolve({
|
|
2774
|
+
success: false,
|
|
2775
|
+
exitCode: 1,
|
|
2776
|
+
stdout,
|
|
2777
|
+
stderr: error.message,
|
|
2778
|
+
durationMs: Date.now() - startTime,
|
|
2779
|
+
timedOut: false,
|
|
2780
|
+
error: error.message
|
|
2781
|
+
});
|
|
2782
|
+
});
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
async executeInContainer(context, params) {
|
|
2786
|
+
const startTime = Date.now();
|
|
2787
|
+
const timeout = params.timeoutMs ?? 60000;
|
|
2788
|
+
await this.ensureContainer(context);
|
|
2789
|
+
const dockerArgs = ["exec"];
|
|
2790
|
+
const workdir = params.cwd ? path.join(context.containerWorkdir, params.cwd) : context.containerWorkdir;
|
|
2791
|
+
dockerArgs.push("-w", workdir);
|
|
2792
|
+
for (const [key, value] of Object.entries(params.env ?? {})) {
|
|
2793
|
+
dockerArgs.push("-e", `${key}=${value}`);
|
|
2794
|
+
}
|
|
2795
|
+
dockerArgs.push(context.containerName);
|
|
2796
|
+
dockerArgs.push("sh", "-c", params.command);
|
|
2797
|
+
return new Promise((resolve) => {
|
|
2798
|
+
const child = spawn("docker", dockerArgs, {
|
|
2799
|
+
env: process.env
|
|
2800
|
+
});
|
|
2801
|
+
let stdout = "";
|
|
2802
|
+
let stderr = "";
|
|
2803
|
+
let timedOut = false;
|
|
2804
|
+
const timeoutId = setTimeout(() => {
|
|
2805
|
+
timedOut = true;
|
|
2806
|
+
child.kill("SIGKILL");
|
|
2807
|
+
}, timeout);
|
|
2808
|
+
child.stdout?.on("data", (data) => {
|
|
2809
|
+
stdout += data.toString();
|
|
2810
|
+
});
|
|
2811
|
+
child.stderr?.on("data", (data) => {
|
|
2812
|
+
stderr += data.toString();
|
|
2813
|
+
});
|
|
2814
|
+
if (params.stdin) {
|
|
2815
|
+
child.stdin?.write(params.stdin);
|
|
2816
|
+
child.stdin?.end();
|
|
2817
|
+
}
|
|
2818
|
+
child.on("close", (code) => {
|
|
2819
|
+
clearTimeout(timeoutId);
|
|
2820
|
+
const result = {
|
|
2821
|
+
success: code === 0 && !timedOut,
|
|
2822
|
+
exitCode: code ?? 1,
|
|
2823
|
+
stdout: stdout.slice(0, 1e5),
|
|
2824
|
+
stderr: stderr.slice(0, 1e5),
|
|
2825
|
+
durationMs: Date.now() - startTime,
|
|
2826
|
+
timedOut
|
|
2827
|
+
};
|
|
2828
|
+
if (timedOut)
|
|
2829
|
+
result.error = "Command timed out";
|
|
2830
|
+
resolve(result);
|
|
2831
|
+
});
|
|
2832
|
+
child.on("error", (error) => {
|
|
2833
|
+
clearTimeout(timeoutId);
|
|
2834
|
+
resolve({
|
|
2835
|
+
success: false,
|
|
2836
|
+
exitCode: 1,
|
|
2837
|
+
stdout,
|
|
2838
|
+
stderr: error.message,
|
|
2839
|
+
durationMs: Date.now() - startTime,
|
|
2840
|
+
timedOut: false,
|
|
2841
|
+
error: error.message
|
|
2842
|
+
});
|
|
2843
|
+
});
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
async ensureContainer(context) {
|
|
2847
|
+
if (this.activeContainers.has(context.containerName)) {
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
const existingLock = this.containerLocks.get(context.containerName);
|
|
2851
|
+
if (existingLock) {
|
|
2852
|
+
await existingLock;
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
const lockPromise = this.ensureContainerInternal(context);
|
|
2856
|
+
this.containerLocks.set(context.containerName, lockPromise);
|
|
2857
|
+
try {
|
|
2858
|
+
await lockPromise;
|
|
2859
|
+
} finally {
|
|
2860
|
+
this.containerLocks.delete(context.containerName);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
async ensureContainerInternal(context) {
|
|
2864
|
+
if (this.activeContainers.has(context.containerName)) {
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
const checkResult = await this.executeDockerCommand([
|
|
2868
|
+
"ps",
|
|
2869
|
+
"-a",
|
|
2870
|
+
"-q",
|
|
2871
|
+
"-f",
|
|
2872
|
+
`name=^${context.containerName}$`
|
|
2873
|
+
]);
|
|
2874
|
+
if (checkResult.stdout.trim()) {
|
|
2875
|
+
const startResult = await this.executeDockerCommand(["start", context.containerName]);
|
|
2876
|
+
if (startResult.success) {
|
|
2877
|
+
this.activeContainers.add(context.containerName);
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
const dockerArgs = ["run", "-d", "--name", context.containerName];
|
|
2882
|
+
if (context.docker.memoryLimit) {
|
|
2883
|
+
dockerArgs.push("-m", context.docker.memoryLimit);
|
|
2884
|
+
}
|
|
2885
|
+
if (context.docker.cpuLimit) {
|
|
2886
|
+
dockerArgs.push("--cpus", context.docker.cpuLimit);
|
|
2887
|
+
}
|
|
2888
|
+
if (context.docker.network) {
|
|
2889
|
+
dockerArgs.push("--network", context.docker.network);
|
|
2890
|
+
}
|
|
2891
|
+
const mountMode = context.workspaceAccess === "ro" ? "ro" : "rw";
|
|
2892
|
+
dockerArgs.push("-v", `${context.workspaceDir}:${context.containerWorkdir}:${mountMode}`);
|
|
2893
|
+
for (const mount of context.docker.mounts ?? []) {
|
|
2894
|
+
dockerArgs.push("-v", `${mount.host}:${mount.container}:${mount.mode}`);
|
|
2895
|
+
}
|
|
2896
|
+
for (const [key, value] of Object.entries(context.docker.env ?? {})) {
|
|
2897
|
+
dockerArgs.push("-e", `${key}=${value}`);
|
|
2898
|
+
}
|
|
2899
|
+
if (context.docker.extraArgs) {
|
|
2900
|
+
dockerArgs.push(...context.docker.extraArgs);
|
|
2901
|
+
}
|
|
2902
|
+
dockerArgs.push(context.docker.image, "tail", "-f", "/dev/null");
|
|
2903
|
+
const result = await this.executeDockerCommand(dockerArgs);
|
|
2904
|
+
if (result.success) {
|
|
2905
|
+
this.activeContainers.add(context.containerName);
|
|
2906
|
+
} else {
|
|
2907
|
+
throw new Error(`Failed to create container: ${result.stderr}`);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
async stopContainer(containerName) {
|
|
2911
|
+
await this.executeDockerCommand(["stop", containerName], { timeoutMs: 1e4 });
|
|
2912
|
+
await this.executeDockerCommand(["rm", "-f", containerName], { timeoutMs: 1e4 });
|
|
2913
|
+
this.activeContainers.delete(containerName);
|
|
2914
|
+
}
|
|
2915
|
+
async startBrowser(sessionKey) {
|
|
2916
|
+
const context = await this.getSandboxContext(sessionKey);
|
|
2917
|
+
if (!context) {
|
|
2918
|
+
return null;
|
|
2919
|
+
}
|
|
2920
|
+
const config = this.getConfig();
|
|
2921
|
+
if (!config.browser.enabled) {
|
|
2922
|
+
return null;
|
|
2923
|
+
}
|
|
2924
|
+
if (context.browser) {
|
|
2925
|
+
return context.browser;
|
|
2926
|
+
}
|
|
2927
|
+
const browserContainerName = `${config.browser.containerPrefix}-${context.containerName.slice(-12)}`;
|
|
2928
|
+
const browserArgs = [
|
|
2929
|
+
"run",
|
|
2930
|
+
"-d",
|
|
2931
|
+
"--name",
|
|
2932
|
+
browserContainerName,
|
|
2933
|
+
"-p",
|
|
2934
|
+
`${config.browser.cdpPort}:9222`
|
|
2935
|
+
];
|
|
2936
|
+
if (config.browser.enableNoVnc) {
|
|
2937
|
+
browserArgs.push("-p", `${config.browser.noVncPort}:6080`);
|
|
2938
|
+
}
|
|
2939
|
+
browserArgs.push(config.browser.image);
|
|
2940
|
+
const result = await this.executeDockerCommand(browserArgs);
|
|
2941
|
+
if (!result.success) {
|
|
2942
|
+
this.runtime.logger.error({
|
|
2943
|
+
sessionKey,
|
|
2944
|
+
error: result.stderr
|
|
2945
|
+
}, "Failed to start browser container");
|
|
2946
|
+
return null;
|
|
2947
|
+
}
|
|
2948
|
+
const browserContext = {
|
|
2949
|
+
bridgeUrl: `http://localhost:${config.browser.cdpPort}`,
|
|
2950
|
+
...config.browser.enableNoVnc ? { noVncUrl: `http://localhost:${config.browser.noVncPort}` } : {},
|
|
2951
|
+
containerName: browserContainerName
|
|
2952
|
+
};
|
|
2953
|
+
context.browser = browserContext;
|
|
2954
|
+
this.emitSandboxEvent("SANDBOX_BROWSER_STARTED", {
|
|
2955
|
+
sessionKey,
|
|
2956
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2957
|
+
containerName: browserContainerName
|
|
2958
|
+
});
|
|
2959
|
+
return browserContext;
|
|
2960
|
+
}
|
|
2961
|
+
async stopBrowser(sessionKey) {
|
|
2962
|
+
const context = this.contexts.get(sessionKey);
|
|
2963
|
+
if (!context?.browser) {
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
await this.stopContainer(context.browser.containerName);
|
|
2967
|
+
this.emitSandboxEvent("SANDBOX_BROWSER_STOPPED", {
|
|
2968
|
+
sessionKey,
|
|
2969
|
+
...context.roomId !== undefined ? { roomId: context.roomId } : {},
|
|
2970
|
+
containerName: context.browser.containerName
|
|
2971
|
+
});
|
|
2972
|
+
delete context.browser;
|
|
2973
|
+
}
|
|
2974
|
+
startSweeper() {
|
|
2975
|
+
if (this.sweeper) {
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const config = this.getConfig();
|
|
2979
|
+
const intervalMs = Math.max(config.prune.idleHours * 60 * 60 * 1000 / 4, 60000);
|
|
2980
|
+
this.sweeper = setInterval(() => {
|
|
2981
|
+
this.sweepIdleSandboxes();
|
|
2982
|
+
}, intervalMs);
|
|
2983
|
+
this.sweeper.unref?.();
|
|
2984
|
+
}
|
|
2985
|
+
sweepIdleSandboxes() {
|
|
2986
|
+
const config = this.getConfig();
|
|
2987
|
+
const idleMs = config.prune.idleHours * 60 * 60 * 1000;
|
|
2988
|
+
const maxAgeMs = config.prune.maxAgeDays * 24 * 60 * 60 * 1000;
|
|
2989
|
+
const now2 = Date.now();
|
|
2990
|
+
for (const [sessionKey, context] of this.contexts.entries()) {
|
|
2991
|
+
const idle = now2 - context.lastAccessedAt > idleMs;
|
|
2992
|
+
const tooOld = now2 - context.createdAt > maxAgeMs;
|
|
2993
|
+
if (idle || tooOld) {
|
|
2994
|
+
this.destroySandbox(sessionKey).catch((err) => {
|
|
2995
|
+
this.runtime.logger.error({
|
|
2996
|
+
sessionKey,
|
|
2997
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2998
|
+
}, "Failed to destroy idle sandbox");
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
on(event, handler) {
|
|
3004
|
+
this.emitter.on(event, handler);
|
|
3005
|
+
}
|
|
3006
|
+
off(event, handler) {
|
|
3007
|
+
this.emitter.off(event, handler);
|
|
3008
|
+
}
|
|
3009
|
+
emitSandboxEvent(type, payload) {
|
|
3010
|
+
this.emitter.emit(type, payload);
|
|
3011
|
+
this.emitter.emit("sandbox", { type, ...payload });
|
|
3012
|
+
}
|
|
3013
|
+
async stop() {
|
|
3014
|
+
if (this.sweeper) {
|
|
3015
|
+
clearInterval(this.sweeper);
|
|
3016
|
+
this.sweeper = null;
|
|
3017
|
+
}
|
|
3018
|
+
for (const sessionKey of this.contexts.keys()) {
|
|
3019
|
+
await this.destroySandbox(sessionKey);
|
|
3020
|
+
}
|
|
3021
|
+
this.emitter.removeAllListeners();
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
// src/services/subagent-service.ts
|
|
3026
|
+
import crypto3 from "node:crypto";
|
|
3027
|
+
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
3028
|
+
import {
|
|
3029
|
+
ChannelType,
|
|
3030
|
+
EventType as EventType2,
|
|
3031
|
+
Service as Service4
|
|
3032
|
+
} from "@elizaos/core";
|
|
3033
|
+
class SubagentService extends Service4 {
|
|
3034
|
+
static serviceType = "SUBAGENT";
|
|
3035
|
+
capabilityDescription = "Manages subagent spawning, lifecycle, and communication";
|
|
3036
|
+
emitter = new EventEmitter4;
|
|
3037
|
+
subagentRuns = new Map;
|
|
3038
|
+
activeRuns = new Map;
|
|
3039
|
+
sweeper = null;
|
|
3040
|
+
initialized = false;
|
|
3041
|
+
static async start(runtime) {
|
|
3042
|
+
const service = new SubagentService(runtime);
|
|
3043
|
+
await service.initialize();
|
|
3044
|
+
return service;
|
|
3045
|
+
}
|
|
3046
|
+
async initialize() {
|
|
3047
|
+
if (this.initialized) {
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
this.initialized = true;
|
|
3051
|
+
this.runtime.registerEvent(EventType2.RUN_ENDED, async (payload) => {
|
|
3052
|
+
await this.handleRunEnded(payload);
|
|
3053
|
+
});
|
|
3054
|
+
this.runtime.registerEvent(EventType2.RUN_TIMEOUT, async (payload) => {
|
|
3055
|
+
await this.handleRunTimeout(payload);
|
|
3056
|
+
});
|
|
3057
|
+
this.startSweeper();
|
|
3058
|
+
}
|
|
3059
|
+
getConfig() {
|
|
3060
|
+
const settings = this.runtime.character?.settings;
|
|
3061
|
+
const subagents = settings?.subagents ?? {};
|
|
3062
|
+
return {
|
|
3063
|
+
enabled: subagents.enabled !== false,
|
|
3064
|
+
...subagents.model !== undefined ? { model: subagents.model } : {},
|
|
3065
|
+
...subagents.thinking !== undefined ? { thinking: subagents.thinking } : {},
|
|
3066
|
+
timeoutSeconds: subagents.timeoutSeconds ?? 300,
|
|
3067
|
+
allowAgents: subagents.allowAgents ?? [],
|
|
3068
|
+
archiveAfterMinutes: subagents.archiveAfterMinutes ?? 60
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
getAgentToAgentPolicy() {
|
|
3072
|
+
const settings = this.runtime.character?.settings;
|
|
3073
|
+
const a2aConfig = settings?.agentToAgent ?? {};
|
|
3074
|
+
const enabled = a2aConfig.enabled === true;
|
|
3075
|
+
const allowRules = (a2aConfig.allow ?? []).map((rule) => ({
|
|
3076
|
+
source: rule.source ?? "*",
|
|
3077
|
+
target: rule.target ?? "*"
|
|
3078
|
+
}));
|
|
3079
|
+
return {
|
|
3080
|
+
enabled,
|
|
3081
|
+
allowRules,
|
|
3082
|
+
isAllowed: (sourceAgentId, targetAgentId) => {
|
|
3083
|
+
if (!enabled) {
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
if (sourceAgentId === targetAgentId) {
|
|
3087
|
+
return true;
|
|
3088
|
+
}
|
|
3089
|
+
const sourceNorm = normalizeAgentId2(sourceAgentId);
|
|
3090
|
+
const targetNorm = normalizeAgentId2(targetAgentId);
|
|
3091
|
+
for (const rule of allowRules) {
|
|
3092
|
+
const sourceMatch = rule.source === "*" || normalizeAgentId2(rule.source) === sourceNorm;
|
|
3093
|
+
const targetMatch = rule.target === "*" || normalizeAgentId2(rule.target) === targetNorm;
|
|
3094
|
+
if (sourceMatch && targetMatch) {
|
|
3095
|
+
return true;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return false;
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
async spawnSubagent(params, requesterContext) {
|
|
3103
|
+
const config = this.getConfig();
|
|
3104
|
+
if (!config.enabled) {
|
|
3105
|
+
return {
|
|
3106
|
+
status: "forbidden",
|
|
3107
|
+
error: "Subagent spawning is disabled"
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
if (requesterContext.sessionKey && isSubagentSessionKey2(requesterContext.sessionKey)) {
|
|
3111
|
+
return {
|
|
3112
|
+
status: "forbidden",
|
|
3113
|
+
error: "sessions_spawn is not allowed from sub-agent sessions"
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
|
|
3117
|
+
const targetAgentId = params.agentId ? normalizeAgentId2(params.agentId) : requesterAgentId;
|
|
3118
|
+
if (targetAgentId !== requesterAgentId) {
|
|
3119
|
+
const allowAgents = config.allowAgents ?? [];
|
|
3120
|
+
const allowAny = allowAgents.some((v) => v.trim() === "*");
|
|
3121
|
+
const allowSet = new Set(allowAgents.filter((v) => v.trim() && v.trim() !== "*").map((v) => normalizeAgentId2(v)));
|
|
3122
|
+
if (!allowAny && !allowSet.has(targetAgentId)) {
|
|
3123
|
+
return {
|
|
3124
|
+
status: "forbidden",
|
|
3125
|
+
error: `agentId "${targetAgentId}" is not allowed for subagent spawning`
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const childSessionKey = createSubagentSessionKey(targetAgentId);
|
|
3130
|
+
const runId = crypto3.randomUUID();
|
|
3131
|
+
const childRoomId = sessionKeyToRoomId(childSessionKey, targetAgentId);
|
|
3132
|
+
const roomMetadata = {
|
|
3133
|
+
isSubagent: true,
|
|
3134
|
+
sessionKey: childSessionKey,
|
|
3135
|
+
task: params.task,
|
|
3136
|
+
spawnedAt: Date.now(),
|
|
3137
|
+
cleanup: params.cleanup ?? "keep"
|
|
3138
|
+
};
|
|
3139
|
+
if (requesterContext.roomId)
|
|
3140
|
+
roomMetadata.parentRoomId = requesterContext.roomId;
|
|
3141
|
+
if (requesterContext.sessionKey)
|
|
3142
|
+
roomMetadata.parentSessionKey = requesterContext.sessionKey;
|
|
3143
|
+
if (params.label)
|
|
3144
|
+
roomMetadata.label = params.label;
|
|
3145
|
+
const childRoom = {
|
|
3146
|
+
id: childRoomId,
|
|
3147
|
+
name: params.label || `Subagent: ${params.task.slice(0, 50)}`,
|
|
3148
|
+
source: "subagent",
|
|
3149
|
+
type: ChannelType.SELF,
|
|
3150
|
+
channelId: childSessionKey,
|
|
3151
|
+
agentId: this.runtime.agentId,
|
|
3152
|
+
worldId: this.runtime.agentId,
|
|
3153
|
+
metadata: roomMetadata
|
|
3154
|
+
};
|
|
3155
|
+
await this.runtime.ensureRoomExists(childRoom);
|
|
3156
|
+
const now2 = Date.now();
|
|
3157
|
+
const archiveAfterMs = config.archiveAfterMinutes ? config.archiveAfterMinutes * 60000 : undefined;
|
|
3158
|
+
const record = {
|
|
3159
|
+
runId,
|
|
3160
|
+
childSessionKey,
|
|
3161
|
+
requesterSessionKey: requesterContext.sessionKey ?? "unknown",
|
|
3162
|
+
requesterDisplayKey: requesterContext.sessionKey ?? "main",
|
|
3163
|
+
task: params.task,
|
|
3164
|
+
cleanup: params.cleanup ?? "keep",
|
|
3165
|
+
createdAt: now2,
|
|
3166
|
+
startedAt: now2,
|
|
3167
|
+
cleanupHandled: false,
|
|
3168
|
+
roomId: childRoomId,
|
|
3169
|
+
worldId: this.runtime.agentId
|
|
3170
|
+
};
|
|
3171
|
+
const normalizedOrigin = normalizeDeliveryContext(requesterContext.origin);
|
|
3172
|
+
if (normalizedOrigin)
|
|
3173
|
+
record.requesterOrigin = normalizedOrigin;
|
|
3174
|
+
if (params.label)
|
|
3175
|
+
record.label = params.label;
|
|
3176
|
+
if (archiveAfterMs)
|
|
3177
|
+
record.archiveAtMs = now2 + archiveAfterMs;
|
|
3178
|
+
this.subagentRuns.set(runId, record);
|
|
3179
|
+
const spawnPayload = {
|
|
3180
|
+
runId,
|
|
3181
|
+
childSessionKey,
|
|
3182
|
+
childRoomId,
|
|
3183
|
+
task: params.task
|
|
3184
|
+
};
|
|
3185
|
+
if (requesterContext.sessionKey)
|
|
3186
|
+
spawnPayload.requesterSessionKey = requesterContext.sessionKey;
|
|
3187
|
+
if (requesterContext.roomId)
|
|
3188
|
+
spawnPayload.requesterRoomId = requesterContext.roomId;
|
|
3189
|
+
if (params.label)
|
|
3190
|
+
spawnPayload.label = params.label;
|
|
3191
|
+
this.emitSubagentEvent("SUBAGENT_SPAWN_REQUESTED", spawnPayload);
|
|
3192
|
+
const systemPromptParams = {
|
|
3193
|
+
childSessionKey,
|
|
3194
|
+
task: params.task
|
|
3195
|
+
};
|
|
3196
|
+
if (requesterContext.sessionKey)
|
|
3197
|
+
systemPromptParams.requesterSessionKey = requesterContext.sessionKey;
|
|
3198
|
+
if (requesterContext.origin)
|
|
3199
|
+
systemPromptParams.requesterOrigin = requesterContext.origin;
|
|
3200
|
+
if (params.label)
|
|
3201
|
+
systemPromptParams.label = params.label;
|
|
3202
|
+
const systemPrompt = this.buildSubagentSystemPrompt(systemPromptParams);
|
|
3203
|
+
const taskMetadata = {
|
|
3204
|
+
isSubagentTask: true,
|
|
3205
|
+
runId,
|
|
3206
|
+
systemPromptOverride: systemPrompt
|
|
3207
|
+
};
|
|
3208
|
+
const modelOverride = params.model || config.model;
|
|
3209
|
+
const thinkingOverride = params.thinking || config.thinking;
|
|
3210
|
+
if (modelOverride)
|
|
3211
|
+
taskMetadata.modelOverride = modelOverride;
|
|
3212
|
+
if (thinkingOverride)
|
|
3213
|
+
taskMetadata.thinkingOverride = thinkingOverride;
|
|
3214
|
+
const initialMessage = {
|
|
3215
|
+
id: hashToUUID(`${runId}-initial`),
|
|
3216
|
+
entityId: this.runtime.agentId,
|
|
3217
|
+
agentId: this.runtime.agentId,
|
|
3218
|
+
roomId: childRoomId,
|
|
3219
|
+
content: {
|
|
3220
|
+
text: params.task,
|
|
3221
|
+
type: "text",
|
|
3222
|
+
metadata: taskMetadata
|
|
3223
|
+
}
|
|
3224
|
+
};
|
|
3225
|
+
this.executeSubagentRun(runId, initialMessage, params.runTimeoutSeconds).catch((error) => {
|
|
3226
|
+
this.runtime.logger.error(`Subagent execution error [runId=${runId}]: ${error}`);
|
|
3227
|
+
this.handleSubagentError(runId, error);
|
|
3228
|
+
});
|
|
3229
|
+
return {
|
|
3230
|
+
status: "accepted",
|
|
3231
|
+
childSessionKey,
|
|
3232
|
+
childRoomId,
|
|
3233
|
+
runId,
|
|
3234
|
+
modelApplied: !!(params.model || config.model)
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
async executeSubagentRun(runId, initialMessage, timeoutSeconds) {
|
|
3238
|
+
const config = this.getConfig();
|
|
3239
|
+
const timeout = (timeoutSeconds ?? config.timeoutSeconds ?? 300) * 1000;
|
|
3240
|
+
const controller = new AbortController;
|
|
3241
|
+
this.activeRuns.set(runId, controller);
|
|
3242
|
+
const timeoutId = timeout > 0 ? setTimeout(() => {
|
|
3243
|
+
controller.abort();
|
|
3244
|
+
this.handleSubagentTimeout(runId);
|
|
3245
|
+
}, timeout) : null;
|
|
3246
|
+
try {
|
|
3247
|
+
await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
|
|
3248
|
+
runtime: this.runtime,
|
|
3249
|
+
message: initialMessage,
|
|
3250
|
+
source: "subagent"
|
|
3251
|
+
});
|
|
3252
|
+
await this.waitForCompletion(runId, timeout, controller.signal);
|
|
3253
|
+
} finally {
|
|
3254
|
+
if (timeoutId) {
|
|
3255
|
+
clearTimeout(timeoutId);
|
|
3256
|
+
}
|
|
3257
|
+
this.activeRuns.delete(runId);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
async waitForCompletion(runId, timeoutMs, signal) {
|
|
3261
|
+
const startTime = Date.now();
|
|
3262
|
+
const pollInterval = 500;
|
|
3263
|
+
while (!signal.aborted) {
|
|
3264
|
+
const record = this.subagentRuns.get(runId);
|
|
3265
|
+
if (!record) {
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
if (record.endedAt) {
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
if (Date.now() - startTime > timeoutMs + 5000) {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
handleSubagentTimeout(runId) {
|
|
3278
|
+
const record = this.subagentRuns.get(runId);
|
|
3279
|
+
if (!record || record.endedAt) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
record.endedAt = Date.now();
|
|
3283
|
+
record.outcome = { status: "timeout" };
|
|
3284
|
+
const timeoutPayload = {
|
|
3285
|
+
runId,
|
|
3286
|
+
childSessionKey: record.childSessionKey,
|
|
3287
|
+
task: record.task,
|
|
3288
|
+
status: "timeout"
|
|
3289
|
+
};
|
|
3290
|
+
if (record.roomId)
|
|
3291
|
+
timeoutPayload.childRoomId = record.roomId;
|
|
3292
|
+
this.emitSubagentEvent("SUBAGENT_RUN_TIMEOUT", timeoutPayload);
|
|
3293
|
+
this.announceSubagentResult(runId).catch((err) => {
|
|
3294
|
+
this.runtime.logger.error(`Failed to announce timeout [runId=${runId}]: ${err}`);
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
handleSubagentError(runId, error) {
|
|
3298
|
+
const record = this.subagentRuns.get(runId);
|
|
3299
|
+
if (!record) {
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
record.endedAt = Date.now();
|
|
3303
|
+
record.outcome = {
|
|
3304
|
+
status: "error",
|
|
3305
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3306
|
+
};
|
|
3307
|
+
const errorPayload = {
|
|
3308
|
+
runId,
|
|
3309
|
+
childSessionKey: record.childSessionKey,
|
|
3310
|
+
task: record.task,
|
|
3311
|
+
status: "error"
|
|
3312
|
+
};
|
|
3313
|
+
if (record.outcome.error)
|
|
3314
|
+
errorPayload.error = record.outcome.error;
|
|
3315
|
+
if (record.roomId)
|
|
3316
|
+
errorPayload.childRoomId = record.roomId;
|
|
3317
|
+
this.emitSubagentEvent("SUBAGENT_RUN_FAILED", errorPayload);
|
|
3318
|
+
this.announceSubagentResult(runId).catch((err) => {
|
|
3319
|
+
this.runtime.logger.error(`Failed to announce error [runId=${runId}]: ${err}`);
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
async handleRunEnded(payload) {
|
|
3323
|
+
const p = payload;
|
|
3324
|
+
if (!p.roomId) {
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
for (const [runId, record] of this.subagentRuns.entries()) {
|
|
3328
|
+
if (record.roomId === p.roomId && !record.endedAt) {
|
|
3329
|
+
record.endedAt = Date.now();
|
|
3330
|
+
record.outcome = { status: "ok" };
|
|
3331
|
+
const completedPayload = {
|
|
3332
|
+
runId,
|
|
3333
|
+
childSessionKey: record.childSessionKey,
|
|
3334
|
+
childRoomId: record.roomId,
|
|
3335
|
+
task: record.task,
|
|
3336
|
+
status: "completed",
|
|
3337
|
+
endedAt: record.endedAt,
|
|
3338
|
+
durationMs: record.endedAt - (record.startedAt ?? record.createdAt)
|
|
3339
|
+
};
|
|
3340
|
+
if (record.startedAt)
|
|
3341
|
+
completedPayload.startedAt = record.startedAt;
|
|
3342
|
+
this.emitSubagentEvent("SUBAGENT_RUN_COMPLETED", completedPayload);
|
|
3343
|
+
await this.announceSubagentResult(runId);
|
|
3344
|
+
break;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
async handleRunTimeout(payload) {
|
|
3349
|
+
const p = payload;
|
|
3350
|
+
if (!p.roomId) {
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
for (const [runId, record] of this.subagentRuns.entries()) {
|
|
3354
|
+
if (record.roomId === p.roomId && !record.endedAt) {
|
|
3355
|
+
this.handleSubagentTimeout(runId);
|
|
3356
|
+
break;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
async announceSubagentResult(runId) {
|
|
3361
|
+
const record = this.subagentRuns.get(runId);
|
|
3362
|
+
if (!record) {
|
|
3363
|
+
return false;
|
|
3364
|
+
}
|
|
3365
|
+
if (record.cleanupCompletedAt || record.cleanupHandled) {
|
|
3366
|
+
return false;
|
|
3367
|
+
}
|
|
3368
|
+
record.cleanupHandled = true;
|
|
3369
|
+
let reply;
|
|
3370
|
+
if (record.roomId) {
|
|
3371
|
+
const memories = await this.runtime.getMemories({
|
|
3372
|
+
tableName: "messages",
|
|
3373
|
+
roomId: record.roomId,
|
|
3374
|
+
count: 10
|
|
3375
|
+
});
|
|
3376
|
+
const lastAssistant = memories.filter((m) => m.entityId === this.runtime.agentId).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
|
|
3377
|
+
reply = lastAssistant?.content?.text;
|
|
3378
|
+
}
|
|
3379
|
+
const durationMs = record.endedAt ? record.endedAt - (record.startedAt ?? record.createdAt) : undefined;
|
|
3380
|
+
const statsLine = `Runtime: ${formatDurationShort(durationMs) ?? "n/a"} • Session: ${record.childSessionKey}`;
|
|
3381
|
+
const outcome = record.outcome ?? { status: "unknown" };
|
|
3382
|
+
const statusLabel = outcome.status === "ok" ? "completed successfully" : outcome.status === "timeout" ? "timed out" : outcome.status === "error" ? `failed: ${outcome.error || "unknown error"}` : "finished with unknown status";
|
|
3383
|
+
const taskLabel = record.label || record.task || "background task";
|
|
3384
|
+
const triggerMessage = [
|
|
3385
|
+
`A background task "${taskLabel}" just ${statusLabel}.`,
|
|
3386
|
+
"",
|
|
3387
|
+
"Findings:",
|
|
3388
|
+
reply || "(no output)",
|
|
3389
|
+
"",
|
|
3390
|
+
statsLine,
|
|
3391
|
+
"",
|
|
3392
|
+
"Summarize this naturally for the user. Keep it brief (1-2 sentences).",
|
|
3393
|
+
"Do not mention technical details like tokens, stats, or that this was a background task.",
|
|
3394
|
+
"You can respond with NO_REPLY if no announcement is needed."
|
|
3395
|
+
].join(`
|
|
3396
|
+
`);
|
|
3397
|
+
if (record.requesterSessionKey && record.requesterSessionKey !== "unknown") {
|
|
3398
|
+
const requesterRoomId = sessionKeyToRoomId(record.requesterSessionKey, extractAgentIdFromSessionKey(record.requesterSessionKey));
|
|
3399
|
+
const metadata = {
|
|
3400
|
+
isSubagentAnnouncement: true,
|
|
3401
|
+
subagentRunId: runId
|
|
3402
|
+
};
|
|
3403
|
+
if (record.requesterOrigin) {
|
|
3404
|
+
metadata.deliveryContext = record.requesterOrigin;
|
|
3405
|
+
}
|
|
3406
|
+
const announceMessage = {
|
|
3407
|
+
id: hashToUUID(`${runId}-announce`),
|
|
3408
|
+
entityId: this.runtime.agentId,
|
|
3409
|
+
agentId: this.runtime.agentId,
|
|
3410
|
+
roomId: requesterRoomId,
|
|
3411
|
+
content: {
|
|
3412
|
+
text: triggerMessage,
|
|
3413
|
+
type: "text",
|
|
3414
|
+
metadata
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
|
|
3418
|
+
runtime: this.runtime,
|
|
3419
|
+
message: announceMessage,
|
|
3420
|
+
source: "subagent_announce"
|
|
3421
|
+
});
|
|
3422
|
+
}
|
|
3423
|
+
const announcePayload = {
|
|
3424
|
+
runId,
|
|
3425
|
+
childSessionKey: record.childSessionKey,
|
|
3426
|
+
task: record.task,
|
|
3427
|
+
outcome
|
|
3428
|
+
};
|
|
3429
|
+
if (record.requesterSessionKey)
|
|
3430
|
+
announcePayload.requesterSessionKey = record.requesterSessionKey;
|
|
3431
|
+
this.emitSubagentEvent("SUBAGENT_ANNOUNCE_SENT", announcePayload);
|
|
3432
|
+
record.cleanupCompletedAt = Date.now();
|
|
3433
|
+
if (record.cleanup === "delete") {
|
|
3434
|
+
this.subagentRuns.delete(runId);
|
|
3435
|
+
}
|
|
3436
|
+
return true;
|
|
3437
|
+
}
|
|
3438
|
+
buildSubagentSystemPrompt(params) {
|
|
3439
|
+
const taskText = typeof params.task === "string" && params.task.trim() ? params.task.replace(/\s+/g, " ").trim() : "{{TASK_DESCRIPTION}}";
|
|
3440
|
+
const lines = [
|
|
3441
|
+
"# Subagent Context",
|
|
3442
|
+
"",
|
|
3443
|
+
"You are a **subagent** spawned by the main agent for a specific task.",
|
|
3444
|
+
"",
|
|
3445
|
+
"## Your Role",
|
|
3446
|
+
`- You were created to handle: ${taskText}`,
|
|
3447
|
+
"- Complete this task. That's your entire purpose.",
|
|
3448
|
+
"- You are NOT the main agent. Don't try to be.",
|
|
3449
|
+
"",
|
|
3450
|
+
"## Rules",
|
|
3451
|
+
"1. **Stay focused** - Do your assigned task, nothing else",
|
|
3452
|
+
"2. **Complete the task** - Your final message will be automatically reported to the main agent",
|
|
3453
|
+
"3. **Don't initiate** - No heartbeats, no proactive actions, no side quests",
|
|
3454
|
+
"4. **Be ephemeral** - You may be terminated after task completion. That's fine.",
|
|
3455
|
+
"",
|
|
3456
|
+
"## Output Format",
|
|
3457
|
+
"When complete, your final response should include:",
|
|
3458
|
+
"- What you accomplished or found",
|
|
3459
|
+
"- Any relevant details the main agent should know",
|
|
3460
|
+
"- Keep it concise but informative",
|
|
3461
|
+
"",
|
|
3462
|
+
"## What You DON'T Do",
|
|
3463
|
+
"- NO user conversations (that's main agent's job)",
|
|
3464
|
+
"- NO external messages unless explicitly tasked with a specific recipient",
|
|
3465
|
+
"- NO cron jobs or persistent state",
|
|
3466
|
+
"- NO pretending to be the main agent",
|
|
3467
|
+
"",
|
|
3468
|
+
"## Session Context",
|
|
3469
|
+
params.label ? `- Label: ${params.label}` : undefined,
|
|
3470
|
+
params.requesterSessionKey ? `- Requester session: ${params.requesterSessionKey}` : undefined,
|
|
3471
|
+
params.requesterOrigin?.channel ? `- Requester channel: ${params.requesterOrigin.channel}` : undefined,
|
|
3472
|
+
`- Your session: ${params.childSessionKey}`,
|
|
3473
|
+
""
|
|
3474
|
+
].filter((line) => line !== undefined);
|
|
3475
|
+
return lines.join(`
|
|
3476
|
+
`);
|
|
3477
|
+
}
|
|
3478
|
+
async sendToAgent(params, requesterContext) {
|
|
3479
|
+
const runId = crypto3.randomUUID();
|
|
3480
|
+
const policy = this.getAgentToAgentPolicy();
|
|
3481
|
+
let targetSessionKey = params.sessionKey;
|
|
3482
|
+
if (!targetSessionKey && params.label) {
|
|
3483
|
+
const matchingRun = this.findSubagentRunByLabel(params.label, params.agentId);
|
|
3484
|
+
if (matchingRun) {
|
|
3485
|
+
targetSessionKey = matchingRun.childSessionKey;
|
|
3486
|
+
} else {
|
|
3487
|
+
return {
|
|
3488
|
+
status: "error",
|
|
3489
|
+
runId,
|
|
3490
|
+
error: `No subagent found with label "${params.label}"`
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
if (!targetSessionKey) {
|
|
3495
|
+
return {
|
|
3496
|
+
status: "error",
|
|
3497
|
+
runId,
|
|
3498
|
+
error: "Either sessionKey or label is required"
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
|
|
3502
|
+
const targetAgentId = extractAgentIdFromSessionKey(targetSessionKey);
|
|
3503
|
+
if (requesterAgentId !== targetAgentId) {
|
|
3504
|
+
if (!policy.enabled) {
|
|
3505
|
+
return {
|
|
3506
|
+
status: "forbidden",
|
|
3507
|
+
runId,
|
|
3508
|
+
error: "Agent-to-agent messaging is disabled. Set settings.agentToAgent.enabled=true to allow cross-agent sends."
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
3511
|
+
if (!policy.isAllowed(requesterAgentId, targetAgentId)) {
|
|
3512
|
+
return {
|
|
3513
|
+
status: "forbidden",
|
|
3514
|
+
runId,
|
|
3515
|
+
error: "Agent-to-agent messaging denied by policy."
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
const targetRoomId = sessionKeyToRoomId(targetSessionKey, targetAgentId);
|
|
3520
|
+
const a2aContextParams = {
|
|
3521
|
+
targetSessionKey
|
|
3522
|
+
};
|
|
3523
|
+
if (requesterContext.sessionKey)
|
|
3524
|
+
a2aContextParams.requesterSessionKey = requesterContext.sessionKey;
|
|
3525
|
+
const contextMessage = this.buildAgentToAgentContext(a2aContextParams);
|
|
3526
|
+
const a2aMetadata = {
|
|
3527
|
+
isAgentToAgent: true,
|
|
3528
|
+
runId,
|
|
3529
|
+
systemPromptOverride: contextMessage
|
|
3530
|
+
};
|
|
3531
|
+
if (requesterContext.sessionKey)
|
|
3532
|
+
a2aMetadata.senderSessionKey = requesterContext.sessionKey;
|
|
3533
|
+
if (requesterContext.roomId)
|
|
3534
|
+
a2aMetadata.senderRoomId = requesterContext.roomId;
|
|
3535
|
+
const message = {
|
|
3536
|
+
id: hashToUUID(`${runId}-a2a`),
|
|
3537
|
+
entityId: this.runtime.agentId,
|
|
3538
|
+
agentId: this.runtime.agentId,
|
|
3539
|
+
roomId: targetRoomId,
|
|
3540
|
+
content: {
|
|
3541
|
+
text: params.message,
|
|
3542
|
+
type: "text",
|
|
3543
|
+
metadata: a2aMetadata
|
|
3544
|
+
}
|
|
3545
|
+
};
|
|
3546
|
+
const timeoutMs = (params.timeoutSeconds ?? 30) * 1000;
|
|
3547
|
+
if (params.timeoutSeconds === 0) {
|
|
3548
|
+
this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
|
|
3549
|
+
runtime: this.runtime,
|
|
3550
|
+
message,
|
|
3551
|
+
source: "a2a"
|
|
3552
|
+
}).catch((err) => {
|
|
3553
|
+
this.runtime.logger.error(`A2A send error [runId=${runId}]: ${err}`);
|
|
3554
|
+
});
|
|
3555
|
+
const asyncPayload = {
|
|
3556
|
+
runId,
|
|
3557
|
+
childSessionKey: targetSessionKey,
|
|
3558
|
+
task: params.message
|
|
3559
|
+
};
|
|
3560
|
+
if (requesterContext.sessionKey)
|
|
3561
|
+
asyncPayload.requesterSessionKey = requesterContext.sessionKey;
|
|
3562
|
+
this.emitSubagentEvent("A2A_MESSAGE_SENT", asyncPayload);
|
|
3563
|
+
return {
|
|
3564
|
+
status: "accepted",
|
|
3565
|
+
runId,
|
|
3566
|
+
sessionKey: targetSessionKey,
|
|
3567
|
+
delivery: { status: "pending", mode: "async" }
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
const sentAt = Date.now();
|
|
3571
|
+
await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
|
|
3572
|
+
runtime: this.runtime,
|
|
3573
|
+
message,
|
|
3574
|
+
source: "a2a"
|
|
3575
|
+
});
|
|
3576
|
+
const pollIntervalMs = 500;
|
|
3577
|
+
const maxPolls = Math.ceil(timeoutMs / pollIntervalMs);
|
|
3578
|
+
let lastReply;
|
|
3579
|
+
for (let poll = 0;poll < maxPolls; poll++) {
|
|
3580
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
3581
|
+
const memories = await this.runtime.getMemories({
|
|
3582
|
+
tableName: "messages",
|
|
3583
|
+
roomId: targetRoomId,
|
|
3584
|
+
count: 10
|
|
3585
|
+
});
|
|
3586
|
+
const newReplies = memories.filter((m) => m.entityId === this.runtime.agentId && m.id !== message.id && m.createdAt && m.createdAt > sentAt);
|
|
3587
|
+
if (newReplies.length > 0) {
|
|
3588
|
+
lastReply = newReplies.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
|
|
3589
|
+
break;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
const syncPayload = {
|
|
3593
|
+
runId,
|
|
3594
|
+
childSessionKey: targetSessionKey,
|
|
3595
|
+
task: params.message
|
|
3596
|
+
};
|
|
3597
|
+
if (requesterContext.sessionKey)
|
|
3598
|
+
syncPayload.requesterSessionKey = requesterContext.sessionKey;
|
|
3599
|
+
this.emitSubagentEvent("A2A_MESSAGE_SENT", syncPayload);
|
|
3600
|
+
if (!lastReply) {
|
|
3601
|
+
return {
|
|
3602
|
+
status: "timeout",
|
|
3603
|
+
runId,
|
|
3604
|
+
sessionKey: targetSessionKey,
|
|
3605
|
+
error: `No response received within ${params.timeoutSeconds ?? 30} seconds`,
|
|
3606
|
+
delivery: { status: "timeout", mode: "sync" }
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
const result = {
|
|
3610
|
+
status: "ok",
|
|
3611
|
+
runId,
|
|
3612
|
+
sessionKey: targetSessionKey,
|
|
3613
|
+
delivery: { status: "delivered", mode: "sync" }
|
|
3614
|
+
};
|
|
3615
|
+
if (lastReply.content?.text)
|
|
3616
|
+
result.reply = lastReply.content.text;
|
|
3617
|
+
return result;
|
|
3618
|
+
}
|
|
3619
|
+
buildAgentToAgentContext(params) {
|
|
3620
|
+
return [
|
|
3621
|
+
"# Agent-to-Agent Message Context",
|
|
3622
|
+
"",
|
|
3623
|
+
"This message was sent by another agent session.",
|
|
3624
|
+
params.requesterSessionKey ? `- Sender: ${params.requesterSessionKey}` : undefined,
|
|
3625
|
+
`- Target: ${params.targetSessionKey}`,
|
|
3626
|
+
"",
|
|
3627
|
+
"Process this message and respond appropriately."
|
|
3628
|
+
].filter((l) => l !== undefined).join(`
|
|
3629
|
+
`);
|
|
3630
|
+
}
|
|
3631
|
+
getSubagentRun(runId) {
|
|
3632
|
+
return this.subagentRuns.get(runId);
|
|
3633
|
+
}
|
|
3634
|
+
findSubagentRunByLabel(label, agentId) {
|
|
3635
|
+
const normalizedLabel = label.toLowerCase().trim();
|
|
3636
|
+
const normalizedAgentId = agentId ? normalizeAgentId2(agentId) : undefined;
|
|
3637
|
+
for (const run of this.subagentRuns.values()) {
|
|
3638
|
+
const runLabel = run.label?.toLowerCase().trim();
|
|
3639
|
+
if (runLabel !== normalizedLabel) {
|
|
3640
|
+
continue;
|
|
3641
|
+
}
|
|
3642
|
+
if (normalizedAgentId) {
|
|
3643
|
+
const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
|
|
3644
|
+
if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
if (!run.endedAt) {
|
|
3649
|
+
return run;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
const completedRuns = [...this.subagentRuns.values()].filter((run) => {
|
|
3653
|
+
const runLabel = run.label?.toLowerCase().trim();
|
|
3654
|
+
if (runLabel !== normalizedLabel) {
|
|
3655
|
+
return false;
|
|
3656
|
+
}
|
|
3657
|
+
if (normalizedAgentId) {
|
|
3658
|
+
const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
|
|
3659
|
+
if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
|
|
3660
|
+
return false;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
return true;
|
|
3664
|
+
}).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
|
|
3665
|
+
return completedRuns[0];
|
|
3666
|
+
}
|
|
3667
|
+
listSubagentRuns(requesterSessionKey) {
|
|
3668
|
+
const runs = [...this.subagentRuns.values()];
|
|
3669
|
+
if (!requesterSessionKey) {
|
|
3670
|
+
return runs;
|
|
3671
|
+
}
|
|
3672
|
+
return runs.filter((r) => r.requesterSessionKey === requesterSessionKey);
|
|
3673
|
+
}
|
|
3674
|
+
cancelSubagentRun(runId) {
|
|
3675
|
+
const controller = this.activeRuns.get(runId);
|
|
3676
|
+
if (controller) {
|
|
3677
|
+
controller.abort();
|
|
3678
|
+
return true;
|
|
3679
|
+
}
|
|
3680
|
+
return false;
|
|
3681
|
+
}
|
|
3682
|
+
on(event, handler) {
|
|
3683
|
+
this.emitter.on(event, handler);
|
|
3684
|
+
}
|
|
3685
|
+
off(event, handler) {
|
|
3686
|
+
this.emitter.off(event, handler);
|
|
3687
|
+
}
|
|
3688
|
+
emitSubagentEvent(type, payload) {
|
|
3689
|
+
this.emitter.emit(type, payload);
|
|
3690
|
+
this.emitter.emit("task", { type, ...payload });
|
|
3691
|
+
}
|
|
3692
|
+
startSweeper() {
|
|
3693
|
+
if (this.sweeper) {
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
this.sweeper = setInterval(() => {
|
|
3697
|
+
this.sweepOldRuns();
|
|
3698
|
+
}, 60000);
|
|
3699
|
+
this.sweeper.unref?.();
|
|
3700
|
+
}
|
|
3701
|
+
sweepOldRuns() {
|
|
3702
|
+
const now2 = Date.now();
|
|
3703
|
+
for (const [runId, record] of this.subagentRuns.entries()) {
|
|
3704
|
+
if (record.archiveAtMs && record.archiveAtMs <= now2) {
|
|
3705
|
+
this.subagentRuns.delete(runId);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
async stop() {
|
|
3710
|
+
if (this.sweeper) {
|
|
3711
|
+
clearInterval(this.sweeper);
|
|
3712
|
+
this.sweeper = null;
|
|
3713
|
+
}
|
|
3714
|
+
for (const controller of this.activeRuns.values()) {
|
|
3715
|
+
controller.abort();
|
|
3716
|
+
}
|
|
3717
|
+
this.activeRuns.clear();
|
|
3718
|
+
this.emitter.removeAllListeners();
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
// src/types/messaging.ts
|
|
3722
|
+
var MessagingEventType = {
|
|
3723
|
+
SEND_REQUESTED: "MESSAGING_SEND_REQUESTED",
|
|
3724
|
+
SENT: "MESSAGING_SENT",
|
|
3725
|
+
SEND_FAILED: "MESSAGING_SEND_FAILED",
|
|
3726
|
+
DELIVERED: "MESSAGING_DELIVERED",
|
|
3727
|
+
READ: "MESSAGING_READ"
|
|
3728
|
+
};
|
|
3729
|
+
// src/types/sandbox.ts
|
|
3730
|
+
var SandboxEventType = {
|
|
3731
|
+
CREATED: "SANDBOX_CREATED",
|
|
3732
|
+
DESTROYED: "SANDBOX_DESTROYED",
|
|
3733
|
+
COMMAND_STARTED: "SANDBOX_COMMAND_STARTED",
|
|
3734
|
+
COMMAND_COMPLETED: "SANDBOX_COMMAND_COMPLETED",
|
|
3735
|
+
COMMAND_FAILED: "SANDBOX_COMMAND_FAILED",
|
|
3736
|
+
BROWSER_STARTED: "SANDBOX_BROWSER_STARTED",
|
|
3737
|
+
BROWSER_STOPPED: "SANDBOX_BROWSER_STOPPED"
|
|
3738
|
+
};
|
|
3739
|
+
// src/types/subagent.ts
|
|
3740
|
+
var SubagentEventType = {
|
|
3741
|
+
SPAWN_REQUESTED: "SUBAGENT_SPAWN_REQUESTED",
|
|
3742
|
+
RUN_STARTED: "SUBAGENT_RUN_STARTED",
|
|
3743
|
+
RUN_COMPLETED: "SUBAGENT_RUN_COMPLETED",
|
|
3744
|
+
RUN_FAILED: "SUBAGENT_RUN_FAILED",
|
|
3745
|
+
RUN_TIMEOUT: "SUBAGENT_RUN_TIMEOUT",
|
|
3746
|
+
ANNOUNCE_SENT: "SUBAGENT_ANNOUNCE_SENT",
|
|
3747
|
+
A2A_MESSAGE_SENT: "A2A_MESSAGE_SENT",
|
|
3748
|
+
A2A_MESSAGE_RECEIVED: "A2A_MESSAGE_RECEIVED"
|
|
3749
|
+
};
|
|
785
3750
|
// index.ts
|
|
3751
|
+
var sessionProviders = getSessionProviders2();
|
|
786
3752
|
var agentOrchestratorPlugin = {
|
|
787
3753
|
name: "agent-orchestrator",
|
|
788
|
-
description: "Orchestrate tasks across
|
|
789
|
-
services: [AgentOrchestratorService],
|
|
790
|
-
providers: [taskContextProvider],
|
|
3754
|
+
description: "Orchestrate tasks across agent providers with subagent spawning, agent-to-agent communication, sandboxed execution, and cross-platform messaging",
|
|
3755
|
+
services: [AgentOrchestratorService, SubagentService, SandboxService, MessagingService],
|
|
3756
|
+
providers: [taskContextProvider, orchestratorConfigProvider, ...sessionProviders],
|
|
791
3757
|
actions: [
|
|
792
3758
|
createTaskAction,
|
|
793
3759
|
listTasksAction,
|
|
@@ -795,23 +3761,103 @@ var agentOrchestratorPlugin = {
|
|
|
795
3761
|
searchTasksAction,
|
|
796
3762
|
pauseTaskAction,
|
|
797
3763
|
resumeTaskAction,
|
|
798
|
-
cancelTaskAction
|
|
3764
|
+
cancelTaskAction,
|
|
3765
|
+
spawnSubagentAction,
|
|
3766
|
+
sendToSessionAction,
|
|
3767
|
+
listSubagentsAction,
|
|
3768
|
+
cancelSubagentAction,
|
|
3769
|
+
getSubagentStatusAction,
|
|
3770
|
+
sendCrossPlatformMessageAction,
|
|
3771
|
+
sendToDeliveryContextAction,
|
|
3772
|
+
sendToRoomAction,
|
|
3773
|
+
sendToSessionMessageAction,
|
|
3774
|
+
listMessagingChannelsAction
|
|
799
3775
|
]
|
|
800
3776
|
};
|
|
801
3777
|
var typescript_default = agentOrchestratorPlugin;
|
|
802
3778
|
export {
|
|
3779
|
+
upsertSessionEntry,
|
|
3780
|
+
updateSessionStoreEntry,
|
|
3781
|
+
updateSessionStore,
|
|
3782
|
+
toAgentStoreSessionKey,
|
|
3783
|
+
toAgentRequestSessionKey,
|
|
803
3784
|
switchTaskAction,
|
|
3785
|
+
spawnSubagentAction,
|
|
3786
|
+
sessionKeyToRoomId,
|
|
3787
|
+
sendToSessionMessageAction,
|
|
3788
|
+
sendToSessionAction,
|
|
3789
|
+
sendToRoomAction,
|
|
3790
|
+
sendToDeliveryContextAction,
|
|
3791
|
+
sendCrossPlatformMessageAction,
|
|
804
3792
|
searchTasksAction,
|
|
3793
|
+
saveSessionStore,
|
|
805
3794
|
resumeTaskAction,
|
|
3795
|
+
resolveThreadSessionKeys,
|
|
3796
|
+
resolveThreadParentSessionKey,
|
|
3797
|
+
resolveStorePath,
|
|
3798
|
+
resolveStateDir,
|
|
3799
|
+
resolveSessionTranscriptPath,
|
|
3800
|
+
resolveDefaultSessionStorePath,
|
|
3801
|
+
resolveAgentSessionsDir,
|
|
3802
|
+
resolveAgentIdFromSessionKey,
|
|
806
3803
|
pauseTaskAction,
|
|
3804
|
+
parseSessionKey,
|
|
3805
|
+
parseAgentSessionKey,
|
|
3806
|
+
orchestratorConfigProvider,
|
|
3807
|
+
normalizeSessionKey,
|
|
3808
|
+
normalizeMainKey,
|
|
3809
|
+
normalizeDeliveryContext,
|
|
3810
|
+
normalizeAgentId as normalizeCoreAgentId,
|
|
3811
|
+
normalizeAgentId2 as normalizeAgentId,
|
|
3812
|
+
normalizeAccountId,
|
|
3813
|
+
mergeSessionEntry,
|
|
3814
|
+
mergeDeliveryContext,
|
|
3815
|
+
loadSessionStore,
|
|
807
3816
|
listTasksAction,
|
|
3817
|
+
listSubagentsAction,
|
|
3818
|
+
listSessionKeys,
|
|
3819
|
+
listMessagingChannelsAction,
|
|
3820
|
+
isValidSessionEntry,
|
|
3821
|
+
isSubagentSessionKey2 as isSubagentSessionKey,
|
|
3822
|
+
isSubagentSessionKey as isCoreSubagentSessionKey,
|
|
3823
|
+
isAcpSessionKey,
|
|
3824
|
+
hashToUUID,
|
|
3825
|
+
getSubagentStatusAction,
|
|
3826
|
+
getSessionProviders,
|
|
3827
|
+
getSessionEntry,
|
|
3828
|
+
getOrchestratorConfig,
|
|
808
3829
|
getConfiguredAgentOrchestratorOptions,
|
|
3830
|
+
formatTokenCount,
|
|
3831
|
+
formatDurationShort,
|
|
3832
|
+
extractSessionContext,
|
|
3833
|
+
extractAgentIdFromSessionKey,
|
|
3834
|
+
deleteSessionEntry,
|
|
809
3835
|
typescript_default as default,
|
|
810
3836
|
createTaskAction,
|
|
3837
|
+
createSubagentSessionKey,
|
|
3838
|
+
createSessionSkillsProvider,
|
|
3839
|
+
createSessionProvider,
|
|
3840
|
+
createSessionEntry,
|
|
3841
|
+
createSendPolicyProvider,
|
|
811
3842
|
configureAgentOrchestratorPlugin,
|
|
812
3843
|
cancelTaskAction,
|
|
3844
|
+
cancelSubagentAction,
|
|
3845
|
+
buildSubagentSessionKey,
|
|
3846
|
+
buildSessionKey,
|
|
3847
|
+
buildGroupHistoryKey,
|
|
3848
|
+
buildAgentSessionKey,
|
|
3849
|
+
buildAgentPeerSessionKey,
|
|
3850
|
+
buildAgentMainSessionKey,
|
|
3851
|
+
buildAcpSessionKey,
|
|
813
3852
|
agentOrchestratorPlugin,
|
|
3853
|
+
SubagentService,
|
|
3854
|
+
SubagentEventType,
|
|
3855
|
+
SessionStateManager,
|
|
3856
|
+
SandboxService,
|
|
3857
|
+
SandboxEventType,
|
|
3858
|
+
MessagingService,
|
|
3859
|
+
MessagingEventType,
|
|
814
3860
|
AgentOrchestratorService
|
|
815
3861
|
};
|
|
816
3862
|
|
|
817
|
-
//# debugId=
|
|
3863
|
+
//# debugId=4AD40853FB59A54864756E2164756E21
|